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

我有一个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

我认为这需要基准测试。使用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['sales_ratio'] = df.groupby(['state'])['sales'].transform(lambda x: x/x.sum())

注意,如果你仍在使用Python 2,你必须用float(x)替换lambda项分母中的x。

df.groupby('state').office_id.value_counts(normalize = True)

我使用value_counts方法,但它返回的百分比像0.70和0.30,而不是像70和30。

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

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