【问题标题】:Pandas DataFrame aggregate function using multiple columns使用多列的 Pandas DataFrame 聚合函数
【发布时间】:2012-06-12 15:50:47
【问题描述】:

有没有一种方法可以编写DataFrame.agg 方法中使用的聚合函数,它可以访问多个正在聚合的数据列?典型的用例是加权平均、加权标准差函数。

我希望能够写出类似的东西

def wAvg(c, w):
    return ((c * w).sum() / w.sum())

df = DataFrame(....) # df has columns c and w, i want weighted average
                     # of c using w as weight.
df.aggregate ({"c": wAvg}) # and somehow tell it to use w column as weights ...

【问题讨论】:

标签: python pandas


【解决方案1】:

您可以通过以下方式实现此功能:

(df['c'] * df['w']).groupby(df['groups']).sum() / df.groupby('groups')['w'].sum()

例如:

df = pd.DataFrame({'groups': [1, 1, 2, 2], 'c': [3, 3, 4, 4], 'w': [5, 5, 6, 6]})
(df['c'] * df['w']).groupby(df['groups']).sum() / df.groupby('groups')['w'].sum()

结果:

groups
1    3.0
2    4.0
dtype: float64

【讨论】:

    【解决方案2】:

    这是一个具有以下好处的解决方案:

    1. 您无需提前定义函数
    2. 您可以在管道中使用它(因为它使用 lambda)
    3. 您可以命名结果列

    df.groupby('group')
      .apply(lambda x: pd.Series({
    'weighted_average': np.average(x.data, weights = x.weights)})
    

    您还可以使用相同的代码来执行多个聚合:

    df.groupby('group')
      .apply(lambda x: pd.Series({
    'weighted_average': np.average(x.data, weights = x.weights), 
    'regular_average': np.average(x.data)}))
    

    【讨论】:

      【解决方案3】:

      我的解决方案与 Nathaniel 的解决方案类似,只是针对单个列,而且我不会每次都深度复制整个数据框,这可能会非常慢。解决方案 groupby(...).apply(...) 的性能增益约为 100x(!)

      def weighted_average(df, data_col, weight_col, by_col):
          df['_data_times_weight'] = df[data_col] * df[weight_col]
          df['_weight_where_notnull'] = df[weight_col] * pd.notnull(df[data_col])
          g = df.groupby(by_col)
          result = g['_data_times_weight'].sum() / g['_weight_where_notnull'].sum()
          del df['_data_times_weight'], df['_weight_where_notnull']
          return result
      

      【讨论】:

      • 如果您始终使用 PEP8 并删除多余的 del 行,将会更具可读性。
      • 谢谢! del 这一行其实并不是多余的,因为我就地更改了输入DataFrame以提高性能,所以我必须清理一下。
      • 但是您在结束函数的下一行返回结果。一旦函数完成,所有内部对象都会被清除。
      • 但请注意 df 不是内部对象。它是函数的一个参数,只要您从不分配给它 (df = something),它就仍然是一个浅拷贝,并且会就地更改。在这种情况下,列将被添加到 DataFrame。尝试复制粘贴此函数并在没有 del 行的情况下运行它,并查看它通过添加列来更改给定的 DataFrame。
      • 这并不能回答问题,因为加权平均只是作为多列上的任何聚合的示例。
      【解决方案4】:

      可以使用apply 从 groupby 对象返回任意数量的聚合值。简单地说,返回一个系列,索引值将成为新的列名。

      让我们看一个简单的例子:

      df = pd.DataFrame({'group':['a','a','b','b'],
                         'd1':[5,10,100,30],
                         'd2':[7,1,3,20],
                         'weights':[.2,.8, .4, .6]},
                       columns=['group', 'd1', 'd2', 'weights'])
      df
      
        group   d1  d2  weights
      0     a    5   7      0.2
      1     a   10   1      0.8
      2     b  100   3      0.4
      3     b   30  20      0.6
      

      定义一个将传递给apply 的自定义函数。它隐式接受一个 DataFrame - 意味着 data 参数是一个 DataFrame。请注意它如何使用多个列,而 agg groupby 方法无法做到这一点:

      def weighted_average(data):
          d = {}
          d['d1_wa'] = np.average(data['d1'], weights=data['weights'])
          d['d2_wa'] = np.average(data['d2'], weights=data['weights'])
          return pd.Series(d)
      

      使用我们的自定义函数调用 groupby apply 方法:

      df.groupby('group').apply(weighted_average)
      
             d1_wa  d2_wa
      group              
      a        9.0    2.2
      b       58.0   13.2
      

      如其他答案中所述,您可以通过将加权总数预先计算到新的 DataFrame 列中来获得更好的性能,并完全避免使用apply

      【讨论】:

        【解决方案5】:

        是的;使用.apply(...) 函数,它将在每个子DataFrame 上调用。例如:

        grouped = df.groupby(keys)
        
        def wavg(group):
            d = group['data']
            w = group['weights']
            return (d * w).sum() / w.sum()
        
        grouped.apply(wavg)
        

        【讨论】:

        • 将其分解为如下几个操作可能更有效:(1) 创建一列权重,(2) 通过权重对观测值进行归一化,(3) 计算加权观测值和权重分组总和,(4) 通过权重总和对观测值加权总和进行归一化。
        • 如果我们想计算许多变量(列)的 wavg,例如:除了 df['weights'] 之外的所有东西?
        • @Wes,有没有什么办法可以用agg() 和围绕np.average(...weights=...) 构建的lambda 来做到这一点,或者自从这篇文章首次出现以来,pandas 对加权平均值的任何新的原生支持?
        • @Wes McKinney:在您的书中,您建议采用这种方法:get_wavg = lambda g: np.average(g['data'], weights = g['weights']); grouped.apply(wavg)两者可以互换吗?
        【解决方案6】:

        通过groupby(...).apply(...) 完成此操作是不合格的。这是我一直使用的解决方案(主要使用 kalu 的逻辑)。

        def grouped_weighted_average(self, values, weights, *groupby_args, **groupby_kwargs):
           """
            :param values: column(s) to take the average of
            :param weights_col: column to weight on
            :param group_args: args to pass into groupby (e.g. the level you want to group on)
            :param group_kwargs: kwargs to pass into groupby
            :return: pandas.Series or pandas.DataFrame
            """
        
            if isinstance(values, str):
                values = [values]
        
            ss = []
            for value_col in values:
                df = self.copy()
                prod_name = 'prod_{v}_{w}'.format(v=value_col, w=weights)
                weights_name = 'weights_{w}'.format(w=weights)
        
                df[prod_name] = df[value_col] * df[weights]
                df[weights_name] = df[weights].where(~df[prod_name].isnull())
                df = df.groupby(*groupby_args, **groupby_kwargs).sum()
                s = df[prod_name] / df[weights_name]
                s.name = value_col
                ss.append(s)
            df = pd.concat(ss, axis=1) if len(ss) > 1 else ss[0]
            return df
        
        pandas.DataFrame.grouped_weighted_average = grouped_weighted_average
        

        【讨论】:

        • 当你说表现不佳时。有多少区别?测量过吗?
        • 谈论“非表现”:每次将“self”复制到“df”并将“sum”应用于整个df,它看起来是否“表现”,甚至是“健全”(某些专栏可能包含不打算求和的值)?
        【解决方案7】:

        我经常这样做,发现以下非常方便:

        def weighed_average(grp):
            return grp._get_numeric_data().multiply(grp['COUNT'], axis=0).sum()/grp['COUNT'].sum()
        df.groupby('SOME_COL').apply(weighed_average)
        

        这将计算df 中所有数字列的加权平均值,并删除非数字列。

        【讨论】:

        • 这太快了!干得好!
        • 如果您有多个列,这真的很不错。不错!
        • @santon,感谢您的回答。你能举一个你的解决方案的例子吗?我在尝试使用您的解决方案时收到错误“KeyError: 'COUNT'。
        • @SModi 您可以轻松地将函数修改为def weighted_average(grp, col='COUNT'): ... 以参数化列名。 (当然,将代码中的'COUNT' 替换为col 也是如此。)然后,只需调用apply(lambda g: weigthed_average(g, "nt_pop")
        • 感谢这工作。我之前尝试过正确地编辑了函数,但我没有正确调用它。
        【解决方案8】:

        以下内容(基于 Wes McKinney 的回答)完全符合我的要求。我很乐意了解pandas 中是否有更简单的方法。

        def wavg_func(datacol, weightscol):
            def wavg(group):
                dd = group[datacol]
                ww = group[weightscol] * 1.0
                return (dd * ww).sum() / ww.sum()
            return wavg
        
        
        def df_wavg(df, groupbycol, weightscol):
            grouped = df.groupby(groupbycol)
            df_ret = grouped.agg({weightscol:sum})
            datacols = [cc for cc in df.columns if cc not in [groupbycol, weightscol]]
            for dcol in datacols:
                try:
                    wavg_f = wavg_func(dcol, weightscol)
                    df_ret[dcol] = grouped.apply(wavg_f)
                except TypeError:  # handle non-numeric columns
                    df_ret[dcol] = grouped.agg({dcol:min})
            return df_ret
        

        函数df_wavg() 返回一个按“groupby”列分组的数据框,并返回权重列的权重总和。其他列要么是加权平均值,要么是非数字的,min() 函数用于聚合。

        【讨论】:

          猜你喜欢
          • 2021-07-22
          • 2022-11-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-06-24
          • 1970-01-01
          • 2018-08-21
          相关资源
          最近更新 更多