docs 展示了如何使用以输出列名作为键的 dict 一次对 groupby 对象应用多个函数:
In [563]: grouped['D'].agg({'result1' : np.sum,
.....: 'result2' : np.mean})
.....:
Out[563]:
result2 result1
A
bar -0.579846 -1.739537
foo -0.280588 -1.402938
但是,这仅适用于 Series groupby 对象。并且当 dict 类似地传递给 groupby DataFrame 时,它期望键是函数将应用于的列名。
我想要做的是将多个功能应用于多个列(但某些列将被多次操作)。此外,某些函数将依赖于 groupby 对象中的其他列(如 sumif 函数)。我目前的解决方案是逐列进行,并执行类似于上面的代码的操作,将 lambdas 用于依赖于其他行的函数。但这需要很长时间,(我认为遍历 groupby 对象需要很长时间)。我必须对其进行更改,以便在一次运行中遍历整个 groupby 对象,但我想知道 pandas 中是否有内置的方法可以稍微干净地做到这一点。
例如,我尝试过类似的东西
grouped.agg({'C_sum' : lambda x: x['C'].sum(),
'C_std': lambda x: x['C'].std(),
'D_sum' : lambda x: x['D'].sum()},
'D_sumifC3': lambda x: x['D'][x['C'] == 3].sum(), ...)
但正如预期的那样,我得到了一个 KeyError(因为如果从 DataFrame 调用 agg
,则键必须是一列)。
有没有内置的方法可以做我想做的事情,或者有可能添加这个功能,还是我只需要手动遍历 groupby?
当前接受的答案的后半部分已过时,并且有两次弃用。首先也是最重要的,您不能再将字典字典传递给 agg
groupby 方法。其次,永远不要使用 .ix
。
如果您希望同时使用两个单独的列,我建议使用 apply
方法,该方法隐式地将 DataFrame 传递给应用的函数。让我们使用与上面类似的数据框
df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]
df
a b c d group
0 0.418500 0.030955 0.874869 0.145641 0
1 0.446069 0.901153 0.095052 0.487040 0
2 0.843026 0.936169 0.926090 0.041722 1
3 0.635846 0.439175 0.828787 0.714123 1
从列名映射到聚合函数的字典仍然是执行聚合的完美方式。
df.groupby('group').agg({'a':['sum', 'max'],
'b':'mean',
'c':'sum',
'd': lambda x: x.max() - x.min()})
a b c d
sum max mean sum <lambda>
group
0 0.864569 0.446069 0.466054 0.969921 0.341399
1 1.478872 0.843026 0.687672 1.754877 0.672401
如果您不喜欢那个丑陋的 lambda 列名称,您可以使用普通函数并为特殊的 __name__
属性提供自定义名称,如下所示:
def max_min(x):
return x.max() - x.min()
max_min.__name__ = 'Max minus Min'
df.groupby('group').agg({'a':['sum', 'max'],
'b':'mean',
'c':'sum',
'd': max_min})
a b c d
sum max mean sum Max minus Min
group
0 0.864569 0.446069 0.466054 0.969921 0.341399
1 1.478872 0.843026 0.687672 1.754877 0.672401
使用应用并返回一个系列
现在,如果您有多个列需要一起交互,那么您不能使用 agg
,它会隐式地将 Series 传递给聚合函数。当使用 apply
时,整个组作为 DataFrame 被传递到函数中。
我建议制作一个返回所有聚合系列的自定义函数。使用 Series 索引作为新列的标签:
def f(x):
d = {}
d['a_sum'] = x['a'].sum()
d['a_max'] = x['a'].max()
d['b_mean'] = x['b'].mean()
d['c_d_prodsum'] = (x['c'] * x['d']).sum()
return pd.Series(d, index=['a_sum', 'a_max', 'b_mean', 'c_d_prodsum'])
df.groupby('group').apply(f)
a_sum a_max b_mean c_d_prodsum
group
0 0.864569 0.446069 0.466054 0.173711
1 1.478872 0.843026 0.687672 0.630494
如果您喜欢 MultiIndexes,您仍然可以返回一个带有这样的系列:
def f_mi(x):
d = []
d.append(x['a'].sum())
d.append(x['a'].max())
d.append(x['b'].mean())
d.append((x['c'] * x['d']).sum())
return pd.Series(d, index=[['a', 'a', 'b', 'c_d'],
['sum', 'max', 'mean', 'prodsum']])
df.groupby('group').apply(f_mi)
a b c_d
sum max mean prodsum
group
0 0.864569 0.446069 0.466054 0.173711
1 1.478872 0.843026 0.687672 0.630494
对于第一部分,您可以传递键的列名字典和值的函数列表:
In [28]: df
Out[28]:
A B C D E GRP
0 0.395670 0.219560 0.600644 0.613445 0.242893 0
1 0.323911 0.464584 0.107215 0.204072 0.927325 0
2 0.321358 0.076037 0.166946 0.439661 0.914612 1
3 0.133466 0.447946 0.014815 0.130781 0.268290 1
In [26]: f = {'A':['sum','mean'], 'B':['prod']}
In [27]: df.groupby('GRP').agg(f)
Out[27]:
A B
sum mean prod
GRP
0 0.719580 0.359790 0.102004
1 0.454824 0.227412 0.034060
更新 1:
因为聚合函数适用于 Series,所以对其他列名的引用会丢失。为了解决这个问题,您可以引用完整的数据帧并使用 lambda 函数中的组索引对其进行索引。
这是一个hacky解决方法:
In [67]: f = {'A':['sum','mean'], 'B':['prod'], 'D': lambda g: df.loc[g.index].E.sum()}
In [69]: df.groupby('GRP').agg(f)
Out[69]:
A B D
sum mean prod <lambda>
GRP
0 0.719580 0.359790 0.102004 1.170219
1 0.454824 0.227412 0.034060 1.182901
在这里,结果“D”列由总和的“E”值组成。
更新 2:
这是一种我认为可以满足您要求的方法。首先制作一个自定义的 lambda 函数。下面,g 引用了该组。聚合时,g 将是一个系列。将 g.index
传递给 df.ix[]
从 df 中选择当前组。然后我测试列 C 是否小于 0.5。返回的布尔系列被传递给 g[]
,它只选择那些符合条件的行。
In [95]: cust = lambda g: g[df.loc[g.index]['C'] < 0.5].sum()
In [96]: f = {'A':['sum','mean'], 'B':['prod'], 'D': {'my name': cust}}
In [97]: df.groupby('GRP').agg(f)
Out[97]:
A B D
sum mean prod my name
GRP
0 0.719580 0.359790 0.102004 0.204072
1 0.454824 0.227412 0.034060 0.570441
{funcname: func}
的字典作为值而不是列表传递来保留我的自定义名称。但在任何一种情况下,我都无法传递使用其他列的 lambda
(如上面的 lambda x: x['D'][x['C'] < 3].sum()
:“KeyError: 'D'”)。知道这是否可能吗?
KeyError: 'D'
df['A'].ix[g.index][df['C'] < 0].sum()
一起工作。不过,这开始变得非常混乱——我认为为了可读性,手动循环可能更可取,而且我不确定有没有办法在 agg
参数中给它我的首选名称(而不是 <lambda>
)。我希望有人可能知道更直接的方法...
{'D': {'my name':lambda function}}
传递一个字典,它将使内部字典键成为列名。
Pandas >= 0.25.0,命名聚合
由于 pandas 版本 0.25.0
或更高版本,我们正在远离基于字典的聚合和重命名,而转向接受 tuple
的 named aggregations。现在我们可以同时聚合 + 重命名为信息量更大的列名:
例子:
df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]
a b c d group
0 0.521279 0.914988 0.054057 0.125668 0
1 0.426058 0.828890 0.784093 0.446211 0
2 0.363136 0.843751 0.184967 0.467351 1
3 0.241012 0.470053 0.358018 0.525032 1
使用命名聚合应用 GroupBy.agg
:
df.groupby('group').agg(
a_sum=('a', 'sum'),
a_mean=('a', 'mean'),
b_mean=('b', 'mean'),
c_sum=('c', 'sum'),
d_range=('d', lambda x: x.max() - x.min())
)
a_sum a_mean b_mean c_sum d_range
group
0 0.947337 0.473668 0.871939 0.838150 0.320543
1 0.604149 0.302074 0.656902 0.542985 0.057681
作为 Ted Petrou 回答的替代方案(主要是美学),我发现我更喜欢稍微紧凑的列表。请不要考虑接受它,这只是对 Ted 答案的更详细的评论,以及代码/数据。 Python/pandas 不是我的第一个/最好的,但我发现这读起来很好:
df.groupby('group') \
.apply(lambda x: pd.Series({
'a_sum' : x['a'].sum(),
'a_max' : x['a'].max(),
'b_mean' : x['b'].mean(),
'c_d_prodsum' : (x['c'] * x['d']).sum()
})
)
a_sum a_max b_mean c_d_prodsum
group
0 0.530559 0.374540 0.553354 0.488525
1 1.433558 0.832443 0.460206 0.053313
我发现它更让人联想到 dplyr
管道和 data.table
链式命令。并不是说它们更好,只是对我来说更熟悉。 (我当然认识到对这些类型的操作使用更正式的 def
函数的力量和偏好。这只是一种替代方法,不一定更好。)
我以与 Ted 相同的方式生成数据,我将添加一个可重复性的种子。
import numpy as np
np.random.seed(42)
df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]
df
a b c d group
0 0.374540 0.950714 0.731994 0.598658 0
1 0.156019 0.155995 0.058084 0.866176 0
2 0.601115 0.708073 0.020584 0.969910 1
3 0.832443 0.212339 0.181825 0.183405 1
pd.Series
的字典的键。例如,('a', 'sum') : x['a'].sum()
而不是 'a_sum' : x['a'].sum()
为了通过控制输出列名来支持特定于列的聚合,pandas 接受 GroupBy.agg() 中的特殊语法,称为“命名聚合”,其中
关键字是输出列名
这些值是元组,其第一个元素是要选择的列,第二个元素是要应用于该列的聚合。 Pandas 为 pandas.NamedAgg 命名元组提供了 ['column', 'aggfunc'] 字段,以便更清楚地了解参数是什么。像往常一样,聚合可以是可调用的或字符串别名。
>>> animals = pd.DataFrame({
... 'kind': ['cat', 'dog', 'cat', 'dog'],
... 'height': [9.1, 6.0, 9.5, 34.0],
... 'weight': [7.9, 7.5, 9.9, 198.0]
... })
>>> print(animals)
kind height weight
0 cat 9.1 7.9
1 dog 6.0 7.5
2 cat 9.5 9.9
3 dog 34.0 198.0
>>> print(
... animals
... .groupby('kind')
... .agg(
... min_height=pd.NamedAgg(column='height', aggfunc='min'),
... max_height=pd.NamedAgg(column='height', aggfunc='max'),
... average_weight=pd.NamedAgg(column='weight', aggfunc=np.mean),
... )
... )
min_height max_height average_weight
kind
cat 9.1 9.5 8.90
dog 6.0 34.0 102.75
pandas.NamedAgg 只是一个命名元组。也允许使用普通元组。
>>> print(
... animals
... .groupby('kind')
... .agg(
... min_height=('height', 'min'),
... max_height=('height', 'max'),
... average_weight=('weight', np.mean),
... )
... )
min_height max_height average_weight
kind
cat 9.1 9.5 8.90
dog 6.0 34.0 102.75
其他关键字参数不会传递给聚合函数。只有成对的 (column, aggfunc) 应该作为 **kwargs 传递。如果您的聚合函数需要其他参数,请使用 functools.partial() 部分应用它们。
命名聚合也适用于 Series groupby 聚合。在这种情况下,没有列选择,因此值只是函数。
>>> print(
... animals
... .groupby('kind')
... .height
... .agg(
... min_height='min',
... max_height='max',
... )
... )
min_height max_height
kind
cat 9.1 9.5
dog 6.0 34.0
agg_dict = { "min_height": pd.NamedAgg(column='height', aggfunc='min'), "max_height": pd.NamedAgg(column='height', aggfunc='max'), "average_weight": pd.NamedAgg(column='weight', aggfunc=np.mean) } animals.groupby("kind").agg(**agg_dict)
这是使用命名聚合的“exans”答案的一个转折。它是相同的,但参数解包允许您仍然将字典传递给 agg 函数。
命名的 aggs 是一个不错的功能,但乍一看似乎很难以编程方式编写,因为它们使用关键字,但实际上参数/关键字解包很简单。
animals = pd.DataFrame({'kind': ['cat', 'dog', 'cat', 'dog'],
'height': [9.1, 6.0, 9.5, 34.0],
'weight': [7.9, 7.5, 9.9, 198.0]})
agg_dict = {
"min_height": pd.NamedAgg(column='height', aggfunc='min'),
"max_height": pd.NamedAgg(column='height', aggfunc='max'),
"average_weight": pd.NamedAgg(column='weight', aggfunc=np.mean)
}
animals.groupby("kind").agg(**agg_dict)
结果
min_height max_height average_weight
kind
cat 9.1 9.5 8.90
dog 6.0 34.0 102.75
泰德的回答令人惊叹。我最终使用了一个较小的版本,以防有人感兴趣。当您正在寻找一个依赖于多列值的聚合时很有用:
创建一个数据框
df = pd.DataFrame({
'a': [1, 2, 3, 4, 5, 6],
'b': [1, 1, 0, 1, 1, 0],
'c': ['x', 'x', 'y', 'y', 'z', 'z']
})
print(df)
a b c
0 1 1 x
1 2 1 x
2 3 0 y
3 4 1 y
4 5 1 z
5 6 0 z
使用 apply 进行分组和聚合(使用多个列)
print(
df
.groupby('c')
.apply(lambda x: x['a'][(x['a'] > 1) & (x['b'] == 1)]
.mean()
)
c
x 2.0
y 4.0
z 5.0
使用聚合进行分组和聚合(使用多个列)
我喜欢这种方法,因为我仍然可以使用聚合。也许人们会告诉我为什么在对组进行聚合时需要 apply 来获取多个列。
现在看起来很明显,但是只要您不直接在 groupby 之后选择感兴趣的列,您就可以从聚合函数中访问数据框的所有列。
只能访问选定的列
df.groupby('c')['a'].aggregate(lambda x: x[x > 1].mean())
访问所有列,因为选择毕竟是魔法
df.groupby('c').aggregate(lambda x: x[(x['a'] > 1) & (x['b'] == 1)].mean())['a']
或类似的
df.groupby('c').aggregate(lambda x: x['a'][(x['a'] > 1) & (x['b'] == 1)].mean())
我希望这有帮助。