这显然很简单,但作为一个笨拙的新人我会陷入困境.
我有一个CSV文件,其中包含3个列,State,Office ID和该办公室的Sales.
我想计算某个州的每个办公室的销售百分比(每个州的所有百分比总和为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'})
Run Code Online (Sandbox Code Playgroud)
返回:
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
Run Code Online (Sandbox Code Playgroud)
我似乎无法弄清楚如何"高达"的state水平groupby与总起来sales对整个state计算分数.
exp*_*rer 172
保罗轰的答案是正确的,你将不得不作出第二个groupby对象,但你可以在一个简单的方式计算百分比-只是groupby在state_office和划分sales其总和列.复制保罗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()))
Run Code Online (Sandbox Code Playgroud)
返回:
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
Run Code Online (Sandbox Code Playgroud)
Pau*_*l H 44
您需要创建第二个groupby对象按状态分组,然后使用该div方法:
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'})
state = df.groupby(['state']).agg({'sales': 'sum'})
state_office.div(state, level='state') * 100
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
Run Code Online (Sandbox Code Playgroud)
的level='state'在kwarg div告诉大熊猫广播/在连接上的值dataframes基state索引的水平.
Can*_*ner 36
(这个解决方案的灵感来自这篇文章https://pbpython.com/pandas_transform.html)
我发现以下解决方案是最简单的(可能也是最快的)使用transformation:
转换:虽然聚合必须返回数据的简化版本,但转换可以返回完整数据的一些转换版本以重新组合。对于这样的转换,输出与输入的形状相同。
因此,使用transformation,解决方案是 1-liner:
df['%'] = 100 * df['sales'] / df.groupby('state')['sales'].transform('sum')
Run Code Online (Sandbox Code Playgroud)
如果你打印:
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
Run Code Online (Sandbox Code Playgroud)
And*_*den 26
为简明起见,我将使用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
Run Code Online (Sandbox Code Playgroud)
对于多个组,您必须使用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
Run Code Online (Sandbox Code Playgroud)
这似乎比其他答案稍微高一点(仅比Radical的答案速度低两倍,对我来说〜0.08s).
Lit*_*les 17
我认为这需要基准测试.使用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)]
})
Run Code Online (Sandbox Code Playgroud)
正如他对答案的评论,Andy充分利用了矢量化和熊猫索引.
c = df.groupby(['state', 'office_id'])['sales'].sum().rename("count")
c / c.groupby(level=0).sum()
Run Code Online (Sandbox Code Playgroud)
每回路3.42 ms ±16.7μs
(平均值±标准偏差,7次运行,每次100次循环)
state_office = df.groupby(['state', 'office_id']).agg({'sales': 'sum'})
state = df.groupby(['state']).agg({'sales': 'sum'})
state_office.div(state, level='state') * 100
Run Code Online (Sandbox Code Playgroud)
每循环4.66 ms ±24.4μs
(平均值±标准偏差,7次运行,每次100次循环)
这是最慢的答案,因为它计算0级中的x.sum()每一个x.
对我来说,这仍然是一个有用的答案,虽然不是目前的形式.对于较小数据集的快速EDA,apply允许您使用方法链将其写入一行.因此,我们无需决定变量的名称,这对于您最有价值的资源(您的大脑!!)实际上计算成本非常高.
这是修改,
(
df.groupby(['state', 'office_id'])
.agg({'sales': 'sum'})
.groupby(level=0)
.apply(lambda x: 100 * x / float(x.sum()))
)
Run Code Online (Sandbox Code Playgroud)
每回路10.6 ms ±81.5μs
(平均值±标准偏差,7次运行,每次100次循环)
所以没有人关心小数据集上的6ms.然而,这是3倍的加速,并且在具有高基数组的更大数据集上,这将产生巨大的差异.
添加到上面的代码,我们创建一个具有14412状态类别和600个office_ids的形状(12,000,000,3)的DataFrame,
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
})
Run Code Online (Sandbox Code Playgroud)
使用Andy的,
每回路2 s ±10.4 ms
(平均值±标准偏差,7次运行,每次1次循环)
和exp1orer
每回路19 s ±77.1 ms
(平均值±标准偏差,7次运行,每次1次循环)
所以现在我们看到x10在大型高基数数据集上加速.
如果你用紫外线这一个,一定要把这三个答案给紫外线!
我知道这是一个老问题,但对于具有大量唯一组(可能是因为lambda)的数据集,exp1orer的答案非常慢.我建立了他们的答案,把它变成一个数组计算所以现在它超级快!以下是示例代码:
使用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})
Run Code Online (Sandbox Code Playgroud)
分组后,它看起来像:
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]
Run Code Online (Sandbox Code Playgroud)
查找百分比的数组方法:
# 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)
Run Code Online (Sandbox Code Playgroud)
该方法大约需要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()))
Run Code Online (Sandbox Code Playgroud)
该方法需要约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
Run Code Online (Sandbox Code Playgroud)
查找跨列或索引的百分比的最优雅方法是使用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)]})
Run Code Online (Sandbox Code Playgroud)
输出数据帧是这样的
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
Run Code Online (Sandbox Code Playgroud)
只需指定要聚合的索引、列和值。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%
Run Code Online (Sandbox Code Playgroud)
您可以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
Run Code Online (Sandbox Code Playgroud)
退货
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
Run Code Online (Sandbox Code Playgroud)
但请注意,这仅适用于除state数字以外的所有列,从而可以对整个 DataFrame 求和。例如,如果office_id改为字符,则会出现错误:
df.office_id = df.office_id.astype(str)
df['sales_ratio'] = (df / df.groupby(['state']).transform(sum))['sales']
Run Code Online (Sandbox Code Playgroud)
类型错误:不支持 / 的操作数类型:'str' 和 'str'
我意识到这里已经有了很好的答案。
尽管如此,我还是想贡献自己的力量,因为我对这样一个基本的,简单的问题感到,应该有一眼就能理解的简短解决方案。
它也应该以可以将百分比添加为新列的方式工作,而其余数据框保持不变。最后但并非最不重要的一点是,它应该以明显的方式推广到存在多个分组级别的情况(例如,州和国家而不是仅州)。
以下代码段满足这些条件:
df['sales_ratio'] = df.groupby(['state'])['sales'].transform(lambda x: x/x.sum())
Run Code Online (Sandbox Code Playgroud)
请注意,如果您仍在使用Python 2,则必须用float(x)替换lambda项的分母中的x。
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)]})
df.groupby(['state', 'office_id'])['sales'].sum().rename("weightage").groupby(level = 0).transform(lambda x: x/x.sum())
df.reset_index()
Run Code Online (Sandbox Code Playgroud)
输出:
state office_id weightage
0 AZ 2 0.169814
1 AZ 4 0.192500
2 AZ 6 0.637686
3 CA 1 0.193319
4 CA 3 0.338587
5 CA 5 0.468094
6 CO 1 0.368519
7 CO 3 0.198743
8 CO 5 0.432739
9 WA 2 0.347072
10 WA 4 0.355113
11 WA 6 0.297815
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
161002 次 |
| 最近记录: |