带有矩形的熊猫时间序列子图

时间:2018-03-15 23:36:37

标签: pandas matplotlib

我想在一个子图中使用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)

情节1

然而,当我尝试首先添加矩形时,事情已经开始变得奇怪了:

# 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 2

如果我尝试拆分这两个图,那么这两种方法都不起作用:

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

情节3

Plot 4

我找到了两个解决办法。

第一个涉及使用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)

Plot 5

这不是很好,原因有两个:

  1. 我失去了pandas使用的漂亮的ticklabel格式。
  2. 据我所知,date2num与pandas内部将日期时间表示为浮点数的方式不兼容。 因此,如果我完全使用date2num,则所有其他日期时间也必须转换。
  3. 其他工作涉及虚拟情节:

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

    情节6

    我的问题(最后)是为什么这是必要的? 在单个子图上与多个子图上执行此操作之间有何变化? 是否有更好,更规范的方式?

    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
    

1 个答案:

答案 0 :(得分:1)

我认为你自己找到了原因:matplotlib轴的pandas datetime表示(可能)与matplotlib日期单位完全不同(情况并非总是这样,取决于数据的跨度)。

由于我不知道将矩形坐标转换为pandas单位的任何方法,唯一的选择是以matplotlib单位绘制pandas图。

问题

但是我们从一开始就开始吧。案例1和2对我来说很好。

enter image description here

对于第三种情况,矩形被添加到其他轴,这些轴具有不同的比例。通过打印转换可以看到这一点。

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)

enter image description here

所以确实好的熊猫格式化了。但您可以使用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")
制造

enter image description here