Pandas DataFrame使用多列聚合函数

时间:2012-06-08 15:01:33

标签: python pandas

有没有办法像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 ...

7 个答案:

答案 0 :(得分:79)

是;使用.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 :(得分:4)

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

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

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

答案 2 :(得分:3)

我做了很多,发现以下内容非常方便:

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中所有数字列的加权平均值,并删除非数字列。

答案 3 :(得分:2)

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

答案 4 :(得分:2)

我的解决方案类似于纳撒尼尔的解决方案,只有单一列的解决方案,我不会每次都对整个数据框进行深层复制,这可能会非常慢。解决方案组(...)。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

答案 5 :(得分:1)

这是一个具有以下优点的解决方案:

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

答案 6 :(得分:0)

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

--insecure

例如:

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