将多个函数应用于多个 groupby 列

2024-12-02 08:41:00
admin
原创
169
摘要:问题描述:文档展示了如何使用以输出列名作为键的字典一次在 groupby 对象上应用多个函数:In [563]: grouped['D'].agg({'result1' : np.sum, .....: 'result2' : np.mean}) .....: ...

问题描述:

文档展示了如何使用以输出列名作为键的字典一次在 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 对象。当将字典类似地传递给 groupby DataFrame 时,它​​期望键是将应用该函数的列名。

我想要做的是将多个函数应用于多个列(但某些列将被多次操作)。此外,某些函数将依赖于 groupby 对象中的其他列(如 sumif 函数)。我当前的解决方案是逐列执行,并执行类似上述代码的操作,对依赖于其他行的函数使用 lambda。但这需要很长时间(我认为遍历 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(), ...)

agg但正如预期的那样,我得到了一个KeyError(因为如果从DataFrame调用,键必须是一列)。

是否有任何内置方式可以完成我想要做的事情,或者是否有可能添加此功能,或者我只需要手动遍历 groupby 吗?


解决方案 1:

目前接受的答案的后半部分已经过时,并且有两个弃用之处。首先也是最重要的一点,您不能再将字典字典传递给agggroupby 方法。其次,永远不要使用.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

使用apply和返回 Series

现在,如果您有多个需要交互的列,那么您就不能使用agg,它会隐式地将 Series 传递给聚合函数。当使用apply整个组时,DataFrame 会被传递到函数中。

我建议制作一个自定义函数,返回所有聚合的系列。使用系列索引作为新列的标签:

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,您仍然可以返回一个像这样的 Series:

    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

解决方案 2:

对于第一部分,您可以传递一个包含键的列名字典和一个包含值的函数列表:

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 函数中的组索引对其进行索引。

这是一个棘手的解决方法:

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.indexdf.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

解决方案 3:

Pandas >= 0.25.0,命名聚合

从 pandas 版本0.25.0或更高版本开始,我们不再使用基于字典的聚合和重命名,而是转向接受命名聚合tuple。现在我们可以同时聚合 + 重命名为更具信息量的列名:

例子

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

解决方案 4:

作为 Ted Petrou 答案的替代方案(主要是美学方面),我发现我更喜欢稍微紧凑一点的列表。请不要考虑接受它,它只是对 Ted 答案的更详细的评论,加上代码/数据。Python/pandas 不是我的第一个/最好的,但我发现这个读起来很好:

df.groupby('group') \n  .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

解决方案 5:

0.25.0 版本中的新功能。

为了支持特定于列的聚合并控制输出列名称,pandas 接受GroupBy.agg()中的特殊语法,称为“命名聚合”,其中

  • 关键字是输出列名称

  • 这些值是元组,其第一个元素是要选择的列,第二个元素是要应用于该列的聚合。Pandas 提供了带有字段 ['column', 'aggfunc'] 的 pandas.NamedAgg 命名元组,以使参数更清晰。与往常一样,聚合可以是可调用的或字符串别名。

>>> 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 分组聚合也有效。在这种情况下,没有列选择,因此值只是函数。

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

解决方案 6:

这是对“exans”答案的改进,使用了命名聚合。它与“exans”答案相同,但具有参数解包功能,允许您仍将字典传递给 agg 函数。

命名聚合是一个很好的特性,但乍一看可能很难用编程方式编写,因为它们使用关键字,但实际上使用参数/关键字解包很简单。

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

解决方案 7:

Ted 的回答很棒。如果有人感兴趣的话,我最终使用了较小版本。当您寻找一个依赖于来自多个列的值的聚合时很有用:

创建数据框

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

使用应用进行分组和聚合(使用多列)

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

我希望这会有所帮助。

解决方案 8:

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

不起作用。抛出一个键错误:“KeyError: 'a'”

这个有效(@r2evans 的回答):

df.groupby('group') \n  .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()
  })
)

解决方案 9:

我发现@Erfan 的回答非常有帮助。我想建议一些用于处理来自多列的数据的其他技术。以下是聚合 DataFrame 的示例:

def series_range(series):
    return series.max() - series.min()

grouper = _df.reset_index().groupby('period_num')

_df_periods = grouper.agg(
    num_ashps=('count', 'min'),
    start=('timestamp', 'min'),
    length=('timestamp', series_range),
    elec_used=('elec_meter', series_range),
    heat_made=('heat_made', series_range),
    outdoor_temp=('outdoor_temp', 'mean')
)

当然,一种选择是随后添加列,如果它们可以根据已经在聚合 DataFrame 中汇总的数据计算出来,例如:

_df_periods['cop'] = _df_periods.heat_made / _df_periods.elec_used
_df_periods['length_mins'] = _df_periods.length.view(int) / (60 * 1e9)
_df_periods['avg_power'] = _df_periods.elec_used / (_df_periods.length_mins / 60)

另一种选择是事后添加列,但基于通过下标 DataFrameGroupBy 对象(grouper在本例中)计算的数据,从源 DataFrame 中提取某些列。例如,如果heat_madeelec_used尚未聚合到中,我们可以这样_df_periods计算:cop

_df_periods['cop'] = ((grouper['heat_made'].max() -
                       grouper['heat_made'].min()) / 
                      (grouper['elec_meter'].max() - 
                       grouper['elec_meter'].min()))

重复使用同一个grouper对象可能有助于保持代码简短易读。我不知道它是否重复使用实际的聚合 (group_by) 计算(避免重新计算),但我怀疑不是,也就是说,这只是表面现象。

解决方案 10:

.agg()我们可以将不带括号的函数列表传递给 pandas 方法,而不是将一个函数传递给.agg()

例如。

def cFun(column):
  return column + 1

df['column'].agg([cFun, np.mean])
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用