我想在一个子图中使用pandas时间序列创建一个图,在另一个子图中使用一个矩形。
如果我不包含子图,我可以很容易地实现这一点:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.patches as mpatches
N = 100
np.random.seed(N)
dates = pd.date_range(start='2018-01-01', periods=N, freq='D')
one_third_delta = (dates[-1] - dates[0])/3
one_third_stamp = dates[0] + one_third_delta
ts = pd.Series(index=dates, data=np.random.randn(N))
def add_rectangle(ax, x, y, width, height, **kwargs):
ax.add_patch(mpatches.Rectangle(
(x, y),
width,
height,
**kwargs
))
args = [one_third_stamp, -1, one_third_delta, 2]
kwargs = {
'facecolor': 'orange',
'edgecolor': 'None',
'alpha': 0.5,
}
# Plot 1: 1 subplot with ts plotted first (Working)
fig, ax = plt.subplots()
ts.plot(ax=ax)
add_rectangle(ax, *args, **kwargs)
plt.savefig('plot1.png')
plt.close(fig)
然而,当我尝试首先添加矩形时,事情已经开始变得奇怪了:
# Plot 2: 1 subplot with ts plotted second (Not Working)
fig, ax = plt.subplots()
add_rectangle(ax, *args, **kwargs)
ts.plot(ax=ax)
plt.savefig('plot2.png')
plt.close(fig)
如果我尝试拆分这两个图,那么这两种方法都不起作用:
# Plot 3: 2 subplots with ts plotted first (Not Working)
fig, axes = plt.subplots(2, sharex=True)
ts.plot(ax=axes[1])
add_rectangle(axes[0], *args, **kwargs)
plt.savefig('plot3.png')
plt.close(fig)
# Plot 4: 2 subplots with ts plotted second (Not Working)
fig, axes = plt.subplots(2, sharex=True)
add_rectangle(axes[0], *args, **kwargs)
ts.plot(ax=axes[1])
plt.savefig('plot4.png')
plt.close(fig)
我找到了两个解决办法。
第一个涉及使用matplotlib.dates.date2num
:
# Plot 5: 2 subplots with date2num (Working)
two_thirds_stamp = one_third_stamp + one_third_delta
args_date2num = [
mdates.date2num(one_third_stamp),
-1,
mdates.date2num(two_thirds_stamp) - mdates.date2num(one_third_stamp),
2,
]
df = ts.to_frame().reset_index()
df.columns = ['date', 'value']
df['num'] = df.date.apply(mdates.date2num)
fig, axes = plt.subplots(2, sharex=True)
add_rectangle(axes[0], *args_date2num, **kwargs)
axes[1].plot_date(df.num, df.value, ls='-', marker=None)
axes[0].set_ylim(axes[1].get_ylim())
plt.savefig('plot5.png')
plt.close(fig)
这不是很好,原因有两个:
date2num
与pandas内部将日期时间表示为浮点数的方式不兼容。
因此,如果我完全使用date2num
,则所有其他日期时间也必须转换。其他工作涉及虚拟情节:
# Plot 6: 2 subplots with alpha=0 dummy (Working)
fig, axes = plt.subplots(2, sharex=True)
dummy_ts = ts[::(len(ts)-1)] + 10 # make it out of sight
dummy_ts.plot(ax=axes[0], alpha=0) # and invisible for good measure
add_rectangle(axes[0], *args, **kwargs)
ts.plot(ax=axes[1])
axes[0].set_ylim(axes[1].get_ylim())
plt.savefig('plot6.png')
plt.close(fig)
我的问题(最后)是为什么这是必要的? 在单个子图上与多个子图上执行此操作之间有何变化? 是否有更好,更规范的方式?
Python版本:
Python 3.6.3 (v3.6.3:2c5fed86e0)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Pip冻结:
cycler==0.10.0
kiwisolver==1.0.1
matplotlib==2.2.0
numpy==1.14.2
pandas==0.22.0
pyparsing==2.2.0
python-dateutil==2.7.0
pytz==2018.3
six==1.11.0
答案 0 :(得分:1)
我认为你自己找到了原因:matplotlib轴的pandas datetime表示(可能)与matplotlib日期单位完全不同(情况并非总是这样,取决于数据的跨度)。
由于我不知道将矩形坐标转换为pandas单位的任何方法,唯一的选择是以matplotlib单位绘制pandas图。
但是我们从一开始就开始吧。案例1和2对我来说很好。
对于第三种情况,矩形被添加到其他轴,这些轴具有不同的比例。通过打印转换可以看到这一点。
def add_rectangle(ax, x, y, width, height, **kwargs):
rect = mpatches.Rectangle( (x, y), width, height, **kwargs )
ax.add_patch(rect)
return rect
# Case 1 - working
fig, ax = plt.subplots()
ts.plot(ax=ax)
r = add_rectangle(ax, *args, **kwargs)
print r.get_transform()
# This prints
# BboxTransformTo(
# Bbox(x0=17565.0, y0=-1.0, x1=17598.0, y1=1.0)),
# Case 3 - non-working
fig, axes = plt.subplots(2, sharex=True)
ts.plot(ax=axes[1], x_compat=True)
r = add_rectangle(axes[0], *args, **kwargs)
print r.get_transform()
# BboxTransformTo(
# Bbox(x0=736728.0, y0=-1.0, x1=736761.0, y1=1.0)),
在第二种情况下,单位是matplotlib日期单位,因为pandas没有改变它没有绘制任何东西的轴的变换。
最简单的选择可能是告诉大熊猫不要改变比例。这将使用
完成x_compat=True
这与在matplotlib单元中绘制所有内容的效果基本相同。
# Plot 3: 2 subplots with ts plotted first
fig, axes = plt.subplots(2, sharex=True)
ts.plot(ax=axes[1], x_compat=True)
r = add_rectangle(axes[0], *args, **kwargs)
# Plot 4: 2 subplots with ts plotted second
fig, axes = plt.subplots(2, sharex=True)
add_rectangle(axes[0], *args, **kwargs)
ts.plot(ax=axes[1], x_compat=True)
所以确实好的熊猫格式化了。但您可以使用matplotlib.dates
格式化程序复制它。例如。在this post。提供了添加日期的简单解决方案。在这里,您可能更愿意使用FuncFormatter
,如下所示:
fig, axes = plt.subplots(2, sharex=True)
ts.plot(ax=axes[1], x_compat=True)
r = add_rectangle(axes[0], *args, **kwargs)
import matplotlib.dates as mdates
import matplotlib.ticker as mticker
def f(val, _):
d = mdates.num2date(val)
if d.month == 1:
return d.strftime("%b\n%Y")
else:
return d.strftime("%b")
axes[1].xaxis.set_major_locator(mdates.MonthLocator())
axes[1].xaxis.set_minor_locator(mdates.WeekdayLocator())
axes[1].xaxis.set_major_formatter(mticker.FuncFormatter(f))
fig.autofmt_xdate(rotation=0,ha="center")
制造