如何在 python 中突出显示时间序列线图的周末
how to highlight weekends for time series line plot in python
我正在尝试对共享单车数据集进行分析。部分分析包括以日期方式显示周末的需求。
我在 pandas 中的数据框最后 5 行如下所示。
这是我的日期与总行程图的代码。
import seaborn as sns
sns.set_style("darkgrid")
plt.plot(d17_day_count)
plt.show()
。
我想在情节中突出显示周末。这样它看起来就类似于这个情节。
我正在使用 Python 与 matplotlib 和 seaborn 库。
您可以使用 axvspan
轻松突出显示区域,要获得要突出显示的区域,您可以 运行 通过数据框的索引并搜索周末。我还添加了一个在工作周内突出显示 'occupied hours' 的示例(希望这不会混淆)。
我已经为一个基于天的数据框创建了虚拟数据,另一个为几个小时创建了虚拟数据。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# dummy data (Days)
dates_d = pd.date_range('2017-01-01', '2017-02-01', freq='D')
df = pd.DataFrame(np.random.randint(1, 20, (dates_d.shape[0], 1)))
df.index = dates_d
# dummy data (Hours)
dates_h = pd.date_range('2017-01-01', '2017-02-01', freq='H')
df_h = pd.DataFrame(np.random.randint(1, 20, (dates_h.shape[0], 1)))
df_h.index = dates_h
#two graphs
fig, axes = plt.subplots(nrows=2, ncols=1, sharex=True)
#plot lines
dfs = [df, df_h]
for i, df in enumerate(dfs):
for v in df.columns.tolist():
axes[i].plot(df[v], label=v, color='black', alpha=.5)
def find_weekend_indices(datetime_array):
indices = []
for i in range(len(datetime_array)):
if datetime_array[i].weekday() >= 5:
indices.append(i)
return indices
def find_occupied_hours(datetime_array):
indices = []
for i in range(len(datetime_array)):
if datetime_array[i].weekday() < 5:
if datetime_array[i].hour >= 7 and datetime_array[i].hour <= 19:
indices.append(i)
return indices
def highlight_datetimes(indices, ax):
i = 0
while i < len(indices)-1:
ax.axvspan(df.index[indices[i]], df.index[indices[i] + 1], facecolor='green', edgecolor='none', alpha=.5)
i += 1
#find to be highlighted areas, see functions
weekend_indices = find_weekend_indices(df.index)
occupied_indices = find_occupied_hours(df_h.index)
#highlight areas
highlight_datetimes(weekend_indices, axes[0])
highlight_datetimes(occupied_indices, axes[1])
#formatting..
axes[0].xaxis.grid(b=True, which='major', color='black', linestyle='--', alpha=1) #add xaxis gridlines
axes[1].xaxis.grid(b=True, which='major', color='black', linestyle='--', alpha=1) #add xaxis gridlines
axes[0].set_xlim(min(dates_d), max(dates_d))
axes[0].set_title('Weekend days', fontsize=10)
axes[1].set_title('Occupied hours', fontsize=10)
plt.show()
我尝试使用已接受答案中的代码,但索引的使用方式,时间序列中的最后一个周末并没有完全突出显示,尽管当前显示的图像表明了这一点(这主要是在频率上很明显6 小时或以上)。此外,如果数据频率高于每天,它也不起作用。这就是为什么我在这里分享一个使用 x 轴单位的解决方案,以便可以突出显示周末(或任何其他重复出现的时间段)而不会出现与索引相关的任何问题。
此解决方案仅需 6 行代码,并且适用于任何频率。在下面的示例中,它突出显示了整个周末,这使得它比接受的答案更有效小频率(例如30分钟)会产生很多多边形来覆盖整个周末。
x 轴范围用于计算以天为单位的绘图覆盖的时间范围,这是 matplotlib dates. Then a weekends
mask is computed and passed to the where
argument of the fill_between
绘图函数使用的单位。掩码被处理为右排他的,因此在这种情况下,它们必须包含星期一,以便绘制到星期一 00:00 的高光。因为当周末出现在限制附近时,绘制这些突出显示可能会改变 x 轴限制,因此在绘制后 x 轴限制将设置回原始值。
请注意,与 axvspan
相反,fill_between
函数需要 y1
和 y2
参数。出于某种原因,使用默认的 y 轴限制会在图框与周末亮点的顶部和底部之间留下一个小间隙。此问题在创建绘图后由 运行 ax.set_ylim(*ax.get_ylim())
解决。
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
# Create sample dataset
rng = np.random.default_rng(seed=1234) # random number generator
dti = pd.date_range('2017-01-01', '2017-05-15', freq='D')
counts = 5000 + np.cumsum(rng.integers(-1000, 1000, size=dti.size))
df = pd.DataFrame(dict(Counts=counts), index=dti)
# Draw pandas plot: x_compat=True converts the pandas x-axis units to matplotlib
# date units (not strictly necessary when using a daily frequency like here)
ax = df.plot(x_compat=True, figsize=(10, 5), legend=None, ylabel='Counts')
ax.set_ylim(*ax.get_ylim()) # reset y limits to display highlights without gaps
# Highlight weekends based on the x-axis units
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 using matplotlib date tick locators and formatters
ax.xaxis.set_major_locator(mdates.MonthLocator())
ax.xaxis.set_minor_locator(mdates.MonthLocator(bymonthday=np.arange(5, 31, step=7)))
ax.xaxis.set_major_formatter(mdates.DateFormatter('\n%b'))
ax.xaxis.set_minor_formatter(mdates.DateFormatter('%d'))
# Additional formatting
ax.figure.autofmt_xdate(rotation=0, ha='center')
title = 'Daily count of trips with weekends highlighted from SAT 00:00 to MON 00:00'
ax.set_title(title, pad=20, fontsize=14);
如您所见,无论数据从何处开始和结束,周末始终突出显示。
您可以在我发布的答案中找到此解决方案的更多示例 and 。
在这方面我还有另一个建议,它的灵感来自于其他贡献者以前的帖子。代码如下:
import datetime
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
rng = np.random.default_rng(seed=42) # random number generator
dti = pd.date_range('2021-08-01', '2021-08-31', freq='D')
counts = 5000 + np.cumsum(rng.integers(-1000, 1000, size=dti.size))
df = pd.DataFrame(dict(Counts=counts), index=dti)
weekends = [d for d in df.index if d.isoweekday() in [6,7]]
weekend_list = []
for weekendday in weekends:
d1 = weekendday
d2 = weekendday + datetime.timedelta(days=1)
weekend_list.append((d1, d2))
weekend_df = pd.DataFrame(weekend_list)
sns.set()
plt.figure(figsize=(15, 10), dpi=100)
df.plot()
plt.legend(bbox_to_anchor=(1.02, 0), loc="lower left", borderaxespad=0)
plt.ylabel("Counts")
plt.xlabel("Date of visit")
plt.xticks(rotation = 0)
plt.title("Daily counts of shop visits with weekends highlighted in green")
ax = plt.gca()
for d in weekend_df.index:
print(weekend_df[0][d], weekend_df[1][d])
ax.axvspan(weekend_df[0][d], weekend_df[1][d], facecolor="g", edgecolor="none", alpha=0.5)
ax.relim()
ax.autoscale_view()
plt.savefig("junk.png", dpi=100, bbox_inches='tight', pad_inches=0.2)
结果将类似于下图:
我正在尝试对共享单车数据集进行分析。部分分析包括以日期方式显示周末的需求。 我在 pandas 中的数据框最后 5 行如下所示。
这是我的日期与总行程图的代码。
import seaborn as sns
sns.set_style("darkgrid")
plt.plot(d17_day_count)
plt.show()
。
我想在情节中突出显示周末。这样它看起来就类似于这个情节。
我正在使用 Python 与 matplotlib 和 seaborn 库。
您可以使用 axvspan
轻松突出显示区域,要获得要突出显示的区域,您可以 运行 通过数据框的索引并搜索周末。我还添加了一个在工作周内突出显示 'occupied hours' 的示例(希望这不会混淆)。
我已经为一个基于天的数据框创建了虚拟数据,另一个为几个小时创建了虚拟数据。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# dummy data (Days)
dates_d = pd.date_range('2017-01-01', '2017-02-01', freq='D')
df = pd.DataFrame(np.random.randint(1, 20, (dates_d.shape[0], 1)))
df.index = dates_d
# dummy data (Hours)
dates_h = pd.date_range('2017-01-01', '2017-02-01', freq='H')
df_h = pd.DataFrame(np.random.randint(1, 20, (dates_h.shape[0], 1)))
df_h.index = dates_h
#two graphs
fig, axes = plt.subplots(nrows=2, ncols=1, sharex=True)
#plot lines
dfs = [df, df_h]
for i, df in enumerate(dfs):
for v in df.columns.tolist():
axes[i].plot(df[v], label=v, color='black', alpha=.5)
def find_weekend_indices(datetime_array):
indices = []
for i in range(len(datetime_array)):
if datetime_array[i].weekday() >= 5:
indices.append(i)
return indices
def find_occupied_hours(datetime_array):
indices = []
for i in range(len(datetime_array)):
if datetime_array[i].weekday() < 5:
if datetime_array[i].hour >= 7 and datetime_array[i].hour <= 19:
indices.append(i)
return indices
def highlight_datetimes(indices, ax):
i = 0
while i < len(indices)-1:
ax.axvspan(df.index[indices[i]], df.index[indices[i] + 1], facecolor='green', edgecolor='none', alpha=.5)
i += 1
#find to be highlighted areas, see functions
weekend_indices = find_weekend_indices(df.index)
occupied_indices = find_occupied_hours(df_h.index)
#highlight areas
highlight_datetimes(weekend_indices, axes[0])
highlight_datetimes(occupied_indices, axes[1])
#formatting..
axes[0].xaxis.grid(b=True, which='major', color='black', linestyle='--', alpha=1) #add xaxis gridlines
axes[1].xaxis.grid(b=True, which='major', color='black', linestyle='--', alpha=1) #add xaxis gridlines
axes[0].set_xlim(min(dates_d), max(dates_d))
axes[0].set_title('Weekend days', fontsize=10)
axes[1].set_title('Occupied hours', fontsize=10)
plt.show()
我尝试使用已接受答案中的代码,但索引的使用方式,时间序列中的最后一个周末并没有完全突出显示,尽管当前显示的图像表明了这一点(这主要是在频率上很明显6 小时或以上)。此外,如果数据频率高于每天,它也不起作用。这就是为什么我在这里分享一个使用 x 轴单位的解决方案,以便可以突出显示周末(或任何其他重复出现的时间段)而不会出现与索引相关的任何问题。
此解决方案仅需 6 行代码,并且适用于任何频率。在下面的示例中,它突出显示了整个周末,这使得它比接受的答案更有效小频率(例如30分钟)会产生很多多边形来覆盖整个周末。
x 轴范围用于计算以天为单位的绘图覆盖的时间范围,这是 matplotlib dates. Then a weekends
mask is computed and passed to the where
argument of the fill_between
绘图函数使用的单位。掩码被处理为右排他的,因此在这种情况下,它们必须包含星期一,以便绘制到星期一 00:00 的高光。因为当周末出现在限制附近时,绘制这些突出显示可能会改变 x 轴限制,因此在绘制后 x 轴限制将设置回原始值。
请注意,与 axvspan
相反,fill_between
函数需要 y1
和 y2
参数。出于某种原因,使用默认的 y 轴限制会在图框与周末亮点的顶部和底部之间留下一个小间隙。此问题在创建绘图后由 运行 ax.set_ylim(*ax.get_ylim())
解决。
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
# Create sample dataset
rng = np.random.default_rng(seed=1234) # random number generator
dti = pd.date_range('2017-01-01', '2017-05-15', freq='D')
counts = 5000 + np.cumsum(rng.integers(-1000, 1000, size=dti.size))
df = pd.DataFrame(dict(Counts=counts), index=dti)
# Draw pandas plot: x_compat=True converts the pandas x-axis units to matplotlib
# date units (not strictly necessary when using a daily frequency like here)
ax = df.plot(x_compat=True, figsize=(10, 5), legend=None, ylabel='Counts')
ax.set_ylim(*ax.get_ylim()) # reset y limits to display highlights without gaps
# Highlight weekends based on the x-axis units
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 using matplotlib date tick locators and formatters
ax.xaxis.set_major_locator(mdates.MonthLocator())
ax.xaxis.set_minor_locator(mdates.MonthLocator(bymonthday=np.arange(5, 31, step=7)))
ax.xaxis.set_major_formatter(mdates.DateFormatter('\n%b'))
ax.xaxis.set_minor_formatter(mdates.DateFormatter('%d'))
# Additional formatting
ax.figure.autofmt_xdate(rotation=0, ha='center')
title = 'Daily count of trips with weekends highlighted from SAT 00:00 to MON 00:00'
ax.set_title(title, pad=20, fontsize=14);
如您所见,无论数据从何处开始和结束,周末始终突出显示。
您可以在我发布的答案中找到此解决方案的更多示例
在这方面我还有另一个建议,它的灵感来自于其他贡献者以前的帖子。代码如下:
import datetime
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
rng = np.random.default_rng(seed=42) # random number generator
dti = pd.date_range('2021-08-01', '2021-08-31', freq='D')
counts = 5000 + np.cumsum(rng.integers(-1000, 1000, size=dti.size))
df = pd.DataFrame(dict(Counts=counts), index=dti)
weekends = [d for d in df.index if d.isoweekday() in [6,7]]
weekend_list = []
for weekendday in weekends:
d1 = weekendday
d2 = weekendday + datetime.timedelta(days=1)
weekend_list.append((d1, d2))
weekend_df = pd.DataFrame(weekend_list)
sns.set()
plt.figure(figsize=(15, 10), dpi=100)
df.plot()
plt.legend(bbox_to_anchor=(1.02, 0), loc="lower left", borderaxespad=0)
plt.ylabel("Counts")
plt.xlabel("Date of visit")
plt.xticks(rotation = 0)
plt.title("Daily counts of shop visits with weekends highlighted in green")
ax = plt.gca()
for d in weekend_df.index:
print(weekend_df[0][d], weekend_df[1][d])
ax.axvspan(weekend_df[0][d], weekend_df[1][d], facecolor="g", edgecolor="none", alpha=0.5)
ax.relim()
ax.autoscale_view()
plt.savefig("junk.png", dpi=100, bbox_inches='tight', pad_inches=0.2)
结果将类似于下图: