如何用Python/Matplotlib/Seaborn按天分组绘制小时平均volume图表?
实现类似Excel透视表的按天按小时Volume平均值图表(Python/Seaborn/Matplotlib)
问题背景
现有mean_by_day_hour DataFrame结构如下:
day hour volume 0 Mon 0 301.25 1 Mon 0 260.50 2 Mon 0 329.00 3 Mon 1 149.00 4 Mon 1 161.00 .. ... ... ... 499 Sun 22 722.00 500 Sun 22 857.00 501 Sun 23 600.25 502 Sun 23 457.00 503 Sun 23 510.00
需要绘制按天、按小时统计的volume平均值图表,效果和Excel透视表一致。曾尝试添加time_interval列并手动调整xticks,但存在两个问题:
- 无法实现Excel式的双X轴(上层为day,下层为hour)效果
- 手动调整xticks不够自动化,DataFrame结构变化时需重新修改代码
倾向使用Seaborn的hue参数,也接受Matplotlib实现,当前代码如下:
#Loc is to order by day of week, not relevant here mean_by_day_hour = pd.pivot_table(df, values=["volume"], index=["day", "hour", "period"], aggfunc=np.mean).loc[get_day_names(df, "day_number")] print(mean_by_day_hour) mean_by_day_hour.reset_index(inplace=True) #Workaround by creating time_interval column mean_by_day_hour["time_interval"] = mean_by_day_hour["day"].astype(str) + "-" + mean_by_day_hour["hour"].astype(str) + "h" plt.figure(figsize=(10, 5)) sns.lineplot(x="time_interval", y='volume', hue="period", data=mean_by_day_hour, errorbar=None) #df_list has the different dataframes: one per period plt.xticks(mean_by_day_hour["time_interval"].iloc[0:int(len(mean_by_day_hour)/len(df_list)):12].index, mean_by_day_hour["time_interval"].iloc[0:len(mean_by_day_hour):12*len(df_list)].values) plt.show()
解决方案
步骤1:预处理数据(规范顺序+聚合平均值)
首先确保星期按正确顺序排序,同时完成day-hour-period组合的平均值计算:
import pandas as pd import seaborn as sns import matplotlib.pyplot as plt import numpy as np # 定义固定的星期顺序,避免字符串排序混乱 day_order = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] # 聚合计算各组合的平均值(如果原数据未做聚合) agg_df = mean_by_day_hour.groupby(['day', 'hour', 'period'])['volume'].mean().reset_index() # 将day转为分类类型,强制按指定顺序排序 agg_df['day'] = pd.Categorical(agg_df['day'], categories=day_order, ordered=True) agg_df = agg_df.sort_values(['day', 'hour'])
步骤2:绘制双X轴图表(完全模拟Excel透视表效果)
通过Matplotlib双轴功能结合Seaborn,实现上层显示星期、下层显示小时的效果,全程自动适配数据:
plt.figure(figsize=(16, 6)) # 用数据索引作为x轴基准,方便后续定位刻度 sns.lineplot(x=agg_df.index, y='volume', hue='period', data=agg_df, marker='o', errorbar=None) # 处理下层X轴(小时) # 提取每个hour对应的第一个数据索引位置 hour_positions = agg_df.groupby(['day', 'hour']).first().index.get_level_values(0).index hour_labels = agg_df.groupby(['day', 'hour']).first()['hour'].values plt.xticks(ticks=hour_positions, labels=hour_labels, rotation=0) # 添加上层X轴(星期) ax1 = plt.gca() ax2 = ax1.twiny() # 计算每个星期对应的中间索引位置作为刻度点 day_positions = agg_df.groupby('day').apply(lambda x: x.index.mean()).values day_labels = agg_df['day'].unique() ax2.set_xticks(ticks=day_positions) ax2.set_xticklabels(day_labels, fontweight='bold') # 调整上层轴的位置,使其显示在下层轴上方 ax2.spines['top'].set_position(('outward', 30)) # 图表美化 ax1.set_xlabel('Hour', fontsize=12) ax2.set_xlabel('Day', fontsize=12) plt.ylabel('Average Volume', fontsize=12) plt.title('Average Volume by Day and Hour', fontsize=14) plt.tight_layout() plt.show()
备选方案:按天分组的子图展示
如果不需要严格的双轴,也可以用Seaborn的catplot生成按天分组的子图,每个子图展示对应天数的小时趋势:
g = sns.catplot(x='hour', y='volume', hue='period', col='day', col_order=day_order, data=agg_df, kind='line', errorbar=None, height=4, aspect=0.8) g.set_titles('{col_name}') plt.tight_layout() plt.show()
内容的提问来源于stack exchange,提问作者cotak
相关产品推荐
相关产品推荐

