我有一个CSV文件,其中包含3列,分别是州,办公室ID和该办公室的销售。
我想计算给定状态下每个办事处的销售额百分比(每个州的所有百分比总计为100%)。
df = pd.DataFrame({'state': ['CA', 'WA', 'CO', 'AZ'] * 3,
'office_id': range(1, 7) * 2,
'sales': [np.random.randint(100000, 999999)
for _ in range(12)]})
df.groupby(['state', 'office_id']).agg({'sales': 'sum'})
这将返回:
sales
state office_id
AZ 2 839507
4 373917
6 347225
CA 1 798585
3 890850
5 454423
CO 1 819975
3 202969
5 614011
WA 2 163942
4 369858
6 959285
我似乎无法弄清楚如何“达到”
state
的groupby
水平,从而将整个sales
的state
总计起来计算分数。#1 楼
Paul H的回答是正确的,您将必须创建第二个groupby
对象,但是您可以以一种更简单的方式计算百分比-只需groupby
和state_office
并将sales
列除以其和即可。复制Paul H答案的开头:# From Paul H
import numpy as np
import pandas as pd
np.random.seed(0)
df = pd.DataFrame({'state': ['CA', 'WA', 'CO', 'AZ'] * 3,
'office_id': list(range(1, 7)) * 2,
'sales': [np.random.randint(100000, 999999)
for _ in range(12)]})
state_office = df.groupby(['state', 'office_id']).agg({'sales': 'sum'})
# Change: groupby state_office and divide by sum
state_pcts = state_office.groupby(level=0).apply(lambda x:
100 * x / float(x.sum()))
返回值:
sales
state office_id
AZ 2 16.981365
4 19.250033
6 63.768601
CA 1 19.331879
3 33.858747
5 46.809373
CO 1 36.851857
3 19.874290
5 43.273852
WA 2 34.707233
4 35.511259
6 29.781508
评论
这里发生了什么?据我了解,x是某种类型的表,因此100 * x在直觉上没有意义(尤其是当某些单元格包含诸如AZ之类的字符串时,...)。
– dhardy
2015年2月6日,9:42
@dhardy state_office是具有多重索引的系列-因此它只是一列,其值都是数字。完成分组方式后,每个x都是该列的子集。那有意义吗?
–exp1orer
2015年2月8日在15:22
可以,但对我不起作用。 Python 3中的熊猫工作方式有所不同吗?
– dhardy
2015年2月9日在9:59
level = 0是什么意思?
–van_d39
16年11月22日在22:39
@Veenit表示您是按索引的第一级而不是其中一个列进行分组。
–exp1orer
16年11月23日在7:50
#2 楼
您需要制作第二个按状态分组的groupby对象,然后使用div
方法:level='state'
中的div
kwarg告诉熊猫广播/加入数据帧基于索引的state
级别中的值。评论
如果您有3个索引,此方法有效吗?我首先在3列上进行了分组。然后,我仅对2进行了第二次分组,并计算了总和。然后,我尝试使用div,但级别为[[index1“,” index2“],但它告诉我两个MultiIndex对象之间的级别上的Join是不明确的。
– Ger
17年1月4日在13:23
@Ger它确实起作用,但是我无法从该描述中得知您在做什么错。在网站上搜索更多。如果找不到任何内容,请创建一个新的问题,并用一个可复制的示例演示该问题。 stackoverflow.com/questions/20109391/…
– Paul H
17年1月4日在15:20
#3 楼
为简洁起见,我将使用SeriesGroupBy:In [11]: c = df.groupby(['state', 'office_id'])['sales'].sum().rename("count")
In [12]: c
Out[12]:
state office_id
AZ 2 925105
4 592852
6 362198
CA 1 819164
3 743055
5 292885
CO 1 525994
3 338378
5 490335
WA 2 623380
4 441560
6 451428
Name: count, dtype: int64
In [13]: c / c.groupby(level=0).sum()
Out[13]:
state office_id
AZ 2 0.492037
4 0.315321
6 0.192643
CA 1 0.441573
3 0.400546
5 0.157881
CO 1 0.388271
3 0.249779
5 0.361949
WA 2 0.411101
4 0.291196
6 0.297703
Name: count, dtype: float64
对于多个组,您必须使用transform(使用Radical的df):
In [21]: c = df.groupby(["Group 1","Group 2","Final Group"])["Numbers I want as percents"].sum().rename("count")
In [22]: c / c.groupby(level=[0, 1]).transform("sum")
Out[22]:
Group 1 Group 2 Final Group
AAHQ BOSC OWON 0.331006
TLAM 0.668994
MQVF BWSI 0.288961
FXZM 0.711039
ODWV NFCH 0.262395
...
Name: count, dtype: float64
这似乎比其他答案要好一些(对我来说,仅比Radical答案的速度低两倍,约0.08秒)。
评论
这是超级快。我建议将其作为首选的熊猫方法。真正利用了numpy的向量化和熊猫索引功能。
–查尔斯
18 Mar 23 '18 at 12:14
这对我也很有效,因为我正在与多个小组合作。谢谢。
–艾琳
18年8月14日在6:01
#4 楼
我认为这需要进行基准测试。使用OP的原始DataFrame,df = pd.DataFrame({
'state': ['CA', 'WA', 'CO', 'AZ'] * 3,
'office_id': range(1, 7) * 2,
'sales': [np.random.randint(100000, 999999) for _ in range(12)]
})
第一安迪·海顿
矢量化和熊猫索引的优势。
c = df.groupby(['state', 'office_id'])['sales'].sum().rename("count")
c / c.groupby(level=0).sum()
每个循环3.42 ms±16.7 µs
(平均±标准偏差,运行7次,每个循环100个)
第二个Paul H
state_office = df.groupby(['state', 'office_id']).agg({'sales': 'sum'})
state = df.groupby(['state']).agg({'sales': 'sum'})
state_office.div(state, level='state') * 100
每个循环4.66 ms±24.4 µs
(平均值±标准运行7次运行,每个循环100次)
第三次exp1orer
这是最慢的答案,因为它为每个
x.sum()
计算x
在0级。对我来说,这仍然是一个有用的答案,尽管不是目前的形式。为了在较小的数据集上进行快速EDA,
apply
允许您使用方法链接将其写在一行中。因此,我们不需要决定变量的名称,对于您最宝贵的资源(您的大脑!)来说,这实际上在计算上非常昂贵。这里是修改,
(
df.groupby(['state', 'office_id'])
.agg({'sales': 'sum'})
.groupby(level=0)
.apply(lambda x: 100 * x / float(x.sum()))
)
每个循环10.6 ms±81.5 µs
(平均±标准偏差,运行7次,每个循环100个)
所以没人在小型数据集上大约需要6ms。但是,这样做的速度提高了3倍,并且在具有高基数groupbys的较大数据集上将产生巨大的差异。
在上面的代码中,我们制作了一个形状为(12,000,000,3 )包含14412个状态类别和600个office_id,
import string
import numpy as np
import pandas as pd
np.random.seed(0)
groups = [
''.join(i) for i in zip(
np.random.choice(np.array([i for i in string.ascii_lowercase]), 30000),
np.random.choice(np.array([i for i in string.ascii_lowercase]), 30000),
np.random.choice(np.array([i for i in string.ascii_lowercase]), 30000),
)
]
df = pd.DataFrame({'state': groups * 400,
'office_id': list(range(1, 601)) * 20000,
'sales': [np.random.randint(100000, 999999)
for _ in range(12)] * 1000000
})
使用Andy's,
每个循环2 s±10.4 ms
(平均±标准偏差7次运行,每个1循环)
和exp1orer
每个循环19 s±77.1 ms
(平均值±标准偏差7次运行,每个循环1次)
因此,现在我们看到x10在大型,高基数数据集上的速度有所提高。
如果您要确保对这三个答案进行UV处理紫外线!!
#5 楼
(此解决方案的灵感来自本文https://pbpython.com/pandas_transform.html)。我发现以下使用
transformation
的解决方案最简单(可能是最快):转换:虽然聚合必须返回缩减后的
数据版本,但是转换可以返回完整的
数据的某些转换后的版本以进行重组。对于这种转换,输出与输入具有相同的形状。
因此使用
transformation
,解决方案是1-liner:df['%'] = 100 * df['sales'] / df.groupby('state')['sales'].transform('sum')
如果打印:
print(df.sort_values(['state', 'office_id']).reset_index(drop=True))
state office_id sales %
0 AZ 2 195197 9.844309
1 AZ 4 877890 44.274352
2 AZ 6 909754 45.881339
3 CA 1 614752 50.415708
4 CA 3 395340 32.421767
5 CA 5 209274 17.162525
6 CO 1 549430 42.659629
7 CO 3 457514 35.522956
8 CO 5 280995 21.817415
9 WA 2 828238 35.696929
10 WA 4 719366 31.004563
11 WA 6 772590 33.298509
评论
@Cancer这是我最喜欢的答案,因为它使df保持为df(不转换为序列),仅添加%列。谢谢
– T.Fung
5月16日13:25
这个答案的变化对我来说使用transform('max')效果很好
–谢尔顿
6月8日13:34
指向描述transform()的帖子的链接非常棒。我认为这是一个比所选解决方案更好的解决方案,但需要学习转换(我认为这是一个积极的方面:)
– pojda
12月1日14:30
#6 楼
我知道这是一个古老的问题,但是对于具有大量唯一组的数据集,exp1orer的答案非常慢(可能是由于lambda)。我建立了他们的答案,将其转换为数组计算,因此现在超级快!下面是示例代码:创建具有50,000个唯一组的测试数据框
import random
import string
import pandas as pd
import numpy as np
np.random.seed(0)
# This is the total number of groups to be created
NumberOfGroups = 50000
# Create a lot of groups (random strings of 4 letters)
Group1 = [''.join(random.choice(string.ascii_uppercase) for _ in range(4)) for x in range(NumberOfGroups/10)]*10
Group2 = [''.join(random.choice(string.ascii_uppercase) for _ in range(4)) for x in range(NumberOfGroups/2)]*2
FinalGroup = [''.join(random.choice(string.ascii_uppercase) for _ in range(4)) for x in range(NumberOfGroups)]
# Make the numbers
NumbersForPercents = [np.random.randint(100, 999) for _ in range(NumberOfGroups)]
# Make the dataframe
df = pd.DataFrame({'Group 1': Group1,
'Group 2': Group2,
'Final Group': FinalGroup,
'Numbers I want as percents': NumbersForPercents})
分组后的外观如下:
Numbers I want as percents
Group 1 Group 2 Final Group
AAAH AQYR RMCH 847
XDCL 182
DQGO ALVF 132
AVPH 894
OVGH NVOO 650
VKQP 857
VNLY HYFW 884
MOYH 469
XOOC GIDS 168
HTOY 544
AACE HNXU RAXK 243
YZNK 750
NOYI NYGC 399
ZYCI 614
QKGK CRLF 520
UXNA 970
TXAR MLNB 356
NMFJ 904
VQYG NPON 504
QPKQ 948
...
[50000 rows x 1 columns]
求百分比的数组方法:
# Initial grouping (basically a sorted version of df)
PreGroupby_df = df.groupby(["Group 1","Group 2","Final Group"]).agg({'Numbers I want as percents': 'sum'}).reset_index()
# Get the sum of values for the "final group", append "_Sum" to it's column name, and change it into a dataframe (.reset_index)
SumGroup_df = df.groupby(["Group 1","Group 2"]).agg({'Numbers I want as percents': 'sum'}).add_suffix('_Sum').reset_index()
# Merge the two dataframes
Percents_df = pd.merge(PreGroupby_df, SumGroup_df)
# Divide the two columns
Percents_df["Percent of Final Group"] = Percents_df["Numbers I want as percents"] / Percents_df["Numbers I want as percents_Sum"] * 100
# Drop the extra _Sum column
Percents_df.drop(["Numbers I want as percents_Sum"], inplace=True, axis=1)
此方法大约需要0.15秒
最佳答案方法(使用lambda函数):
state_office = df.groupby(['Group 1','Group 2','Final Group']).agg({'Numbers I want as percents': 'sum'})
state_pcts = state_office.groupby(level=['Group 1','Group 2']).apply(lambda x: 100 * x / float(x.sum()))
此方法大约需要21秒钟才能产生相同的结果。
结果:
Group 1 Group 2 Final Group Numbers I want as percents Percent of Final Group
0 AAAH AQYR RMCH 847 82.312925
1 AAAH AQYR XDCL 182 17.687075
2 AAAH DQGO ALVF 132 12.865497
3 AAAH DQGO AVPH 894 87.134503
4 AAAH OVGH NVOO 650 43.132050
5 AAAH OVGH VKQP 857 56.867950
6 AAAH VNLY HYFW 884 65.336290
7 AAAH VNLY MOYH 469 34.663710
8 AAAH XOOC GIDS 168 23.595506
9 AAAH XOOC HTOY 544 76.404494
#7 楼
我知道这里已经有了很好的答案。尽管如此,我还是想贡献自己的力量,因为我觉得像这样一个基本的,简单的问题,应该有一个一目了然的简短解决方案。
它也应该以一种方式工作,即我可以将百分比添加为新列,而其余数据框保持不变。最后但并非最不重要的一点是,它应该以明显的方式推广到存在多个分组级别的情况(例如,州和国家而不是仅州)。
以下代码段满足了这些条件:
df['sales_ratio'] = df.groupby(['state'])['sales'].transform(lambda x: x/x.sum())
请注意,如果您仍在使用Python 2,则必须用float(x)替换lambda项的分母中的x。
评论
这是IMO的最佳答案。要添加的唯一内容就是* 100,以百分比表示。
– Bouncner
19年4月4日在11:56
@Bouncner:是的,严格来说,您必须乘以100才能得到百分比-或将新变量从“ sales_percentage”重命名为“ sales_ratio”。就个人而言,我更喜欢后者,并据此编辑了答案。感谢您的提及!
–MightyCurious
19年5月5日在7:30
但是,如果您有多个级别,则无法使用。
–艾琳
19年6月13日在9:33
@irene:好点,谢谢!在这种情况下,df.reset_index()。groupby(['state'])['sales']。transform(lambda x:x / x.sum())可能会起作用。还是我忽略了什么?
–MightyCurious
19年6月14日在12:38
这个答案很好。它不涉及创建临时的groupby对象,非常简洁,并且从左到右的逻辑读法非常好。
– C. Braun
19年11月22日在19:56
#8 楼
查找跨列或索引百分比的最优雅的方法是使用pd.crosstab
。样本数据
df = pd.DataFrame({'state': ['CA', 'WA', 'CO', 'AZ'] * 3,
'office_id': list(range(1, 7)) * 2,
'sales': [np.random.randint(100000, 999999) for _ in range(12)]})
输出数据帧是这样的
print(df)
state office_id sales
0 CA 1 764505
1 WA 2 313980
2 CO 3 558645
3 AZ 4 883433
4 CA 5 301244
5 WA 6 752009
6 CO 1 457208
7 AZ 2 259657
8 CA 3 584471
9 WA 4 122358
10 CO 5 721845
11 AZ 6 136928
只需指定要汇总的索引,列和值。 normalize关键字将根据上下文计算跨索引或列的百分比。
result = pd.crosstab(index=df['state'],
columns=df['office_id'],
values=df['sales'],
aggfunc='sum',
normalize='index').applymap('{:.2f}%'.format)
print(result)
office_id 1 2 3 4 5 6
state
AZ 0.00% 0.20% 0.00% 0.69% 0.00% 0.11%
CA 0.46% 0.00% 0.35% 0.00% 0.18% 0.00%
CO 0.26% 0.00% 0.32% 0.00% 0.42% 0.00%
WA 0.00% 0.26% 0.00% 0.10% 0.00% 0.63%
#9 楼
您可以将整个sum
划分为DataFrame
,然后除以state
总数:# Copying setup from Paul H answer
import numpy as np
import pandas as pd
np.random.seed(0)
df = pd.DataFrame({'state': ['CA', 'WA', 'CO', 'AZ'] * 3,
'office_id': list(range(1, 7)) * 2,
'sales': [np.random.randint(100000, 999999) for _ in range(12)]})
# Add a column with the sales divided by state total sales.
df['sales_ratio'] = (df / df.groupby(['state']).transform(sum))['sales']
df
返回
office_id sales state sales_ratio
0 1 405711 CA 0.193319
1 2 535829 WA 0.347072
2 3 217952 CO 0.198743
3 4 252315 AZ 0.192500
4 5 982371 CA 0.468094
5 6 459783 WA 0.297815
6 1 404137 CO 0.368519
7 2 222579 AZ 0.169814
8 3 710581 CA 0.338587
9 4 548242 WA 0.355113
10 5 474564 CO 0.432739
11 6 835831 AZ 0.637686
但是请注意只能这样做是因为
state
以外的所有列都是数字,从而实现了整个DataFrame的求和。例如,如果office_id
是字符,则会出现错误:df.office_id = df.office_id.astype(str)
df['sales_ratio'] = (df / df.groupby(['state']).transform(sum))['sales']
TypeError:/的不支持的操作数类型:'str'和'str'
评论
我进行编辑以注意,这仅在groupby列以外的所有列均为数字时才有效。但这是相当优雅的。有没有办法使其与其他str列一起使用?
– Max Ghenis
17年1月25日在19:18
据我所知:stackoverflow.com/questions/34099684/…
– iggy
17年1月27日在3:22
#10 楼
我认为这可以在1行中达到目的:df.groupby(['state', 'office_id']).sum().transform(lambda x: x/np.sum(x)*100)
评论
我相信它会占用数据集的所有列。在这种情况下,只有一个。如果您有多个,并且想对单个对象执行此操作,则只需在groupby表达式后指定它即可:df.groupby([[state],'office_id'])[[YOUR COLUMN NAME HERE]]。etcetc为了保持其他列不变,只需重新分配特定列
–louisD
18-10-9在18:12
@louisD:我非常喜欢您尝试使其简短的方法。不幸的是,当我尝试按照您的建议重新分配列时,出现两个错误:“ ValueError:缓冲区dtype不匹配,预期为“ Python对象”,但为“ long long””,并且此外(在处理第一个异常期间):“ TypeError:插入的索引与框架索引的索引不兼容”我使用的代码如下:df ['percent'] = df.groupby(['state','office_id'])。sum()。transform(lambda x: x / np.sum(x)* 100)因此,我将发布一个单独的答案来解决此问题。
–MightyCurious
19年4月18日在6:58
#11 楼
我使用的简单方法是在2个groupby之后进行合并,然后进行简单的除法。import numpy as np
import pandas as pd
np.random.seed(0)
df = pd.DataFrame({'state': ['CA', 'WA', 'CO', 'AZ'] * 3,
'office_id': list(range(1, 7)) * 2,
'sales': [np.random.randint(100000, 999999) for _ in range(12)]})
state_office = df.groupby(['state', 'office_id'])['sales'].sum().reset_index()
state = df.groupby(['state'])['sales'].sum().reset_index()
state_office = state_office.merge(state, left_on='state', right_on ='state', how = 'left')
state_office['sales_ratio'] = 100*(state_office['sales_x']/state_office['sales_y'])
state office_id sales_x sales_y sales_ratio
0 AZ 2 222579 1310725 16.981365
1 AZ 4 252315 1310725 19.250033
2 AZ 6 835831 1310725 63.768601
3 CA 1 405711 2098663 19.331879
4 CA 3 710581 2098663 33.858747
5 CA 5 982371 2098663 46.809373
6 CO 1 404137 1096653 36.851857
7 CO 3 217952 1096653 19.874290
8 CO 5 474564 1096653 43.273852
9 WA 2 535829 1543854 34.707233
10 WA 4 548242 1543854 35.511259
11 WA 6 459783 1543854 29.781508
#12 楼
df = pd.DataFrame({'state': ['CA', 'WA', 'CO', 'AZ'] * 3,
'office_id': list(range(1, 7)) * 2,
'sales': [np.random.randint(100000, 999999)
for _ in range(12)]})
grouped = df.groupby(['state', 'office_id'])
100*grouped.sum()/df[["state","sales"]].groupby('state').sum()
返回值:
sales
state office_id
AZ 2 54.587910
4 33.009225
6 12.402865
CA 1 32.046582
3 44.937684
5 23.015735
CO 1 21.099989
3 31.848658
5 47.051353
WA 2 43.882790
4 10.265275
6 45.851935
#13 楼
作为一个也在学习熊猫的人,我发现其他答案有些隐含,因为熊猫将大部分工作隐藏在幕后。即通过自动匹配列和索引名称来实现操作的方式。此代码应等效于@ exp1orer接受的答案的逐步版本。对于
df
,我将其称为别名state_office_sales
: sales
state office_id
AZ 2 839507
4 373917
6 347225
CA 1 798585
3 890850
5 454423
CO 1 819975
3 202969
5 614011
WA 2 163942
4 369858
6 959285
state_total_sales
是state_office_sales
,按index level 0
(最左侧)中的总和分组。In: state_total_sales = df.groupby(level=0).sum()
state_total_sales
Out:
sales
state
AZ 2448009
CA 2832270
CO 1495486
WA 595859
,因为这两个数据帧共享一个索引名和一个列名pandas。可以通过共享索引找到合适的位置,例如:
In: state_office_sales / state_total_sales
Out:
sales
state office_id
AZ 2 0.448640
4 0.125865
6 0.425496
CA 1 0.288022
3 0.322169
5 0.389809
CO 1 0.206684
3 0.357891
5 0.435425
WA 2 0.321689
4 0.346325
6 0.331986
为了更好地说明这一点,这里有一个
XX
的部分总计,没有等效值。大熊猫将根据索引和列名匹配位置,在此位置,没有大熊猫会忽略它:In: partial_total = pd.DataFrame(
data = {'sales' : [2448009, 595859, 99999]},
index = ['AZ', 'WA', 'XX' ]
)
partial_total.index.name = 'state'
Out:
sales
state
AZ 2448009
WA 595859
XX 99999
In: state_office_sales / partial_total
Out:
sales
state office_id
AZ 2 0.448640
4 0.125865
6 0.425496
CA 1 NaN
3 NaN
5 NaN
CO 1 NaN
3 NaN
5 NaN
WA 2 0.321689
4 0.346325
6 0.331986
没有共享索引或列时非常清楚。此处
missing_index_totals
等于state_total_sales
,只是它没有索引名。 In: missing_index_totals = state_total_sales.rename_axis("")
missing_index_totals
Out:
sales
AZ 2448009
CA 2832270
CO 1495486
WA 595859
In: state_office_sales / missing_index_totals
Out: ValueError: cannot join with no overlapping index names
#14 楼
单行解决方案:df.join(
df.groupby('state').agg(state_total=('sales', 'sum')),
on='state'
).eval('sales / state_total')
这将返回一系列按办公室使用的比率-可以单独使用或分配给原始数据框。
评论
df ['sales'] / df.groupby('state')['sales']。transform('sum')似乎是最明确的答案。