python - groupby - union pandas dataframes




Pandas DataFrame aggrega la funzione utilizzando più colonne (4)

C'è un modo per scrivere una funzione di aggregazione come è usata nel metodo DataFrame.agg , che avrebbe accesso a più di una colonna dei dati che vengono aggregati? I casi d'uso tipici sarebbero la media ponderata, le funzioni di deviazione standard ponderate.

Mi piacerebbe poter scrivere qualcosa del genere

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

È possibile restituire qualsiasi numero di valori aggregati da un oggetto groupby con apply . Semplicemente, restituisci una serie ei valori dell'indice diventeranno i nuovi nomi di colonna.

Vediamo un esempio veloce:

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

Definire una funzione personalizzata che verrà passata per apply . Accetta implicitamente un DataFrame, ovvero il parametro data è un DataFrame. Si noti come utilizza più colonne, il che non è possibile con il metodo 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)

Chiama il metodo groupby apply con la nostra funzione personalizzata:

df.groupby('group').apply(weighted_average)

       d1_wa  d2_wa
group              
a        9.0    2.2
b       58.0   13.2

È possibile ottenere prestazioni migliori eseguendo il calcolo preliminare dei totali ponderati in nuove colonne DataFrame come spiegato in altre risposte ed evitando di utilizzare l' apply tutto.


Il seguente (basato sulla risposta di Wes McKinney) realizza esattamente ciò che stavo cercando. Sarei felice di sapere se c'è un modo più semplice per farlo nei 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

La funzione df_wavg() restituisce un dataframe raggruppato dalla colonna "groupby" e che restituisce la somma dei pesi per la colonna dei pesi. Altre colonne sono le medie ponderate o, se non numeriche, la funzione min() viene utilizzata per l'aggregazione.


Lo faccio molto e ho trovato il seguente abbastanza utile:

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)

Questo calcolerà la media ponderata di tutte le colonne numeriche nel df e non quelle non numeriche.


Realizzare questo tramite groupby(...).apply(...) è non performante. Ecco una soluzione che uso sempre (essenzialmente usando la logica di 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






pandas