Pandas размер графика штрихов изменяет формат даты
У меня есть простой многострочный график, который точно соответствует формату даты, который я хочу задать волшебным образом при использовании следующего кода.
df_ts = df.resample("W", how='max')
df_ts.plot(figsize=(12,8), stacked=True)
![enter image description here]()
Однако даты таинственным образом преобразуются в уродливый и нечитаемый формат при построении тех же данных, что и штриховой график.
df_ts = df.resample("W", how='max')
df_ts.plot(kind='bar', figsize=(12,8), stacked=True)
![enter image description here]()
Исходные данные были немного изменены, чтобы иметь недельный максимум. Почему это радикальное изменение в автоматически установленных датах? Как я могу иметь красиво отформатированные даты, как указано выше?
Вот некоторые фиктивные данные
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)
Ответы
Ответ 1
Код построения предполагает, что каждый столбец в столбчатом графике заслуживает своей собственной метки. Вы можете изменить это предположение, указав свой собственный форматер:
ax.xaxis.set_major_formatter(formatter)
pandas.tseries.converter.TimeSeries_DateFormatter
который Pandas использует для форматирования дат в "хорошем" графике, хорошо работает с линейными графиками, когда значения x представляют собой даты. Однако в гистограмме значения x (по крайней мере, полученные TimeSeries_DateFormatter.__call__
) - это просто целые числа, начинающиеся с нуля. Если вы попытаетесь использовать TimeSeries_DateFormatter
с гистограммой, все метки, таким образом, начнутся в эпоху 1970-1-1 UTC, поскольку это дата, которая соответствует нулю. Таким образом, форматер, используемый для линейных графиков, к сожалению, бесполезен для линейчатых графиков (по крайней мере, насколько я могу видеть).
Самым простым способом получения желаемого форматирования, которое я вижу, является явное создание и установка меток:
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()
урожайность ![enter image description here]()
Для тех, кто ищет простой пример гистограммы с датами:
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]()
Ответ 2
Я тоже боролся с этой проблемой, и после прочтения нескольких постов пришел к следующему решению, которое мне кажется немного более ясным, чем подход matplotlib.dates
.
Ярлыки без изменений:
timeline = pd.DatetimeIndex(start='2018, November', freq='M', periods=15)
df = pd.DataFrame({'date': timeline, 'value': np.random.randn(15)})
df.set_index('date', inplace=True)
df.plot(kind='bar', figsize=(12, 8), color='#2ecc71')
![enter image description here]()
Ярлыки с модификацией:
def line_format(label):
"""
Convert time label to the format of pandas line plot
"""
month = label.month_name()[:3]
if month == 'Jan':
month += f'\n{label.year}'
return month
# Note that we specify rot here
ax = df.plot(kind='bar', figsize=(12, 8), color='#2ecc71', rot=0)
ax.set_xticklabels(map(lambda x: line_format(x), df.index))
![enter image description here]()
Этот подход добавит год к метке, только если это январь
Ответ 3
Здесь, возможно, более простой подход с использованием mdates
, хотя требует, чтобы вы перебирали ваши столбцы, вызывая график штриховки из matplotlib. Вот пример, где я рисую только один столбец и использую mdates для настроенных тиков и меток ( EDIT Добавлена функция циклирования для построения всех столбцов):
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()
(Ссылка matplotlib.org, например, на цикл, чтобы создать график с разбивкой по строкам.) Это дает нам
![введите описание изображения здесь]()
Другим подходом, который должен работать и будет намного проще, является использование df.plot.bar(ax=ax, stacked=True)
, однако он не допускает форматирование оси даты с помощью mdates
и является субъектом мой вопрос.
Ответ 4
Возможно, не самый элегантный, но, надеюсь, простой способ:
fig = plt.figure()
ax = fig.add_subplot(111)
df_ts.plot(kind='bar', figsize=(12,8), stacked=True,ax=ax)
ax.set_xticklabels(''*len(df_ts.index))
df_ts.plot(linewidth=0, ax=ax) # This sets the nice x_ticks automatically
[РЕДАКТИРОВАТЬ]: топор = необходимость топора в df_ts.plot() ![enter image description here]()