matplotlib:X轴的多层级标签合并

需求:如何实现像Excel透视图的多层级标签

创建示例数据集

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']     #显示中文
plt.rcParams['axes.unicode_minus'] = False       #正常显示负号

data = {
    'col1': ['X', 'X', 'Y', 'Y', 'Y', 'Z'],
    'col2': ['A', 'A', 'A', 'B', 'B', 'B'],
    'col3': ['a', 'b', 'c', 'c', 'b', 'c'],
    'value': [1, 2, 3, 4, 5, 6]
}
df = pd.DataFrame(data)
print(df)

方法一:换行符拼接

# 图形基础设置
dpi = 96
img_wdt = 1200
img_hgt = 600
fig = plt.figure(figsize=(img_wdt / dpi, img_hgt / dpi), dpi=dpi)
ax = fig.add_subplot(111)
plt.title("销售情况", fontdict=dict(fontsize=11, fontweight='bold', color='black'), backgroundcolor='yellow', loc='left')

# X轴设置
df['x轴标签'] = df.apply(lambda x: "\n".join([x['col3'], str(x['col2']), x['col1']]), axis=1)
xaxis_n = np.arange(len(df))  # x轴索引列表
plt.xticks(xaxis_n, fontsize=9, rotation=0)
ax.set_xticklabels(list(df['x轴标签']))

ax.bar(xaxis_n, df['value'], label='销量', color=['#0270C1'] * len(df))

plt.show()

缺点:一定程度上实现了,但是相同的分组没有合并。

方法二:数据标签

所以内置的X轴标签方法无法实现合并效果,只能通过数据标签的形式实现。

思路:

  • 最后一层不用合并
  • 计算出每个层级的分割线的纵向长度,也决定该层级标签的Y轴位置,长度可以设置在Y轴范围长度的百分之几(3%)
  • matplotlib无法在Y轴范围之外添加文本和辅助线,准确的说是添加了看不到,因此只能将X轴标签的区域纳入Y轴范围。

实现代码:

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt

plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']  # 显示中文
plt.rcParams['axes.unicode_minus'] = False  # 正常显示负号

data = {
    'col1': ['X', 'X', 'Y', 'Y', 'Z', 'Z'],
    'col2': ['A', 'A', 'A', 'B', 'B', 'B'],
    'col3': ['a', 'b', 'c', 'c', 'c', 'c'],
    'value': [1, 2, 3, 4, 5, 6]
}
df = pd.DataFrame(data)
df['x轴标签'] = df.apply(lambda x: "\n".join([x['col3'], str(x['col2']), x['col1']]), axis=1)
df = df.reset_index()
df['x轴序号'] = df.index
print(df)

# 图形基础设置
dpi = 96
img_wdt = 1200
img_hgt = 600
fig = plt.figure(figsize=(img_wdt / dpi, img_hgt / dpi), dpi=dpi)
ax = fig.add_subplot(111)
plt.title("销售情况", fontdict=dict(fontsize=11, fontweight='bold', color='black'), backgroundcolor='yellow', loc='left')

y_series = df['value']
y_min, y_max = min(y_series), max(y_series)
ylim_min = 0
ylim_max = (y_max - ylim_min) * 1.2
xaxis_n = list(df['x轴序号'])  # x轴索引列表
bar_width = 0.7  # 柱子宽度
bar_interval = (0.5 - bar_width / 2) * 2
xlim_min = min(xaxis_n) - bar_width / 2 - (1 / 2 - bar_width / 2)
xlim_max = max(xaxis_n) + bar_width / 2 + (1 / 2 - bar_width / 2)
plt.xlim(xlim_min, xlim_max)

# X轴设置
split_line_height = (y_max - ylim_min) * 0.07  # x轴标签每层分割线的高度
group_cols = ['col1', 'col2', 'col3']
ylim_min2 = ylim_max - (ylim_max - ylim_min + len(group_cols) * split_line_height)
plt.ylim(ylim_min2, ylim_max)
plt.axhline(y=ylim_min, c="black", ls="-", lw=1)  # 添加X轴水平线

print(xaxis_n)

plt.xticks(xaxis_n, fontsize=9, rotation=0)
ax.set_xticklabels([])  # 原始标签置空
ax.xaxis.set_visible(False)
ax.spines['bottom'].set_visible(False)  # 隐藏图形下方边框

for index, row in df.iterrows():

    if row['x轴序号'] == 0:
        col1_value = {'loc': row['x轴序号'], 'val': row['col1']}
        col2_value = {'loc': row['x轴序号'], 'val': row['col2']}
    else:
        if row['col1'] != col1_value['val']:
            text = col1_value['val']
            text_x = (col1_value['loc'] + row['x轴序号'] - 1) / 2
            ax.text(text_x, ylim_min - split_line_height * 5 / 2, text, ha='center', va='center', fontsize=9,
                    color='black')  # 数据标签
            plt.vlines(row['x轴序号'] - bar_width / 2 - bar_interval / 2, ylim_min, ylim_min - 3 * split_line_height,
                       color='red', linestyles='solid', alpha=1)  # 添加左侧分割线
            col1_value = {**col1_value, 'loc': row['x轴序号'], 'val': row['col1']}

            # 也要更新col2,因为col1分割的点col2必需要分
            text = col2_value['val']
            text_x = (col2_value['loc'] + row['x轴序号'] - 1) / 2
            ax.text(text_x, ylim_min - split_line_height * 3 / 2, text, ha='center', va='center', fontsize=9,
                    color='black')  # 数据标签
            plt.vlines(row['x轴序号'] - bar_width / 2 - bar_interval / 2, ylim_min, ylim_min - 2 * split_line_height,
                       color='blue', linestyles='solid', alpha=1)  # 添加左侧分割线
            col2_value = {**col2_value, 'loc': row['x轴序号'], 'val': row['col2']}

        elif row['col2'] != col2_value['val']:
            text = col2_value['val']
            text_x = (col2_value['loc'] + row['x轴序号'] - 1) / 2
            ax.text(text_x, ylim_min - split_line_height * 3 / 2, text, ha='center', va='center', fontsize=9,
                    color='black')  # 数据标签
            plt.vlines(row['x轴序号'] - bar_width / 2 - bar_interval / 2, ylim_min, ylim_min - 2 * split_line_height,
                       color='blue', linestyles='solid', alpha=1)  # 添加左侧分割线
            col2_value = {**col2_value, 'loc': row['x轴序号'], 'val': row['col2']}
        if index == df.shape[0] - 1:  # 最后一行
            # 第一层
            text = row['col1']
            text_x = (col1_value['loc'] + row['x轴序号']) / 2
            ax.text(text_x, ylim_min - split_line_height * 5 / 2, text, ha='center', va='center', fontsize=9,
                    color='black')  # 数据标签
            # 第二层
            text = row['col2']
            text_x = (col2_value['loc'] + row['x轴序号']) / 2
            ax.text(text_x, ylim_min - split_line_height * 3 / 2, text, ha='center', va='center', fontsize=9,
                    color='black')  # 数据标签

    # 最后一层标签
    ax.text(row['x轴序号'], ylim_min - split_line_height / 2, row['col3'], ha='center', va='center', fontsize=9,
            color='black')  # 数据标签
    plt.vlines(row['x轴序号'] + bar_width / 2 + bar_interval / 2, ylim_min, ylim_min - split_line_height, color='red',
               linestyles='solid', alpha=1)  # 添加右侧分割线

ax.bar(xaxis_n, df['value'], label='value', color=['#0270C1'] * len(df), width=bar_width)

plt.show()

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值