Ted*_*rou 41 plot matplotlib pandas
我有一个简单的堆叠线图,它具有我想要使用以下代码时神奇地设置的日期格式.
df_ts = df.resample("W", how='max')
df_ts.plot(figsize=(12,8), stacked=True)

然而,当绘制与条形图相同的数据时,日期神秘地将自身转换为丑陋且不可读的格式.
df_ts = df.resample("W", how='max')
df_ts.plot(kind='bar', figsize=(12,8), stacked=True)

原始数据进行了一些转换,以获得每周最大值.为什么自动设定日期发生了根本变化?我怎样才能拥有如上所述的格式良好的日期?
这是一些虚拟数据
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)
unu*_*tbu 49
绘图代码假定条形图中的每个条形都应该有自己的标签.您可以通过指定自己的格式化程序来覆盖此假设:
ax.xaxis.set_major_formatter(formatter)
在pandas.tseries.converter.TimeSeries_DateFormatter与大熊猫使用在"好"的情节设置格式的日期行之有效线图当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()
对于那些寻找带有日期的条形图的简单示例的人:
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()
问题是pandas 条形图将日期变量处理为分类变量,其中每个日期都被视为唯一类别,因此 x 轴单位设置为从 0 开始的整数(就像未分配任何值时的默认 DataFrame 索引一样) )并且显示每个日期的完整字符串,没有任何自动格式。
以下是格式化时间序列的 pandas(堆叠)条形图的日期刻度标签的两种解决方案:
但首先,让我们看看当使用 pandas 线图绘制样本数据时,格式良好的刻度标签是什么样子的。
import numpy as np                 # v 1.19.2
import pandas as pd                # v 1.1.3
import matplotlib.dates as mdates  # v 3.3.2
# Create sample dataset with a daily frequency and resample it to a weekly frequency
rng = np.random.default_rng(seed=123) # random number generator
idx = pd.date_range(start='2012-01-01', end='2013-12-31', freq='D')
df_raw = pd.DataFrame(rng.random(size=(idx.size, 3)),
                      index=idx, columns=list('ABC'))
df = df_raw.resample('W').sum()  # default is 'W-SUN'
# Create pandas stacked line plot
ax = df.plot(stacked=True, figsize=(10,5))
由于数据按周分组,并带有星期日的时间戳(频率W-SUN),因此每月刻度标签不一定放置在该月的第一天,并且该月的每个第一周之间可能有 3 或 4 周,因此次要刻度为间距不均匀(如果仔细观察会发现)。以下是主要价格变动的确切日期:
# Convert major x ticks to date labels
np.array([mdates.num2date(tick*7-4).strftime('%Y-%b-%d') for tick in ax.get_xticks()])
"""
array(['2012-Jan-01', '2012-Apr-01', '2012-Jul-01', '2012-Oct-07',
       '2013-Jan-06', '2013-Apr-07', '2013-Jul-07', '2013-Oct-06',
       '2014-Jan-05'], dtype='<U11')
"""
挑战在于选择每月第一周的刻度,因为它们的间隔不均匀。其他答案提供了基于固定刻度频率的简单解决方案,该解决方案根据日期产生奇怪间隔的标签,其中月份有时可以重复(例如 unutbu 的答案中的 7 月)。或者他们提供了基于每月时间序列而不是每周时间序列的解决方案,这种格式更简单,因为每年总是有 12 个月。因此,这里有一个解决方案,可以提供格式良好的刻度标签,如 pandas 线图所示,并且适用于任何数据频率。
# Create pandas stacked bar chart
ax = df.plot.bar(stacked=True, figsize=(10,5))
# Create list of monthly timestamps by selecting the first weekly timestamp of each
# month (in this example, the first Sunday of each month)
monthly_timestamps = [timestamp for idx, timestamp in enumerate(df.index)
                      if (timestamp.month != df.index[idx-1].month) | (idx == 0)]
# Automatically select appropriate number of timestamps so that x-axis does
# not get overcrowded with tick labels
step = 1
while len(monthly_timestamps[::step]) > 10: # increase number if time range >3 years
    step += 1
timestamps = monthly_timestamps[::step]
# Create tick labels from timestamps
labels = [ts.strftime('%b\n%Y') if ts.year != timestamps[idx-1].year
          else ts.strftime('%b') for idx, ts in enumerate(timestamps)]
# Set major ticks and labels
ax.set_xticks([df.index.get_loc(ts) for ts in timestamps])
ax.set_xticklabels(labels)
# Set minor ticks without labels
ax.set_xticks([df.index.get_loc(ts) for ts in monthly_timestamps], minor=True)
# Rotate and center labels
ax.figure.autofmt_xdate(rotation=0, ha='center')
据我所知,无法使用matplotlib.dates(mdates)刻度定位器和格式化程序获得精确的标签格式。尽管如此,如果您更喜欢使用刻度定位器/格式化程序,或者如果您想在使用 matplotlib 的交互式界面(平移/放大和缩小)时拥有动态刻度,那么将 mdates 功能与 pandas 堆积条形图结合起来会派上用场。
此时,考虑直接在 matplotlib 中创建堆积条形图可能会很有用,您需要循环遍历变量来创建堆积条形图。下面显示的基于 pandas 的解决方案的工作原理是循环遍历条形图块,根据 matplotlib 日期单位重新定位它们。所以它基本上是一个循环而不是另一个循环,由您决定哪个更方便。
这个通用解决方案使用 mdatesAutoDateLocator将刻度放在月/年的开头。如果您使用pd.date_rangepandas 生成数据和时间戳(如本例所示),您应该记住,常用的'M'和'Y'频率会生成周期结束日期的时间戳。以下示例中给出的代码将每月/每年的刻度线与'MS'和'YS'频率对齐。
如果您使用期末日期(或未与刻度对齐的其他类型的 pandas 频率AutoDateLocator)导入数据集,我不知道有任何方便的方法来相应地移动 AutoDateLocator 以使标签与条正确对齐。我看到两个选项:i)使用重新采样数据,df.resample('MS').sum()如果这不会导致有关基础数据含义的任何问题;ii) 或者使用另一个日期定位器。
在以下示例中,此问题不会造成任何问题,因为数据具有周末频率,'W-SUN'因此放置在月/年开始频率处的月/年标签就可以了。
# Create pandas stacked bar chart with the default bar width = 0.5
ax = df.plot.bar(stacked=True, figsize=(10,5))
# Compute width of bars in matplotlib date units, 'md' (in days) and adjust it if
# the bar width in df.plot.bar has been set to something else than the default 0.5
bar_width_md_default, = np.diff(mdates.date2num(df.index[:2]))/2
bar_width = ax.patches[0].get_width()
bar_width_md = bar_width*bar_width_md_default/0.5
# Compute new x values in matplotlib date units for the patches (rectangles) that
# make up the stacked bars, adjusting the positions according to the bar width:
# if the frequency is in months (or years), the bars may not always be perfectly
# centered over the tick marks depending on the number of days difference between
# the months (or years) given by df.index[0] and [1] used to compute the bar 
# width, this should not be noticeable if the bars are wide enough.
x_bars_md = mdates.date2num(df.index) - bar_width_md/2
nvar = len(ax.get_legend_handles_labels()[1])
x_patches_md = np.ravel(nvar*[x_bars_md])
# Set bars to new x positions and adjust width: this loop works fine with NaN
# values as well because in bar plot NaNs are drawn with a rectangle of 0 height
# located at the foot of the bar, you can verify this with patch.get_bbox()
for patch, x_md in zip(ax.patches, x_patches_md):
    patch.set_x(x_md)
    patch.set_width(bar_width_md)
# Set major ticks
maj_loc = mdates.AutoDateLocator()
ax.xaxis.set_major_locator(maj_loc)
# Show minor tick under each bar (instead of each month) to highlight
# discrepancy between major tick locator and bar positions seeing as no tick
# locator is available for first-week-of-the-month frequency
ax.set_xticks(x_bars_md + bar_width_md/2, minor=True)
# Set major tick formatter
zfmts = ['', '%b\n%Y', '%b', '%b-%d', '%H:%M', '%H:%M']
fmt = mdates.ConciseDateFormatter(maj_loc, zero_formats=zfmts, show_offset=False)
ax.xaxis.set_major_formatter(fmt)
# Shift the plot frame to where the bars are now located
xmin = min(x_bars_md) - bar_width_md
xmax = max(x_bars_md) + 2*bar_width_md
ax.set_xlim(xmin, xmax)
# Adjust tick label format last, else it may sometimes not be applied correctly
ax.figure.autofmt_xdate(rotation=0, ha='center')
每个条形下方显示的小刻度线,以突出显示条形图的时间戳通常与刻度线标签标记的月/年开始不一致的事实AutoDateLocator。我不知道有任何日期定位器可用于选择每个月第一周的刻度并准确重现解决方案 1 中显示的结果。
文档:日期格式代码,mdates.ConciseDateFormatter
这里有一个简单的方法与大熊猫plot()和不使用matplotlib dates:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
# generate sample data
start = pd.to_datetime("1-1-2012")
index = pd.date_range(start, periods= 365)
df = pd.DataFrame({'A' : np.random.random(365), 'B' : np.random.random(365)}, index=index)
# resample to any timeframe you need, e.g. months
df_months = df.resample("M").sum()
# plot
fig, ax = plt.subplots()
df_months.plot(kind="bar", figsize=(16,5), stacked=True, ax=ax)
# format xtick-labels with list comprehension
ax.set_xticklabels([x.strftime("%Y-%m") for x in df_months.index], rotation=45)
plt.show()
我也一直在努力解决这个问题,在阅读了几篇文章之后,提出了以下解决方案,在我看来,这比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')
带有修改的标签:
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))
仅当是一月时,此方法才会在标签上加上年份
| 归档时间: | 
 | 
| 查看次数: | 20637 次 | 
| 最近记录: |