python - 为什么在使用 Pandas 的内置绘图调用而不是通过 Matplotlib 绘图时我的日期轴格式被破坏了?

标签 python pandas matplotlib

我正在使用 Pandas 和 Matlplotlib 在 Python 中绘制聚合数据。 我的轴自定义命令失败,因为我正在调用两个类似函数中的哪一个来制作条形图。工作案例例如:

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):   
    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)

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)
ax.bar(df.index, df.A)      # loop over columns here to do stacked plot
format_x_date_month_day(ax)
plt.show()

(参见 matplotlib.org 循环创建堆叠条形图的示例。)这给了我们

Bar plot called from Matplotlib, axis formatting by <code>mdates</code>

另一种应该有效并且更容易的方法是使用df.plot.bar(ax=ax, stacked=True),但是它不接受日期使用 mdates 的轴格式:

plt.close('all')
fig, ax = plt.subplots(1)
df.plot.bar(ax=ax, stacked=True)
format_x_date_month_day(ax)
plt.show()

Stacked bar with broken x-axis labeling

mdatesax.figure.autofmt_xdate() 如何与 df.plot.bar 配合使用?

最佳答案

pandas 中的条形图旨在比较类别,而不是显示时间序列或其他类型的连续变量,如 in the docstring 所述:

A bar plot shows comparisons among discrete categories. One axis of the plot shows the specific categories being compared, and the other axis represents a measured value.

这就是为什么无论 x 变量的数据类型如何,pandas 条形图的 x 轴刻度都是从零开始的整数。当使用 matplotlib 创建相同的条形图时,x 轴的比例由 matplotlib 日期数字组成,因此 matplotlib.dates 的刻度定位器和格式化程序模块 (mdates) 可以按预期使用。

为了能够将 pandas 条形图与 mdates 一起使用,您需要将条形沿 x 轴移动到与 matplotlib 日期数字匹配的位置。这要归功于 mdates.date2num功能。以下示例基于您提供的代码进行了一些修改:示例数据集包含 3 个变量,时间序列限制为 45 天,刻度格式已根据我的喜好进行了调整(并且未包装为函数)。

此示例适用于任意数量的变量(有或没有 NaN)以及传递给 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 random dataset
rng = np.random.default_rng(seed=1) # random number generator
nperiods = 45
nvar = 3
idx = pd.date_range('2012-01-01', periods=nperiods, freq='D')
df = pd.DataFrame(rng.integers(11, size=(idx.size, nvar)),
                  index=idx, columns=list('ABC'))

# Draw pandas stacked bar chart
ax = df.plot(kind='bar', stacked=True, figsize=(10,5))

# Compute width of bars in matplotlib date units
pandas_width = ax.patches[0].get_width() # the default bar width is 0.5
mdates_x0 = mdates.date2num(df.index[0])
mdates_x1 = mdates.date2num(df.index[1])
mdates_width_default = (mdates_x1-mdates_x0)/2
mdates_width = pandas_width*mdates_width_default/0.5 # rule of three conversion

# Compute new x values for bars in matplotlib date units, adjusting the
# positions according to the bar width
mdates_x = mdates.date2num(df.index) - mdates_width/2
nvar = len(ax.get_legend_handles_labels()[1])
mdates_x_patches = np.ravel(nvar*[mdates_x])

# Set bars to new x positions: 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, new_x in zip(ax.patches, mdates_x_patches):
    patch.set_x(new_x)
    patch.set_width(mdates_width)

# Set major and minor date tick locators
months = mdates.MonthLocator()
days = mdates.DayLocator(bymonthday=np.arange(31, step=3))
ax.xaxis.set_major_locator(months)
ax.xaxis.set_minor_locator(days)

# Set major date tick formatter
month_fmt = mdates.DateFormatter('\n%b\n%Y')
day_fmt = mdates.DateFormatter('%d')
ax.xaxis.set_major_formatter(month_fmt)
ax.xaxis.set_minor_formatter(day_fmt)

# Shift the plot frame to where the bars are now located
xmin = min(mdates_x) - mdates_width
xmax = max(mdates_x) + 2*mdates_width
ax.set_xlim(xmin, xmax)

# Adjust tick label format last, else it may produce unexpected results
ax.figure.autofmt_xdate(rotation=0, ha='center')

pd_barplot_days

由您决定这是否比使用 matplotlib 从头开始​​绘制堆叠条更方便。

可以稍微修改此解决方案,以根据任何时间频率为时间序列显示适当的刻度标签。下面是一个使用分钟频率、自定义条形宽度和自动日期刻度定位器和格式化程序的示例。仅显示新的/修改的代码行:

import matplotlib.ticker as mtick
#...

idx = pd.date_range('2012-01-01 12', periods=nperiods, freq='T')
#...

ax = df.plot(kind='bar', stacked=True, figsize=(10,5), width=0.3)
#...

# Set adaptive tick locators and major tick formatter
maj_loc = mdates.AutoDateLocator()
ax.xaxis.set_major_locator(maj_loc)
min_loc = mtick.FixedLocator(mdates_x + mdates_width/2)
ax.xaxis.set_minor_locator(min_loc) # draw minor tick under each bar
fmt = mdates.ConciseDateFormatter(maj_loc)
ax.xaxis.set_major_formatter(fmt)
#...

pd_barplot_minutes

您可能会注意到刻度线通常与条形图没有很好地对齐。将图形元素放在一起时,matplotlib 似乎存在一些问题。我发现这通常只有在绘制比有用的细条时才会引人注意。您可以通过运行 ax.get_xticks() 并将其与 patch.get_bbox() 循环 ax.patches.

关于python - 为什么在使用 Pandas 的内置绘图调用而不是通过 Matplotlib 绘图时我的日期轴格式被破坏了?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47965026/

相关文章:

python - 如何使用 exec 动态导入模块

python - 根据 header 身份验证 token 过滤 API 请求

先前缩放时 Matplotlib 不方便 "reset to original view"

Python 对列的操作和 "copy of a slice from a DataFrame"警告

python-3.x - 在 Bokeh python 中创建雷达图的步骤是什么?

python - 在 matplotlib 中使用更多颜色进行绘图

python - Scrapy 和图像抓取的问题

python - 我在这个乒乓球游戏中画了一个边界,但 Racket 可以越过它。我该如何阻止它?

python - 使用 df.apply() 将带参数的函数应用于每一行

python - Pandas :遍历一行并将值添加到空列