这显然很简单,但作为一个麻木的新手,我被卡住了。

我有一个CSV文件,其中包含3列,州,办公室ID,以及该办公室的销售。

我想计算给定州中每个办事处的销售额百分比(每个州所有百分比的总和为100%)。

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']).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

我似乎不知道如何“达到”集团的州级,通过合计整个州的销售来计算分数。


当前回答

为了简洁起见,我使用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

这似乎比其他答案的性能稍好(对我来说,大约0.08秒,是Radical回答速度的两倍)。

其他回答

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

(此解决方案的灵感来自这篇文章https://pbpython.com/pandas_transform.html)

我发现下面的解决方案是最简单的(可能是最快的)使用转换:

类的简化版本 数据转换时,可以返回一些转换后的完整版本 数据重组。对于这样的转换,输出是相同的 形状作为输入。

所以使用变换,解决方案是一行:

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

一行的解决方案:

df.join(
    df.groupby('state').agg(state_total=('sales', 'sum')),
    on='state'
).eval('sales / state_total')

这将返回一系列每个办公室的比率——可以单独使用,也可以分配给原始数据框架。

我认为这需要基准测试。使用OP的原始数据帧,

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)]
})

第0个癌症

新熊猫变形看起来快多了。

df['sales'] / df.groupby('state')['sales'].transform('sum')

每回路1.32 ms±352µs (7次运行的平均值±标准度,每次100次循环)

安迪·海登

正如他的回答所评论的那样,安迪充分利用了向量化和熊猫索引。

c = df.groupby(['state', 'office_id'])['sales'].sum().rename("count")
c / c.groupby(level=0).sum()

每回路3.42 ms±16.7µs (7次运行的平均值±标准度,每次100次循环)


保罗·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

这是最慢的答案,因为它为级别0中的每个x计算x.sum()。

对我来说,这仍然是一个有用的答案,尽管不是目前的形式。对于小型数据集的快速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次循环)


所以没有人会关心小数据集上的6毫秒。然而,这是3倍的速度,在一个更大的数据集上,这将产生巨大的差异。

加上上面的代码,我们创建了一个形状为(12,000,000,3)的DataFrame,其中包含14412个状态类别和600个office_ids,

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
})

使用癌症的,

0.791 s±19.4 ms /循环 (7次运行的平均值±标准值,每个循环1次)

使用安迪的,

2 s±10.4 ms每循环 (7次运行的平均值±标准值,每个循环1次)

和exp1orer

每回路19 s±77.1 ms (7次运行的平均值±标准值,每个循环1次)

所以现在我们看到,用Andy的x10加速了大型、高基数数据集,而用Caner的x20加快了令人印象深刻的速度。


如果你要UV这个答案,一定要UV这三个答案!!

编辑:添加了Caner基准

我认为这一行就可以做到:

df.groupby(['state', 'office_id']).sum().transform(lambda x: x/np.sum(x)*100)