使用pandas按组汇总唯一值

6

I got a dataframe like this:

data = {
    'YEAR' :    [2018,2018,2017,2018,2018,2018],
    'SEASON':   ['SPRING', 'SPRING', 'WINTER', 'SPRING', 'SPRING', 'SPRING'],
    'CODE':     ['A', 'A', 'A', 'B', 'C', 'D'],
    'BUDGET':   [500,200,300,4000,700,0],
    'QUANTITY': [1000,1000,1000,2000,300,4000]
}

df = pd.DataFrame(data)

'''
   BUDGET CODE  QUANTITY  SEASON  YEAR
0     500    A      1000  SPRING  2018
1     200    A      1000  SPRING  2018
2     300    A      1000  WINTER  2017
3    4000    B      2000  SPRING  2018
4     700    C       300  SPRING  2018
5       0    D      4000  SPRING  2018
'''

对于每一个代码,我得到了它的正确预算数量,但是不幸的是,在数量列中,我得到了每个[年份、季节]中该代码的总数量。
我正在编写一个函数,以不同的层级进行数据框聚合,例如,我将一个列表作为输入传递给该函数。
my_list = [
  ['YEAR']
  ['YEAR', 'SEASON']
]

该函数将会按照每个子列表分组输出一系列数据框。

问题在于,我可以使用pd.Series.nunique 聚合 CODE,并且可以用sum函数求和BUDGET列。但是,如果我也对QUANTITY列进行求和,则会累加超过我的要求。我需要的是一种按独立的YEARSEASONCODE进行的sumUniques函数。

def sumUniques(x):
    return '???'

print(df.groupby(['YEAR', 'SEASON']).agg({
    'CODE': pd.Series.nunique,
    'BUDGET': sum,
    'QUANTITY' : sumUniques
}))

'''
             CODE  BUDGET QUANTITY
YEAR SEASON                       
2017 WINTER     1     300      ???
2018 SPRING     4    5400      ???


--> EXPECTED RESULT:
             CODE  BUDGET QUANTITY
YEAR SEASON                       
2017 WINTER     1     300      1000
2018 SPRING     4    5400      7300

'''

我在思考如何最好地实现这一目标,然后我发现了Zero的答案:“Pandas:将列值求和到唯一值”,我已经尝试过它,但似乎我要么没有正确应用它,要么它不适用于我的问题,因为它引发了一个键错误

print(df.groupby(['YEAR', 'SEASON']).agg({
    'CODE': pd.Series.nunique,
    'BUDGET': sum,
    'QUANTITY' : lambda x: x.groupby('CODE').QUANTITY.first().sum()
}))

'''
KeyError: 'CODE'
'''

我想知道最好的方法是什么,希望这对其他人也有所帮助!


如果您的问题得到解答,请接受帮助您的答案。谢谢。 - cs95
我添加了一个新的解决方案和时间记录,你能用你的真实数据检查一下吗? - jezrael
2个回答

16

根据您的评论,需要稍微更复杂的步骤才能得到您的结果。QUANTITY 的解决方法与 jezrael 的答案中所使用的 apply 非常相似,因此感谢他。

df

   BUDGET CODE  QUANTITY  SEASON  YEAR
0     500    A      1000  SPRING  2018
1     200    A      1000  SPRING  2018
2     300    A      1000  WINTER  2017
3    4000    B      2000  SPRING  2018
4     700    C       300  SPRING  2018
5       0    D      4000  SPRING  2018
6     500    E      1000  SPRING  2018

f = {
        'CODE' : 'nunique', 
        'BUDGET' : 'sum'
}

g = df.groupby(['YEAR', 'SEASON'])
v1 = g.agg(f)
v2 = g.agg(lambda x: x.drop_duplicates('CODE', keep='first').QUANTITY.sum())

df = pd.concat([v1, v2.to_frame('QUANTITY')], 1)

df

             CODE  BUDGET  QUANTITY
YEAR SEASON                        
2017 WINTER     1     300      1000
2018 SPRING     5    5900      8300

我认为你2018年春季分组数量应该是7300而不是8300。 - Tyler Russell
不用理会。我看到你已经更改了原始数据框。 - Tyler Russell

10

使用 groupby + apply 与自定义函数:

def f(x):
   a = x['CODE'].nunique()
   b =  x['BUDGET'].sum()
   c = x.drop_duplicates('CODE').QUANTITY.sum()
   #Or:
   #c = x.groupby('CODE').QUANTITY.first().sum()
   return pd.Series([a,b,c], index=['CODE','BUDGET','QUANTITY'])


print (df.groupby(['YEAR', 'SEASON']).apply(f) )

             CODE  BUDGET  QUANTITY
YEAR SEASON                        
2017 WINTER     1     300      1000
2018 SPRING     4    5400      7300

另一种解决方案:

df1 = df.groupby(['YEAR', 'SEASON']).agg({ 'CODE' : 'nunique', 'BUDGET' : 'sum'})
s = df.drop_duplicates(['YEAR', 'SEASON','CODE']).groupby(['YEAR', 'SEASON'])['QUANTITY'].sum()

df = df1.join(s.rename('QUANTITY'))
print (df)
             BUDGET  CODE  QUANTITY
YEAR SEASON                        
2017 WINTER     300     1      1000
2018 SPRING    5900     5      8300

时间:

np.random.seed(123)
N = 1000000
a = ['WINTER', 'AUTUMN', 'SUMMER', 'SPRING']
b = list('ABCDEFGHIJKL')
c = range(1990, 2018)

data = {
    'YEAR' :    np.random.choice(c, N),
    'SEASON':   np.random.choice(a, N),
    'CODE':     np.random.choice(b, N),
    'BUDGET':    np.random.randint(1000,size= N),
    'QUANTITY': np.random.randint(1000,size= N)
}

df = pd.DataFrame(data)
print (df.head())
   BUDGET CODE  QUANTITY  SEASON  YEAR
0      92    L        95  SUMMER  2003
1     961    A       696  SPRING  1992
2     481    G       351  WINTER  1992
3     296    A        51  SPRING  1996
4     896    G        58  AUTUMN  2007

def cols(df):
    f = {
        'CODE' : 'nunique', 
        'BUDGET' : 'sum'
        }

    g = df.groupby(['YEAR', 'SEASON'])
    v1 = g.agg(f)
    v2 = g.agg(lambda x: x.drop_duplicates('CODE', keep='first').QUANTITY.sum())

    return pd.concat([v1, v2.to_frame('QUANTITY')], 1)

def jez2(df):
    df1 = df.groupby(['YEAR', 'SEASON']).agg({ 'CODE' : 'nunique', 'BUDGET' : 'sum'})
    s = df.drop_duplicates(['YEAR', 'SEASON','CODE']).groupby(['YEAR', 'SEASON'])['QUANTITY'].sum()
    return df1.join(s.rename('QUANTITY'))


def f(x):
   a = x['CODE'].nunique()
   b =  x['BUDGET'].sum()
   c = x.groupby('CODE').QUANTITY.first().sum()
   return pd.Series([a,b,c], index=['CODE','BUDGET','QUANTITY'])


print (df.groupby(['YEAR', 'SEASON']).apply(f))

print (jez2(df))
print (cols(df))

In [46]: %timeit (df.groupby(['YEAR', 'SEASON']).apply(f))
1 loop, best of 3: 674 ms per loop

In [47]: %timeit (jez2(df))
1 loop, best of 3: 1.31 s per loop

In [48]: %timeit (cols(df))
1 loop, best of 3: 1.88 s per loop

你的输出完全相反。请检查代码和预算列。 - cs95
我也不理解。Stack Overflow很奇怪,我也因此无缘无故地被踩了。我想可能是有人反悔了,但我的回答里仍然有一个踩的记录。无论如何,这种事情总是会发生。 - cs95
我添加了一些计时,似乎 apply 是最快的。但是也许对于真实的数据来说就有所不同了。 - jezrael
OP已经计时我们的答案了,我的大约花了2秒钟,而你的则花费了超过5秒钟。 - cs95
是的,但我用agg添加了另一种解决方案,我认为OP可以测试一下,因为逻辑不同,所以速度可能会更快。 - jezrael

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接