Python 以小倍数突出显示周末

Python 以小倍数突出显示周末,python,datetime,matplotlib,highlight,Python,Datetime,Matplotlib,Highlight,如何以小倍数突出显示周末? 我读过不同的线程(例如和),但不知道如何在我的案例中实现它,因为我使用的是小倍数,每个月迭代DateTimeIndex(参见下图代码)。在这种情况下,我的数据Profiles是一个间隔15分钟的2年时间序列(即70080个数据点) 但是,周末发生在月底,因此产生错误;在这种情况下:索引器错误:索引2972超出大小为2972的轴0的界限 我的尝试:[编辑-建议人] [10]中的 课堂灯光周末: ''要突出显示周末的类对象'' 定义初始(自我,周期): self.ran

如何以小倍数突出显示周末?

我读过不同的线程(例如和),但不知道如何在我的案例中实现它,因为我使用的是小倍数,每个月迭代
DateTimeIndex
(参见下图代码)。在这种情况下,我的数据
Profiles
是一个间隔15分钟的2年时间序列(即70080个数据点)

但是,周末发生在月底,因此产生错误;在这种情况下:
索引器错误:索引2972超出大小为2972的轴0的界限

我的尝试:[编辑-建议人]

[10]中的

课堂灯光周末:
''要突出显示周末的类对象''
定义初始(自我,周期):
self.ranges=period.index.dayofweek>=5
self.res=[x代表x,(i,j)在枚举中(zip([2]+list(self.ranges),list(self.ranges)+[2]),如果i!=j]
如果self.res[0]==0且self.ranges[0]==False:
del self.res[0]
如果self.res[-1]==len(self.ranges)和self.ranges[-1]==False:
del self.res[-1]
月数=配置文件。loc['2018'].分组比(λx:x.month)
图,axs=plt.子批次(4,3,figsize=(16,12),sharey=True)
axs=axs.flatten()
对于i,j,以月为单位:
axs[i-1]。绘图(j.索引,j)
如果我

问题
如何解决月末的周末问题?

TL;DR跳到方法2的解决方案以查看最佳解决方案,或跳到具有单线图的解决方案的最后一个示例。在所有三个示例中,周末仅使用4-6行代码突出显示,其余用于格式化和再现性



方法和工具 我知道有两种方法可以在时间序列图上突出显示周末,这两种方法可以通过在子图数组上循环应用于单个图和小倍数图。此答案提供了突出显示周末的解决方案,但它们可以轻松调整,以适应任何周期性的工作


方法1:基于数据帧索引突出显示

此方法遵循链接线程中问题和答案中的代码逻辑。不幸的是,当一个周末发生在月末时,出现了一个问题,绘制整个周末所需的索引编号超出了索引范围,从而产生了一个错误。通过计算两个时间戳之间的时间差,并将其添加到DatetimeIndex的每个时间戳(当循环它们以突出显示周末时),可以在下面进一步显示的解决方案中解决此问题

但仍存在两个问题:i)此方法不适用于频率超过一天的时间序列;ii)基于频率小于每小时(如15分钟)的时间序列将需要绘制许多多边形,这会影响性能。出于这些原因,本文介绍此方法是为了编制文档,我建议改用方法2


方法2:基于x轴单位高亮显示

import numpy as np                   # v 1.19.2
import pandas as pd                  # v 1.1.3
import matplotlib.pyplot as plt      # v 3.3.2
import matplotlib.dates as mdates  # used only for method 2

# Create sample dataset
rng = np.random.default_rng(seed=1) # random number generator
dti = pd.date_range('2018-01-01 00:00', '2018-12-31 23:59', freq='6H')
consumption = rng.integers(1000, 2000, size=dti.size)
df = pd.DataFrame(dict(consumption=consumption), index=dti)
该方法使用x轴单位,即自时间原点(1970-01-01)起的天数,独立于正在绘制的时间序列数据识别周末,这使其比方法1灵活得多。仅在每个周末绘制突出显示,这比下面给出的示例的方法1快两倍(根据Jupyter笔记本中的
%%timeit
测试)。这是我推荐使用的方法


matplotlib中可用于实现这两种方法的工具

import numpy as np                   # v 1.19.2
import pandas as pd                  # v 1.1.3
import matplotlib.pyplot as plt      # v 3.3.2
import matplotlib.dates as mdates  # used only for method 2

# Create sample dataset
rng = np.random.default_rng(seed=1) # random number generator
dti = pd.date_range('2018-01-01 00:00', '2018-12-31 23:59', freq='6H')
consumption = rng.integers(1000, 2000, size=dti.size)
df = pd.DataFrame(dict(consumption=consumption), index=dti)
axvspan
,(用于方法1的解决方案)

断条

之间填充(用于方法2的解决方案)

BrokenBarHCollection.span\u其中

在我看来,
fill\u介于
BrokenBarHCollection.span\u之间,其中
基本相同。两者都提供了方便的
where
参数,该参数用于下文进一步介绍的方法2的解决方案中



解决 这是一个可复制的样本数据集,用于说明这两种方法,使用频率为6小时。请注意,数据框仅包含一年的数据,这使得只需使用
df[df.index.month==month]
绘制每个子批次即可选择月度数据。如果您处理的是多年DatetimeIndex,则需要对此进行调整

导入用于所有3个示例的包,并为前2个示例创建数据集

import numpy as np                   # v 1.19.2
import pandas as pd                  # v 1.1.3
import matplotlib.pyplot as plt      # v 3.3.2
import matplotlib.dates as mdates  # used only for method 2

# Create sample dataset
rng = np.random.default_rng(seed=1) # random number generator
dti = pd.date_range('2018-01-01 00:00', '2018-12-31 23:59', freq='6H')
consumption = rng.integers(1000, 2000, size=dti.size)
df = pd.DataFrame(dict(consumption=consumption), index=dti)

方法1的解决方案:基于数据帧索引突出显示

在该解决方案中,使用和每月数据帧的DatetimeIndex
df_month
突出显示周末。周末时间戳是通过
df_month.index[df_month.weekday>=5]选择到_series()
的,通过从DatetimeIndex的频率计算
timedelta
并将其添加到每个时间戳,可以解决超出索引范围的问题

当然,
axvspan
也可以以比这里显示的更智能的方式使用,这样每个周末的亮点都可以一次性绘制,但我相信这会导致比方法2的解决方案更灵活的解决方案和更多的代码

如您所见,周末突出显示了数据的结尾,如3月份所示。如果使用DatetimeIndex设置x轴限制,这当然不明显



解决方案
# Draw and format subplots by looping through months and flattened array of axes
fig, axs = plt.subplots(4, 3, figsize=(10, 9), sharey=True)
for month, ax in zip(df.index.month.unique(), axs.flat):
    # Select monthly data and plot it
    df_month = df[df.index.month == month]
    ax.plot(df_month.index, df_month['consumption'])
    ax.set_ylim(0, 2500) # set limit like plot shown in question, or use next line
#     ax.set_ylim(*ax.get_ylim())
    
    # Highlight weekends based on the x-axis units, regardless of the DatetimeIndex
    xmin, xmax = ax.get_xlim()
    days = np.arange(np.floor(xmin), np.ceil(xmax)+2)
    weekends = [(dt.weekday()>=5)|(dt.weekday()==0) for dt in mdates.num2date(days)]
    ax.fill_between(days, *ax.get_ylim(), where=weekends, facecolor='k', alpha=.1)
    ax.set_xlim(xmin, xmax) # set limits back to default values
     
    # Create appropriate ticks with matplotlib date tick locator and formatter
    tick_loc = mdates.MonthLocator(bymonthday=np.arange(1, 31, step=5))
    ax.xaxis.set_major_locator(tick_loc)
    tick_fmt = mdates.DateFormatter('%d')
    ax.xaxis.set_major_formatter(tick_fmt)
    
    # Add x labels for months
    ax.set_xlabel(df_month.index[0].month_name().upper(), labelpad=5)
    ax.xaxis.set_label_position('top')

# Add title and edit spaces between subplots
year = df.index[0].year
freq = df_month.index.freqstr
title = f'{year} consumption displayed for each month with a {freq} frequency'
fig.suptitle(title.upper(), y=0.95, fontsize=12)
fig.subplots_adjust(wspace=0.1, hspace=0.5)
fig.text(0.5, 0.99, 'Weekends are highlighted by using the x-axis units',
         ha='center', fontsize=14, weight='semibold');
# Create sample dataset with a month start frequency
rng = np.random.default_rng(seed=1) # random number generator
dti = pd.date_range('2018-01-01 00:00', '2018-06-30 23:59', freq='MS')
consumption = rng.integers(1000, 2000, size=dti.size)
df = pd.DataFrame(dict(consumption=consumption), index=dti)

# Draw pandas plot: x_compat=True converts the pandas x-axis units to matplotlib
# date units
ax = df.plot(x_compat=True, figsize=(10, 4), legend=None)
ax.set_ylim(0, 2500) # set limit similar to plot shown in question, or use next line
# ax.set_ylim(*ax.get_ylim())
    
# Highlight weekends based on the x-axis units, regardless of the DatetimeIndex
xmin, xmax = ax.get_xlim()
days = np.arange(np.floor(xmin), np.ceil(xmax)+2)
weekends = [(dt.weekday()>=5)|(dt.weekday()==0) for dt in mdates.num2date(days)]
ax.fill_between(days, *ax.get_ylim(), where=weekends, facecolor='k', alpha=.1)
ax.set_xlim(xmin, xmax) # set limits back to default values

# Additional formatting
ax.figure.autofmt_xdate(rotation=0, ha='center')
ax.set_title('2018 consumption by month'.upper(), pad=15, fontsize=12)

ax.figure.text(0.5, 1.05, 'Weekends are highlighted by using the x-axis units',
               ha='center', fontsize=14, weight='semibold');