使用 pandas 按组对唯一值求和
Sum unique values by group with pandas
我得到了这样一个数据框:
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
'''
对于每个 CODE 我得到了正确的 BUDGET 数量,但是 不幸的是 QUANTITY 列我得到了每个 [Year, Season] 内该代码的总数量。
我正在开发一个函数,用于在不同级别聚合我的数据框作为输入:例如,我给函数一个列表,如
my_list = [
['YEAR']
['YEAR', 'SEASON']
]
并且该函数将输出一系列按每个子列表分组的数据帧。
这里的问题是我可以将 CODE 与 pd.Series.nunique 相加,然后我可以 sum BUDGET 列,但是如果我 sum 还有 QUANTITY 列,我显然会总结比我想要的更多。我需要的是 uniques YEAR、SEASON、[=35= 的某种 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
'''
我在问自己哪种方法可能是实现此目标的最佳方法,然后我想到了 :我已经尝试过了,看来要么我没有正确应用它,要么不适用于我的问题,因为它引发了 key error:
print(df.groupby(['YEAR', 'SEASON']).agg({
'CODE': pd.Series.nunique,
'BUDGET': sum,
'QUANTITY' : lambda x: x.groupby('CODE').QUANTITY.first().sum()
}))
'''
KeyError: 'CODE'
'''
我想知道完成这项工作的最佳方法是什么,希望这对其他人也有帮助!
根据您的意见,需要稍微复杂一些的程序才能得到您的结果。 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
使用 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
我得到了这样一个数据框:
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
'''
对于每个 CODE 我得到了正确的 BUDGET 数量,但是 不幸的是 QUANTITY 列我得到了每个 [Year, Season] 内该代码的总数量。
我正在开发一个函数,用于在不同级别聚合我的数据框作为输入:例如,我给函数一个列表,如
my_list = [
['YEAR']
['YEAR', 'SEASON']
]
并且该函数将输出一系列按每个子列表分组的数据帧。
这里的问题是我可以将 CODE 与 pd.Series.nunique 相加,然后我可以 sum BUDGET 列,但是如果我 sum 还有 QUANTITY 列,我显然会总结比我想要的更多。我需要的是 uniques YEAR、SEASON、[=35= 的某种 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
'''
我在问自己哪种方法可能是实现此目标的最佳方法,然后我想到了
print(df.groupby(['YEAR', 'SEASON']).agg({
'CODE': pd.Series.nunique,
'BUDGET': sum,
'QUANTITY' : lambda x: x.groupby('CODE').QUANTITY.first().sum()
}))
'''
KeyError: 'CODE'
'''
我想知道完成这项工作的最佳方法是什么,希望这对其他人也有帮助!
根据您的意见,需要稍微复杂一些的程序才能得到您的结果。 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
使用 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