【问题标题】:How to change pyplot background colour in region of interest?如何更改感兴趣区域的 pyplot 背景颜色?
【发布时间】:2020-09-16 07:41:28
【问题描述】:

我有一个带有日期时间索引的数据框:

            A  B
date            
2020-05-04  0  0
2020-05-05  5  0
2020-05-07  2  0
2020-05-09  2  0
2020-05-18 -5  0
2020-05-19 -1  0
2020-05-20  0  0
2020-05-21  1  0
2020-05-22  0  0
2020-05-23  3  0
2020-05-24  1  1
2020-05-25  0  1
2020-05-26  4  1
2020-05-27  3  1

我想制作一个线图来随着时间的推移跟踪 A,并在 B 的值为 1 时将绘图的背景涂成红色。我已经实现了以下代码来制作图表:

from matplotlib import dates as mdates
from matplotlib.colors import ListedColormap
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

cmap = ListedColormap(['white','red'])

ax.plot(data['A'])
ax.set_xlabel('')
plt.xticks(rotation = 30)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax.pcolorfast(ax.get_xlim(), ax.get_ylim(),
              data['B'].values[np.newaxis],
              cmap = cmap, alpha = 0.4)
plt.axhline(y = 0, color = 'black')
plt.tight_layout()

这给了我这个图表:

但红色区域错误地从 2020-05-21 而不是 2020-05-24 开始,并且它没有在数据框中的结束日期结束。如何更改我的代码来解决这个问题?

【问题讨论】:

  • 我很难重现该问题。 cmap 是什么样子的? mdates 是 matplotlib.dates 吗?
  • mdates 是 matplotlib.dates 而 cmap 是 ListedColormap

标签: python pandas numpy matplotlib


【解决方案1】:

如果您将ax.pcolorfast(ax.get_xlim(), ... 更改为ax.pcolor(data.index, ...,您将得到您想要的。当前代码的问题在于,通过使用ax.get_xlim(),它会创建一个统一的矩形网格,而您的索引不是统一的(缺少日期),因此彩色网格并不像预期的那样。整件事是:

from matplotlib import dates as mdates
from matplotlib.colors import ListedColormap
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

cmap = ListedColormap(['white','red'])

fig = plt.figure()
ax = fig.add_subplot()

ax.plot(data['A'])
ax.set_xlabel('')

plt.xticks(rotation = 30)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
#here are the two changes use pcolor
ax.pcolor(data.index, #use data.index to create the proper grid
          ax.get_ylim(),
          data['B'].values[np.newaxis], 
          cmap = cmap, alpha = 0.4, 
          linewidth=0, antialiased=True)
plt.axhline(y = 0, color = 'black')
plt.tight_layout()

然后你得到

【讨论】:

  • 我需要一个统一的红色区域。为什么您的代码会在红色区域出现可见的垂直线?
  • @kynnem 尝试在pcolormesh函数中添加linewidth=0, antialiased=True这两个参数?
  • @kynnem 以及为什么,如果你不指定这些参数,它取决于使用的查看器的一些全局rcParams,所以这就是它们出现的原因
【解决方案2】:

在这种情况下,我更喜欢axvspan,有关详细信息,请参阅here

此调整将为data.B==1 所在的区域着色,包括data.B 可能不是连续块的潜在区域

使用来自 data1.csv 的修改后的数据框 data(添加了更多点,即 1):

date        A   B
5/4/2020    0   0
5/5/2020    5   0
5/7/2020    2   1
5/9/2020    2   1
5/18/2020   -5  0
5/19/2020   -1  0
5/20/2020   0   0
5/21/2020   1   0
5/22/2020   0   0
5/23/2020   3   0
5/24/2020   1   1
5/25/2020   0   1
5/26/2020   4   1
5/27/2020   3   1
from matplotlib import dates as mdates
import pandas as pd
import matplotlib.pyplot as plt

data = pd.read_csv('data1.csv',index_col='date')
data.index = pd.to_datetime(data.index)

fig = plt.figure()
ax = fig.add_subplot()

ax.plot(data['A'])
plt.xticks(rotation = 30)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
plt.axhline(y = 0, color = 'black')

# in this case I'm looking for a pair of ones to determine where to color
for i in range(1,len(data.B)):
    if data.B[i]==True and data.B[i-1]==True:
        plt.axvspan(data.index[i-1], data.index[i], color='r', alpha=0.4, lw=0)

plt.tight_layout()

如果data.B==1 将始终是“一个块”,您可以取消for 循环并使用类似的东西代替它:

first = min(idx for idx, val in enumerate(data.B) if val == 1) 
last = max(idx for idx, val in enumerate(data.B) if val == 1) 

plt.axvspan(data.index[first], data.index[last], color='r', alpha=0.4, lw=0)

关于“为什么”您的数据不对齐,@Ben.T has this solution

更新:正如所指出的,for 循环对于大型数据集可能过于粗糙。下面使用 numpy 查找data.B 的下降沿和上升沿,然后循环这些结果:

import numpy as np
diffB = np.append([0], np.diff(data.B))
up = np.where(diffB == 1)[0]
dn = np.where(diffB == -1)[0]

if diffB[np.argmax(diffB!=0)]==-1:
    # we have a falling edge before rising edge, must have started 'up'
    up = np.append([0], up)

if diffB[len(diffB) - np.argmax(diffB[::-1]) - 1]==1:
    # we have a rising edge that never fell, force it 'dn'
    dn = np.append(dn, [len(data.B)-1])

for i in range(len(up)):
    plt.axvspan(data.index[up[i]], data.index[dn[i]], color='r', alpha=0.4, lw=0)

【讨论】:

  • 我很喜欢你的想法。我唯一担心的是如果数据帧很大,那么在 B 列中循环和添加与 1 一样多的 axvspan 可能有点长或消耗内存。你有这方面的知识吗?也,结果将与pcolor 略有不同,因为此版本颜色间隔(1 秒之间)而不是从 1 到下一个日期,即使它是 0(如 pcolor),但这取决于 OP 的需要: )
  • 是的,我的 for 循环对于大型数据集来说有点粗糙。我将使用 numpy 发布更新以查找要着色的区域。 wrt 在 1 之间着色,这是我对配色方案如何与日期一起使用的最佳猜测......如果只有一个日期有 1 应该着色?
  • 使用 pcolor,如果您只有一个 1,那么从 1 的日期到数据中的下一个日期,它是彩色的,这就是该方法的工作原理。我猜你的代码只会将条件 if data.B[i]==True and data.B[i-1]==True: 更改为 if data.B[i-1]==True: 以获得类似的行为:) 但正如我所说,这取决于 OP 的需要
  • 我认为pcolorfast 是一个更好的解决方案:)
猜你喜欢
  • 2011-08-10
  • 1970-01-01
  • 1970-01-01
  • 2012-02-22
  • 2013-03-03
  • 1970-01-01
  • 2013-07-11
  • 2017-11-19
相关资源
最近更新 更多