基础设置

首先进行基础的全局设置,无论是绘制什么图形,这些代码都是必不可少的

import matplotlib.pyplot as plt
from cycler import cycler
import numpy as np

# 进行必要的全局设置,让matplotlilb支持中文
plt.rcParams.update(
    {
        "font.sans-serif": "SimSun",
        "axes.unicode_minus": False,
    }
)

绘制平行柱状图

def _grouped_bar_loc(bar_width, legends_count, data_len):
    """bar_width:1月的苹果,橘子,香蕉三个柱子的总宽度,范围0~1
    series是苹果,橘子,香蕉,legends_count=3
    data_len:data是苹果的1,2,3,4,5月的销售数据,data_len=5
    返回:np.array[所有苹果柱子的位置,所有橘子柱子的位置,所有香蕉柱子的位置]"""
    # 即使本函数的思想要用于其他地方,start_num也不要随意修改
    start_num = (
        -(legends_count / 2 - 0.5) * (bar_width / legends_count)
        if legends_count % 2 == 0
        else -(legends_count // 2) * (bar_width / legends_count)
    )
    # first_bar_loc是苹果,橘子,香蕉的1月柱子的位置
    first_bar_loc = np.linspace(start_num, -start_num, legends_count)
    # https://numpy.org/doc/stable/reference/generated/numpy.broadcast_to.html
    return np.broadcast_to(
        first_bar_loc, (data_len, legends_count)
    ).transpose() + np.broadcast_to(np.arange(data_len), (legends_count, data_len))


def grouped_bar(axe, data, xticklabels, bar_width):
    "data是dict时本函数会对legends重新排序,若需保持顺序请将data转为dataframe并reindex"
    if isinstance(data, dict):
        grouped = sorted(data.items(), key=lambda i: i[0])
        legends = [i[0] for i in grouped]
        arrays = [i[1] for i in grouped]
    elif isinstance(data, pd.DataFrame):
        legends = data.columns
        arrays = data.to_numpy().transpose()
    # ========
    legends_count = len(legends)
    data_len = len(xticklabels)
    single_bar_width = bar_width / legends_count
    bar_loc = _grouped_bar_loc(bar_width, legends_count, data_len)
    for i in range(legends_count):
        axe.bar(bar_loc[i], arrays[i], width=single_bar_width, label=legends[i])
    axe.set(xlim=(-0.5, data_len - 0.5))
    axe.set_xticks(np.arange(data_len), labels=xticklabels)
    axe.xaxis.set_major_locator(plt.MultipleLocator(1))

绘制堆叠柱状图

def _stacked_bar_loc(data: np.ndarray):
    """data:np.array[[苹果的1,2,3,4,5月的销售数据],[橘子的1,2,3,4,5月的销售数据],……]
    返回:[[苹果的1,2,3,4,5月的bottom],[橘子的1,2,3,4,5月的bottom],……]"""
    # https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.bar.html
    # https://numpy.org/doc/stable/reference/generated/numpy.ufunc.accumulate.html
    return np.add.accumulate(data, axis=0) - data


def stacked_bar(axe, data, xticklabels, bar_width):
    "data是dict时本函数会对legends重新排序,若需保持顺序请将data转为dataframe并reindex"
    if isinstance(data, dict):
        grouped = sorted(data.items(), key=lambda i: i[0])
        legends = [i[0] for i in grouped]
        npdata = np.vstack([i[1] for i in grouped])
    elif isinstance(data, pd.DataFrame):
        npdata = data.to_numpy().transpose()
        legends = data.columns
    # ========
    bar_loc = _stacked_bar_loc(npdata)
    data_len = len(xticklabels)
    for i in range(len(legends)):
        axe.bar(np.arange(data_len), npdata[i], bar_width, bar_loc[i], label=legends[i])
    axe.set(xlim=(-0.5, data_len - 0.5))
    axe.set_xticks(np.arange(data_len), labels=xticklabels)
    axe.xaxis.set_major_locator(plt.MultipleLocator(1))

结果展示

# ========首先配置测试数据========
dict数据 = {
    "苹果": np.array([4, 7, 3, 6, 2]),
    "橘子": np.array([8, 4, 2, 6, 1]),
    "香蕉": [3, 4, 5, 2, 5],
}
df数据 = pd.DataFrame(
    {"苹果": [4, 7, 3, 6, 2], "橘子": [8, 4, 2, 6, 1], "香蕉": [3, 4, 5, 2, 5]}
)
月份 = ["1月", "2月", "3月", "4月", "5月"]
# ========开始绘图========
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
#用四种方式绘制复杂柱状图
stacked_bar(axes[0, 0], df数据, 月份, 0.4)
grouped_bar(axes[0, 1], dict数据, 月份, 0.75)
stacked_bar(axes[1, 0], dict数据, 月份, 0.6)
grouped_bar(axes[1, 1], df数据, 月份, 0.6)
# 设置图例,把图例放在外面
# https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.legend.html
axes[0, 0].legend(bbox_to_anchor=(1, 1), loc="lower right", title="df数据")
axes[0, 1].legend(bbox_to_anchor=(1, 1), loc="lower right", title="dict数据")
axes[1, 0].legend(bbox_to_anchor=(1, -0.07), loc="upper right", title="dict数据")
axes[1, 1].legend(bbox_to_anchor=(1, -0.07), loc="upper right", title="df数据")
# 展示并保存图片
fig.tight_layout()
plt.show()

生成的图片如下
matplotlib复杂柱状图