Changement de date dans le format du graphique de la barre de Pandas

j'ai un tracé de ligne empilé simple qui a exactement le format de date que je veux magiquement réglé en utilisant le code suivant.

df_ts = df.resample("W", how='max')
df_ts.plot(figsize=(12,8), stacked=True)

enter image description here

cependant, les dates se transforment mystérieusement en un format laid et illisible en traçant les mêmes données qu'un tracé en barre.

df_ts = df.resample("W", how='max')
df_ts.plot(kind='bar', figsize=(12,8), stacked=True)

enter image description here

les données originales ont été transformées un peu en avoir le max hebdomadaire. Pourquoi ce changement radical des dates fixées automatiquement se produit-il? Comment puis-je avoir les dates joliment formatées comme ci-dessus?

voici quelques données factices

start = pd.to_datetime("1-1-2012")
idx = pd.date_range(start, periods= 365).tolist()
df=pd.DataFrame({'A':np.random.random(365), 'B':np.random.random(365)})
df.index = idx
df_ts = df.resample('W', how= 'max')
df_ts.plot(kind='bar', stacked=True)
30
demandé sur Ted Petrou 2015-05-09 00:40:58

2 réponses

le code de pointage suppose que chaque barre dans un tracé en barre mérite sa propre étiquette. Vous pouvez modifier cette hypothèse en spécifiant votre propre formatteur:

ax.xaxis.set_major_formatter(formatter)

le pandas.tseries.converter.TimeSeries_DateFormatter que Pandas utilise pour formater les dates dans la" bonne "parcelle fonctionne bien avec parcelles de ligne lorsque le x-les valeurs sont des dates. Cependant, avec un graphique à barres les valeurs x (au moins ceux reçu par TimeSeries_DateFormatter.__call__ ) sont simplement des entiers à partir de à zéro . Si vous essayez d'utiliser TimeSeries_DateFormatter avec un graphique à barres, toutes les étiquettes commencent donc à l'époque, 1970-1-1 UTC, puisque c'est la date qui correspond à zéro. Donc le formatteur utilisé pour les tracés de ligne est malheureusement inutile pour la barre parcelles (au moins autant que je peux voir).

la façon la plus simple que je vois pour produire le formatage désiré est de générer et de définir les étiquettes explicitement:

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import matplotlib.ticker as ticker

start = pd.to_datetime("5-1-2012")
idx = pd.date_range(start, periods= 365)
df = pd.DataFrame({'A':np.random.random(365), 'B':np.random.random(365)})
df.index = idx
df_ts = df.resample('W', how= 'max')

ax = df_ts.plot(kind='bar', x=df_ts.index, stacked=True)

# Make most of the ticklabels empty so the labels don't get too crowded
ticklabels = ['']*len(df_ts.index)
# Every 4th ticklable shows the month and day
ticklabels[::4] = [item.strftime('%b %d') for item in df_ts.index[::4]]
# Every 12th ticklabel includes the year
ticklabels[::12] = [item.strftime('%b %d\n%Y') for item in df_ts.index[::12]]
ax.xaxis.set_major_formatter(ticker.FixedFormatter(ticklabels))
plt.gcf().autofmt_xdate()

plt.show()

les rendements enter image description here


pour ceux qui cherchent un exemple simple d'un tracé en barre avec des dates:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker

dates = pd.date_range('2012-1-1', '2017-1-1', freq='M')
df = pd.DataFrame({'A':np.random.random(len(dates)), 'Date':dates})
fig, ax = plt.subplots()
df.plot.bar(x='Date', y='A', ax=ax)
ticklabels = ['']*len(df)
skip = len(df)//12
ticklabels[::skip] = df['Date'].iloc[::skip].dt.strftime('%Y-%m-%d')
ax.xaxis.set_major_formatter(mticker.FixedFormatter(ticklabels))
fig.autofmt_xdate()

# fixes the tracker
# https://matplotlib.org/users/recipes.html
def fmt(x, pos=0, max_i=len(ticklabels)-1):
    i = int(x) 
    i = 0 if i < 0 else max_i if i > max_i else i
    return dates[i]
ax.fmt_xdata = fmt
plt.show()

enter image description here

33
répondu unutbu 2018-05-28 14:37:09

Voici une approche peut-être plus facile en utilisant mdates , mais vous oblige à boucler vos colonnes, appelant le tracé de barre de matplotlib. Voici un exemple où je trace juste une colonne et utilise des mises à jour pour les tiques et les étiquettes personnalisées ( EDIT fonction de boucle ajoutée pour tracer toutes les colonnes empilées):

import datetime
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates

def format_x_date_month_day(ax):   
    # Standard date x-axis formatting block, labels each month and ticks each day
    days = mdates.DayLocator()
    months = mdates.MonthLocator()  # every month
    dayFmt = mdates.DateFormatter('%D')
    monthFmt = mdates.DateFormatter('%Y-%m')
    ax.figure.autofmt_xdate()
    ax.xaxis.set_major_locator(months) 
    ax.xaxis.set_major_formatter(monthFmt)
    ax.xaxis.set_minor_locator(days)

def df_stacked_bar_formattable(df, ax, **kwargs):
    P = []
    lastBar = None

    for col in df.columns:
        X = df.index
        Y = df[col]
        if lastBar is not None:
            P.append(ax.bar(X, Y, bottom=lastBar, **kwargs))
        else:
            P.append(ax.bar(X, Y, **kwargs))
        lastBar = Y
    plt.legend([p[0] for p in P], df.columns)

span_days = 90
start = pd.to_datetime("1-1-2012")
idx = pd.date_range(start, periods=span_days).tolist()
df=pd.DataFrame(index=idx, data={'A':np.random.random(span_days), 'B':np.random.random(span_days)})

plt.close('all')
fig, ax = plt.subplots(1)
df_stacked_bar_formattable(df, ax)
format_x_date_month_day(ax)
plt.show()

(faisant référence à matplotlib.org pour l'exemple d'une boucle pour créer un diagramme en barres empilées.) Ce qui nous donne

enter image description here

une autre approche que devrait travailler et être beaucoup plus facile est d'utiliser df.plot.bar(ax=ax, stacked=True) , cependant il n'admet pas d'axe de date formatage avec mdates et fait l'objet de ma question .

1
répondu eecharlie 2017-12-25 07:04:32