You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用不同方向的多组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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 11:02:03