如何创建分组和堆叠的条形图

How to create grouped and stacked bars

我有一个非常庞大的数据集,其中有很多子公司为不同国家的三个客户群提供服务,就像这样(实际上有更多的子公司和日期):

import pandas as pd
import matplotlib.pyplot as plt

df = pd.DataFrame({'subsidiary': ['EU','EU','EU','EU','EU','EU','EU','EU','EU','US','US','US','US','US','US','US','US','US'],'date': ['2019-03','2019-04', '2019-05','2019-03','2019-04', '2019-05','2019-03','2019-04', '2019-05','2019-03','2019-04', '2019-05','2019-03','2019-04', '2019-05','2019-03','2019-04', '2019-05'],'business': ['RETAIL','RETAIL','RETAIL','CORP','CORP','CORP','PUBLIC','PUBLIC','PUBLIC','RETAIL','RETAIL','RETAIL','CORP','CORP','CORP','PUBLIC','PUBLIC','PUBLIC'],'value': [500.36,600.45,700.55,750.66,950.89,1300.13,100.05,120.00,150.01,800.79,900.55,1000,3500.79,5000.36,4500.25,50.17,75.25,90.33]})
print(df)

我想通过生成堆积条形图对每个子公司进行分析。为此,我首先将 x 轴定义为唯一的月份,然后在这样的国家/地区为每种业务类型定义一个子集:

x=df['date'].drop_duplicates() 
EUCORP = df[(df['subsidiary']=='EU') & (df['business']=='CORP')] 
EURETAIL = df[(df['subsidiary']=='EU') & (df['business']=='RETAIL')] 
EUPUBLIC = df[(df['subsidiary']=='EU') & (df['business']=='PUBLIC')] 

然后我可以为每种业务类型制作条形图:

plotEUCORP = plt.bar(x=x, height=EUCORP['value'], width=.35)
plotEURETAIL = plt.bar(x=x, height=EURETAIL['value'], width=.35)
plotEUPUBLIC = plt.bar(x=x, height=EUPUBLIC['value'], width=.35)

但是,如果我尝试将所有三个堆叠在一张图表中,我总是失败:

plotEURETAIL = plt.bar(x=x, height=EURETAIL['value'], width=.35)
plotEUCORP = plt.bar(x=x, height=EUCORP['value'], width=.35, bottom=EURETAIL)
plotEUPUBLIC = plt.bar(x=x, height=EUPUBLIC['value'], width=.35, bottom=EURETAIL+EUCORP)
plt.show()

我总是收到以下错误消息:


ValueError:缺少 StrCategoryConverter 的类别信息;这可能是由于无意中混合了分类数据和数字数据造成的

ConversionError:无法将值转换为轴单位:子公司日期业务值 0 欧盟 2019-03 零售 500.36 1 欧盟 2019-04 零售 600.45 2 欧盟 2019-05 零售 700.55


我尝试将月份转换为日期格式 and/or 并对其进行索引,但它实际上让我更加困惑...

我真的很感激 help/support 以下任何一项,因为我已经花了很多时间来尝试解决这个问题(我仍然是一个 python 菜鸟,sry) :

  1. 如何修复创建堆积条形图的错误?
  2. 假设错误可以修复,这是创建条形图的最有效方法吗(例如,我真的需要为每个子公司创建三个子 df,还是有更优雅的方法?)
  3. 是否可以编写一个迭代代码,按国家/地区生成堆叠条形图,这样我就不需要为每个子公司创建一个?
  • 仅供参考,堆叠条形图不是最佳选择,因为它们会使比较条形图值变得困难并且很容易被误解。可视化的目的是以易于理解的格式呈现数据;确保消息清楚。并排栏通常是更好的选择。
  • 并排堆叠的条形图很难手动构建,最好使用像 seaborn.catplot 这样的图形级方法,这将创建一个单一的、易于阅读的数据可视化。
  • 条形图刻度位于 0 索引范围(不是日期时间),日期只是标签,因此没有必要将它们转换为 datetime dtype
  • 测试于 python 3.8.11pandas 1.3.2matplotlib 3.4.3seaborn 0.11.2

seaborn

import seaborn as sns

sns.catplot(kind='bar', data=df, col='subsidiary', x='date', y='value', hue='business')

创建分组和堆叠的条形图

  • Stacked Bar Chart and Grouped bar chart with labels
  • 在 OP 中创建堆积条的问题是 bottom 是在该组的整个数据框中设置的,而不仅仅是构成条高度的值.
  • 我真的需要为每个子公司创建三个子 dfs吗。是的,每个组都需要 DataFrame,因此在本例中为 6。
    • 可以使用 dict-comprehension.groupby 对象解压为 dict 来自动创建数据子集。
      • data = {''.join(k): v for k, v in df.groupby(['subsidiary', 'business'])} 创建一个 dictDataFrames
      • 访问如下值:data['EUCORP'].value
  • 自动创建绘图更加困难,可以看出x 取决于每个刻度有多少组柱,bottom 取决于每个后续绘图的值。
import numpy as np
import matplotlib.pyplot as plt

labels=df['date'].drop_duplicates()  # set the dates as labels
x0 = np.arange(len(labels))  # create an array of values for the ticks that can perform arithmetic with width (w)

# create the data groups with a dict comprehension and groupby
data = {''.join(k): v for k, v in df.groupby(['subsidiary', 'business'])}

# build the plots
subs = df.subsidiary.unique()
stacks = len(subs)  # how many stacks in each group for a tick location
business = df.business.unique()

# set the width
w = 0.35

# this needs to be adjusted based on the number of stacks; each location needs to be split into the proper number of locations
x1 = [x0 - w/stacks, x0 + w/stacks]

fig, ax = plt.subplots()
for x, sub in zip(x1, subs):
    bottom = 0
    for bus in business:
        height = data[f'{sub}{bus}'].value.to_numpy()
        ax.bar(x=x, height=height, width=w, bottom=bottom)
        bottom += height
        
ax.set_xticks(x0)
_ = ax.set_xticklabels(labels)
  • 如您所见,小值很难辨别,使用 ax.set_yscale('log') 不能像预期的那样使用堆叠条(例如,它不会使小值更具可读性)。

仅创建堆叠条形图

  • @r-beginners, use .pivot, or .pivot_table 所述,将数据框重塑为宽形式以创建 x 轴为元组的堆叠条形图('date''subsidiary')。
    • 如果每个类别都没有重复值,则使用.pivot
    • 使用.pivot_table,如果有重复值必须用aggfunc组合(如'sum''mean'等)
# reshape the dataframe
dfp = df.pivot(index=['date', 'subsidiary'], columns=['business'], values='value')

# plot stacked bars
dfp.plot(kind='bar', stacked=True, rot=0, figsize=(10, 4))