matplotlib绘制堆叠条形图

from typing import Sequence
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np


class NormalBarChart(object):
    @staticmethod
    def draw_bar(data_df: pd.Series, title: str, seq: list | None = None) -> None:
        value_counts = data_df.value_counts()
        if not seq:
            seq = value_counts.index
        bars = plt.bar(seq, [value_counts[j] for j in seq], color='skyblue')
        for bar in bars:
            yval = bar.get_height()
            plt.text(bar.get_x() + bar.get_width() / 2, yval, yval, ha='center', va='bottom')
            plt.xlabel(title)
            plt.ylabel('频率')



class StackBarChart(object):
    def __init__(
            self, 
            data_group: dict[str: Sequence], 
            stack_label: Sequence[str | int], 
            bar_width: float = 0.8, 
            height_adjust: float = 0.0
        ):
        # dict应该形如: {'Group 1': [10, 20, 30], 'Group 2': [40, 50, 60]}
        self.xlabel: list = list(data_group.keys())
        self.stack_label: Sequence = stack_label
        self.stack_data = self.__get_stack_data(data_group)
        self.bar_width = bar_width
        self.height_adjust = height_adjust
        self.colors = plt.cm.tab10.colors

    def __get_stack_data(self, data_group: pd.DataFrame | dict):
        transposed_data = [list(group) for group in zip(*data_group.values())]
        return {k: v for k, v in zip(self.stack_label, transposed_data)}
    
    def __draw(self, bottom: Sequence) -> None:
        for values, label, color in zip(self.stack_data.values(), self.stack_label, self.colors):
            bars = plt.bar(
                self.xlabel, values, width=self.bar_width, label=label, 
                bottom=bottom, color=color
            )
            for bar, value, bot in zip(bars, values, bottom):
                if value == 0:
                    continue
                plt.text(
                    bar.get_x() + bar.get_width() / 2, 
                    bot + bar.get_height() / 2 + self.height_adjust,
                    value, ha='center', va='bottom'
                )
            bottom += values  # 更新底部高度

    def draw(self, title: str, colors: Sequence = None) -> None:
        bottom = np.zeros(len(self.xlabel))  # 初始化底部高度为0
        if colors is not None:
            if len(colors) != len(self.stack_label):
                raise ValueError("The length of colors should be equal to the length of stack_label")
            self.colors = colors
        self.__draw(bottom)
        plt.xlabel(title)
        plt.ylabel("频数")
    
    def show_legend(self, loc: str = "right"):
        custom_handles = [plt.Rectangle((0, 0), 1, 1, color=color) for color in self.colors]
        plt.legend(custom_handles, self.stack_label, loc=loc)


if __name__ == "__main__":
    plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置字体为黑体
    plt.rcParams['axes.unicode_minus'] = False  # 解决负号无法正常显示的问题
    data_group = {
        'Group 1': [10, 20, 30, 10],
        'Group 2': [40, 50, 60, 10],
        'Group 3': [70, 80, 90, 10]
    }
    # 创建实例并绘制图表
    stack_label = ["同意", "不同意", "中立", "未表态"]
    drawer = StackBarChart(data_group, stack_label, height_adjust=-4)
    drawer.draw("测试")
    drawer.show_legend("upper left")
    plt.show()

运行后的效果图:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值