如何用不同方向的多组X轴刻度/标签实现子组分组?
问题描述
我编写了一段Python脚本(如下),使用Matplotlib绘制箱线图,将数据按子组(季节)、组(城市)以及颜色(年份)进行分组展示。但当前通过空格实现的城市标签较为粗糙,既无法精准居中于对应季节组上方,也不能随图表尺寸缩放。此外,我希望季节标签呈45度倾斜,同时保持城市标签水平(如图所示)。请问如何以更Python化的方式实现这些需求?
import matplotlib.pyplot as plt import numpy as np # data_city_year = [[Spring], [Summer], [Fall], [Winter]] data_a = [[1,2,5], [5,7,2,2,5], [7,2,5], [7,2,5]] # NYC 2016 data_b = [[6,4,2], [1,2,5,3,2], [2,3,5,1], []] # NYC 2017 data_c = [[1,2,5], [5,7,2,2,5], [7,2,5], [7,2,5]] # Chi 2016 data_d = [[6,4,2], [1,2,5,3,2], [2,3,5,1], []] # Chi 2017 data_e = [[1,2,5], [5,7,2,2,5], [7,2,5], [7,2,5]] # Hou 2016 data_f = [[6,4,2], [1,2,5,3,2], [2,3,5,1], []] # Hou 2017 data_g = [[1,2,5], [5,7,2,2,5], [7,2,5], [7,2,5]] # LA 2016 data_h = [[6,4,2], [1,2,5,3,2], [2,3,5,1], []] # LA 2017 def set_box_color(bp, color): plt.setp(bp['boxes'], color=color) plt.setp(bp['whiskers'], color=color) plt.setp(bp['caps'], color=color) plt.setp(bp['medians'], color=color) plt.figure() box_width = 0.6 space_year = 0.4 space_season = 2.0 space_city = 10 bpl = plt.boxplot(data_a, positions=np.array(range(len(data_a)))*space_season-space_year, sym='', widths=box_width) bpr = plt.boxplot(data_b, positions=np.array(range(len(data_b)))*space_season+space_year, sym='', widths=box_width) set_box_color(bpl, '#D7191C') set_box_color(bpr, '#2C7BB6') bpl = plt.boxplot(data_c, positions=np.array(range(len(data_a)))*space_season-space_year+space_city, sym='', widths=box_width) bpr = plt.boxplot(data_d, positions=np.array(range(len(data_b)))*space_season+space_year+space_city, sym='', widths=box_width) set_box_color(bpl, '#D7191C') set_box_color(bpr, '#2C7BB6') bpl = plt.boxplot(data_e, positions=np.array(range(len(data_a)))*space_season-space_year+space_city+space_city, sym='', widths=box_width) bpr = plt.boxplot(data_f, positions=np.array(range(len(data_b)))*space_season+space_year+space_city+space_city, sym='', widths=box_width) set_box_color(bpl, '#D7191C') set_box_color(bpr, '#2C7BB6') bpl = plt.boxplot(data_c, positions=np.array(range(len(data_a)))*space_season-space_year+space_city+space_city+space_city, sym='', widths=box_width) bpr = plt.boxplot(data_d, positions=np.array(range(len(data_b)))*space_season+space_year+space_city+space_city+space_city, sym='', widths=box_width) set_box_color(bpl, '#D7191C') set_box_color(bpr, '#2C7BB6') # draw temporary red and blue lines and use them to create a legend plt.plot([], c='#D7191C', label='2016') plt.plot([], c='#2C7BB6', label='2017') plt.legend(loc='upper center') ticks = ['Spring', 'Summer', 'Fall', 'Winter', 'Spring', 'Summer', 'Fall', 'Winter', 'Spring', 'Summer', 'Fall', 'Winter', 'Spring', 'Summer', 'Fall', 'Winter'] plt.xticks((0,2,4,6,10,12,14,16,20,22,24,26,30,32,34,36), ticks, rotation=45) plt.plot() plt.xlabel('New York Chicago Houston Los Angeles') plt.tight_layout() plt.show()

解决方案
1. 重构数据结构,简化重复代码
将城市、年份和对应数据整理成结构化列表,避免重复调用boxplot,提升代码可维护性:
import matplotlib.pyplot as plt import numpy as np # 结构化数据:每个元素为(城市名称, 2016数据, 2017数据) city_data = [ ("New York", [[1,2,5], [5,7,2,2,5], [7,2,5], [7,2,5]], [[6,4,2], [1,2,5,3,2], [2,3,5,1], []]), ("Chicago", [[1,2,5], [5,7,2,2,5], [7,2,5], [7,2,5]], [[6,4,2], [1,2,5,3,2], [2,3,5,1], []]), ("Houston", [[1,2,5], [5,7,2,2,5], [7,2,5], [7,2,5]], [[6,4,2], [1,2,5,3,2], [2,3,5,1], []]), ("Los Angeles", [[1,2,5], [5,7,2,2,5], [7,2,5], [7,2,5]], [[6,4,2], [1,2,5,3,2], [2,3,5,1], []]) ] seasons = ["Spring", "Summer", "Fall", "Winter"] colors = {"2016": "#D7191C", "2017": "#2C7BB6"}
2. 自动计算布局位置,避免硬编码
定义统一间距参数,自动计算每个箱线图的位置,确保布局规整:
box_width = 0.6 space_year = 0.4 # 同季节不同年份箱线的间距 space_season = 2.0 # 不同季节之间的间距 space_city = 8.0 # 不同城市组之间的间距 plt.figure(figsize=(12, 6)) ax = plt.gca() # 遍历每个城市绘制箱线图 for city_idx, (city_name, data_2016, data_2017) in enumerate(city_data): city_offset = city_idx * space_city # 计算2016和2017数据的位置 pos_2016 = np.arange(len(seasons)) * space_season - space_year + city_offset pos_2017 = np.arange(len(seasons)) * space_season + space_year + city_offset bp_2016 = plt.boxplot(data_2016, positions=pos_2016, sym='', widths=box_width) bp_2017 = plt.boxplot(data_2017, positions=pos_2017, sym='', widths=box_width) # 批量设置颜色 for element in ['boxes', 'whiskers', 'caps', 'medians']: plt.setp(bp_2016[element], color=colors["2016"]) plt.setp(bp_2017[element], color=colors["2017"])
3. 实现双级X轴标签,精准对齐
利用Matplotlib的双轴功能,主轴放置45度倾斜的季节标签,顶部辅助轴放置水平居中的城市标签:
# 设置主X轴(季节标签) season_ticks = [] for city_idx in range(len(city_data)): city_offset = city_idx * space_city season_ticks.extend(np.arange(len(seasons)) * space_season + city_offset) ax.set_xticks(season_ticks) ax.set_xticklabels(seasons * len(city_data), rotation=45, ha="right") # 设置顶部辅助X轴(城市标签) ax2 = ax.twiny() # 计算每个城市标签的居中位置 city_ticks = [city_idx * space_city + (len(seasons)-1)*space_season/2 for city_idx in range(len(city_data))] ax2.set_xticks(city_ticks) ax2.set_xticklabels([city[0] for city in city_data], rotation=0) # 隐藏顶部轴的刻度线,保持简洁 ax2.tick_params(axis='x', which='both', bottom=False, top=False)
4. 完善图例与自适应布局
# 添加图例 plt.plot([], c=colors["2016"], label='2016') plt.plot([], c=colors["2017"], label='2017') plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=2) plt.ylabel("Value") plt.tight_layout() plt.show()
完整优化代码
import matplotlib.pyplot as plt import numpy as np # 结构化数据 city_data = [ ("New York", [[1,2,5], [5,7,2,2,5], [7,2,5], [7,2,5]], [[6,4,2], [1,2,5,3,2], [2,3,5,1], []]), ("Chicago", [[1,2,5], [5,7,2,2,5], [7,2,5], [7,2,5]], [[6,4,2], [1,2,5,3,2], [2,3,5,1], []]), ("Houston", [[1,2,5], [5,7,2,2,5], [7,2,5], [7,2,5]], [[6,4,2], [1,2,5,3,2], [2,3,5,1], []]), ("Los Angeles", [[1,2,5], [5,7,2,2,5], [7,2,5], [7,2,5]], [[6,4,2], [1,2,5,3,2], [2,3,5,1], []]) ] seasons = ["Spring", "Summer", "Fall", "Winter"] colors = {"2016": "#D7191C", "2017": "#2C7BB6"} # 布局参数 box_width = 0.6 space_year = 0.4 space_season = 2.0 space_city = 8.0 plt.figure(figsize=(12, 6)) ax = plt.gca() # 绘制每个城市的箱线图 for city_idx, (city_name, data_2016, data_2017) in enumerate(city_data): city_offset = city_idx * space_city pos_2016 = np.arange(len(seasons)) * space_season - space_year + city_offset pos_2017 = np.arange(len(seasons)) * space_season + space_year + city_offset bp_2016 = plt.boxplot(data_2016, positions=pos_2016, sym='', widths=box_width) bp_2017 = plt.boxplot(data_2017, positions=pos_2017, sym='', widths=box_width) # 设置颜色 for element in ['boxes', 'whiskers', 'caps', 'medians']: plt.setp(bp_2016[element], color=colors["2016"]) plt.setp(bp_2017[element], color=colors["2017"]) # 设置主X轴(季节标签) season_ticks = [] for city_idx in range(len(city_data)): city_offset = city_idx * space_city season_ticks.extend(np.arange(len(seasons)) * space_season + city_offset) ax.set_xticks(season_ticks) ax.set_xticklabels(seasons * len(city_data), rotation=45, ha="right") # 设置顶部辅助X轴(城市标签) ax2 = ax.twiny() city_ticks = [city_idx * space_city + (len(seasons)-1)*space_season/2 for city_idx in range(len(city_data))] ax2.set_xticks(city_ticks) ax2.set_xticklabels([city[0] for city in city_data], rotation=0) ax2.tick_params(axis='x', which='both', bottom=False, top=False) # 图例与布局 plt.plot([], c=colors["2016"], label='2016') plt.plot([], c=colors["2017"], label='2017') plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=2) plt.ylabel("Value") plt.tight_layout() plt.show()
内容的提问来源于stack exchange,提问作者Vuro H
相关产品推荐
相关产品推荐

