如何用Pandas绘制含日期与车队编号的单图多级X轴柱状图?
问题描述
现有如下格式的车辆行驶数据:
import pandas as pd import numpy as np import matplotlib.pyplot as plt # 固定随机种子 np.random.seed(seed = 123) dates = pd.date_range(start = "28/05/2024", end = "28/05/2025", freq = "D") vehicles = np.random.choice(["A", "B", "C", "D", "E", "F"], size = len(dates)) kilometres = np.random.normal(loc = 170, scale = 76, size = len(dates)).round(2) df = pd.DataFrame({"date":dates, "fleet no":vehicles, "kilometres":kilometres}) # 按月份和车辆分组聚合 df = df.groupby(by = [df["date"].dt.to_period("M"), "fleet no"]).agg({"date":"count", "kilometres":"sum"})\ .rename(columns = {"date":"days"}).reset_index(level = None)
需要生成一个单图,X轴同时按月份(格式化为%Y-%b)和车辆编号分组,效果类似以下子图代码的合并版:
groups = df.groupby(by = "date") figure, axes = plt.subplots(nrows = 1, ncols = len(groups), sharey = True, figsize = (16, 5)) for ax, (year, group) in zip(axes, groups): group.set_index("fleet no").rename_axis(year)["days"].plot(kind = "bar", ax = ax)
解决方案
可以通过调整数据结构,结合matplotlib手动绘制分组条形并添加多级X轴标签来实现:
import pandas as pd import numpy as np import matplotlib.pyplot as plt # 固定随机种子 np.random.seed(seed = 123) dates = pd.date_range(start = "28/05/2024", end = "28/05/2025", freq = "D") vehicles = np.random.choice(["A", "B", "C", "D", "E", "F"], size = len(dates)) kilometres = np.random.normal(loc = 170, scale = 76, size = len(dates)).round(2) df = pd.DataFrame({"date":dates, "fleet no":vehicles, "kilometres":kilometres}) # 按月份和车辆分组聚合,保留多级索引 df_grouped = df.groupby(by = [df["date"].dt.to_period("M"), "fleet no"]).agg({"date":"count", "kilometres":"sum"})\ .rename(columns = {"date":"days"}) # 将月份格式化为指定样式 df_grouped.index = df_grouped.index.set_levels(df_grouped.index.levels[0].strftime("%Y-%b"), level=0) # 获取分组信息 months = df_grouped.index.levels[0] vehicles = df_grouped.index.levels[1] n_months = len(months) n_vehicles = len(vehicles) # 设置条形宽度和位置 bar_width = 0.8 / n_vehicles x = np.arange(n_months) # 创建画布 fig, ax = plt.subplots(figsize=(16, 5)) # 绘制每个车辆的条形 for i, vehicle in enumerate(vehicles): # 提取对应车辆的天数数据 data = df_grouped.xs(vehicle, level="fleet no")["days"] ax.bar(x + i * bar_width, data, width=bar_width, label=vehicle) # 设置X轴主刻度(月份分组) ax.set_xticks(x + bar_width * (n_vehicles - 1) / 2) ax.set_xticklabels(months) # 添加次级X轴标签(车辆编号) # 计算每个分组内的位置 sub_ticks = [] for month_x in x: for i in range(n_vehicles): sub_ticks.append(month_x + i * bar_width) ax.set_xticks(sub_ticks, minor=True) ax.set_xticklabels([v for _ in months for v in vehicles], minor=True) # 调整次级标签位置,避免重叠 ax.tick_params(axis='x', which='minor', pad=15) # 添加图例、标题和标签 ax.legend(title="车辆编号") ax.set_ylabel("天数") ax.set_title("各月份车辆行驶天数统计") # 调整布局,防止标签被截断 plt.tight_layout() plt.show()
关键步骤说明
- 保留多级索引并格式化月份标签:将月份周期格式转为
%Y-%b样式,方便后续显示 - 计算条形位置:按月份分组,每个分组内为不同车辆的条形分配位置
- 双级X轴设置:主刻度为月份分组,次级刻度为车辆编号,通过
minor=True实现 - 布局调整:使用
tight_layout()避免标签被截断,调整次级标签的pad参数优化显示
内容的提问来源于stack exchange,提问作者jmcgowan
相关产品推荐
相关产品推荐

