如何使用不同方向的多个 xticks/axis 标签将子组分组在一起?

问题描述 投票:0回答:1

我编写了一个 Python 脚本(如下)来生成 Matplotlib 图,以显示按子组(季节)、组(城市)和不同颜色(年份)分组的箱线图数据。

但是,组(城市)标签相当粗糙(通过在标签文本中包含多个空格来实现),我正在寻找更好的方法来实现这一点。目前,城市标签并未完全集中在每组季节中,并且如果地块大小发生变化,它们也不会缩放。

我想要的另一个功能是季节标签偏移 45 度,同时保持城市文本水平(如图所示)。

我怎样才能以更好、更Pythonic的方式实现这一点?

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()

Link to Boxplot for reference

python matplotlib axis axis-labels xticks
1个回答
0
投票

我相信这段代码可以解决您的问题。所有重要的更改都在代码中进行了注释。请随意删除和修改这些部分以了解发生了什么。基本上我摆脱了相关 matplotlib 函数的 hacky 东西并应用了良好的实践。现在,您还可以毫无问题地重新缩放图像,因为布局不再是硬编码的。

import matplotlib.pyplot as plt
import numpy as np

# Too much repetition for the sample (c, d, e, f, g, h are copies)
data_a = [[1,2,5], [5,7,2,2,5], [7,2,5], [7,2,5]]
data_b = [[6,4,2], [1,2,5,3,2], [2,3,5,1], []]

fig, ax = plt.subplots() # Use object syntax.

box_width = 0.6
space_year = 0.4
space_season = 2.0
space_city = 10

ticks_positions_array = np.array([])

# Avoid repetition with a for loop (mods only requiere changes in one place)
for indx, data_tuple in enumerate(
    [(data_a, data_b), (data_a, data_b), (data_a, data_b), (data_a, data_b)]
):

    left_boxplot = ax.boxplot(
        data_tuple[0],
        ## Use indx*space_city to add consistent spacing
        positions=np.array(range(len(data_tuple[0])))*space_season-space_year+indx*space_city,
        sym='',
        widths=box_width,
        ## Add coloring to the recipe instead of the set_box_color function,
        ## it is clearer, no need to hop to the function definition to know what is doing.
        patch_artist=False,
        boxprops=dict(color='#D7191C'),
        capprops=dict(color='#D7191C'),
        whiskerprops=dict(color='#D7191C'),
        medianprops=dict(color='#D7191C')
    )
    
    right_boxplot = ax.boxplot(
        data_tuple[1], 
        ## Use indx*space_city to add consistent spacing
        positions=np.array(range(len(data_tuple[1])))*space_season+space_year+indx*space_city,
        sym='',
        widths=box_width,
        patch_artist=False,
        ## Add coloring to the recipe instead of the set_box_color function,
        ## it is clearer, no need to hop to the function definition to know what is doing.
        boxprops=dict(color='#2C7BB6'),
        capprops=dict(color='#2C7BB6'),
        whiskerprops=dict(color='#2C7BB6'),
        medianprops=dict(color='#2C7BB6')
    )

    # Keep positions middlepoints to later add ticks:
    ticks_positions_array = np.concatenate(
        [
            ticks_positions_array,
            (np.array(range(len(data_tuple[0])))*space_season-space_year+indx*space_city +
            np.array(range(len(data_tuple[1])))*space_season+space_year+indx*space_city) /2.
        ]
    )

# No need to create empty plots to create a legend. Assign labels to the last to boxes
# of your previous for loop only. (The colors are already there)
ax.legend(
    handles=[left_boxplot["boxes"][0], right_boxplot["boxes"][0]],
    labels=['2016', '2017'],
    loc='upper center'
)

ax.set_xticks(
    ticks = ticks_positions_array, # No manual set, we already have the positions
    labels = ['Spring', 'Summer', 'Fall', 'Winter']*4, # You can multiply a list to reapeat its values.
    rotation=55
)

# City axis by duplicating x axis
ticks_positions_city = [ticks_positions_array[(i*4):(i+1)*4].sum()/4 for i in range (4)] # Avg of the 4 seasons.
ax_second_label = ax.twiny() # New second x axis that shares the same y-axis
ax_second_label.set_xlim(left=min(ticks_positions_array), right=max(ticks_positions_array)) # Fix the length of the axis to match the season one.
ax_second_label.set_xticks(
    ticks = ticks_positions_city, # No manual set, we already have the positions
    labels = ['New York', 'Chicago', 'Houston', 'Los Angeles']
)
ax_second_label.xaxis.set_ticks_position(position="bottom") # Move axis to bottom
ax_second_label.tick_params(
    axis='both',
    which='both',
    length=0, # Hide ticks by making them len=0
    pad=60 # Push down labels to avoid overlapping.
)
© www.soinside.com 2019 - 2024. All rights reserved.