簡體   English   中英

Pandas DataFrame 使用多列聚合 function

[英]Pandas DataFrame aggregate function using multiple columns

有沒有辦法編寫一個聚合 function ,就像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 ...

是的; 使用.apply(...)函數,它將在每個子DataFrameDataFrame 例如:

grouped = df.groupby(keys)

def wavg(group):
    d = group['data']
    w = group['weights']
    return (d * w).sum() / w.sum()

grouped.apply(wavg)

可以使用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

我的解決方案類似於 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

以下(基於 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()函數用於聚合。

我經常這樣做,發現以下內容非常方便:

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中所有數字列的加權平均值,並刪除非數字列。

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

這是一個具有以下優點的解決方案:

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

您可以通過以下方式實現此功能:

(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

添加到 Wes MacKinney 答案,這將重命名聚合列:

grouped = df.groupby(keys)

def wavg(group):
    d = group['data']
    w = group['weights']
    return (d * w).sum() / w.sum()

grouped.apply(wavg).reset_index().rename(columns={0 : "wavg"})

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM