如何创建分组和堆叠的条形图
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) :
- 如何修复创建堆积条形图的错误?
- 假设错误可以修复,这是创建条形图的最有效方法吗(例如,我真的需要为每个子公司创建三个子 df,还是有更优雅的方法?)
- 是否可以编写一个迭代代码,按国家/地区生成堆叠条形图,这样我就不需要为每个子公司创建一个?
- 仅供参考,堆叠条形图不是最佳选择,因为它们会使比较条形图值变得困难并且很容易被误解。可视化的目的是以易于理解的格式呈现数据;确保消息清楚。并排栏通常是更好的选择。
- 并排堆叠的条形图很难手动构建,最好使用像
seaborn.catplot
这样的图形级方法,这将创建一个单一的、易于阅读的数据可视化。
- 条形图刻度位于 0 索引范围(不是日期时间),日期只是标签,因此没有必要将它们转换为
datetime dtype
。
- 测试于
python 3.8.11
、pandas 1.3.2
、matplotlib 3.4.3
、seaborn 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'])}
创建一个 dict
的 DataFrames
- 访问如下值:
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))
我有一个非常庞大的数据集,其中有很多子公司为不同国家的三个客户群提供服务,就像这样(实际上有更多的子公司和日期):
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) :
- 如何修复创建堆积条形图的错误?
- 假设错误可以修复,这是创建条形图的最有效方法吗(例如,我真的需要为每个子公司创建三个子 df,还是有更优雅的方法?)
- 是否可以编写一个迭代代码,按国家/地区生成堆叠条形图,这样我就不需要为每个子公司创建一个?
- 仅供参考,堆叠条形图不是最佳选择,因为它们会使比较条形图值变得困难并且很容易被误解。可视化的目的是以易于理解的格式呈现数据;确保消息清楚。并排栏通常是更好的选择。
- 并排堆叠的条形图很难手动构建,最好使用像
seaborn.catplot
这样的图形级方法,这将创建一个单一的、易于阅读的数据可视化。 - 条形图刻度位于 0 索引范围(不是日期时间),日期只是标签,因此没有必要将它们转换为
datetime dtype
。 - 测试于
python 3.8.11
、pandas 1.3.2
、matplotlib 3.4.3
、seaborn 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'])}
创建一个dict
的DataFrames
- 访问如下值:
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))