2012-06-08 16 views
49

Czy istnieje sposób napisania funkcji agregacji, która jest używana w metodzie DataFrame.agg, która miałaby dostęp do więcej niż jednej kolumny danych, które są agregowane? Typowe przypadki zastosowania to średnia ważona ważona odchyłka standardowa.Funkcja agregacji Pandas DataFrame przy użyciu wielu kolumn

Chciałbym móc napisać coś podobnego

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

Odpowiedz

68

TAK; użyj funkcji .apply(...), która będzie wywoływana na każdym sub-DataFrame. Na przykład:

grouped = df.groupby(keys) 

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

grouped.apply(wavg) 
+0

Skuteczniejsze może być podzielenie tego na kilka operacji w następujący sposób: (1) utworzyć kolumnę wag, (2) znormalizować obserwacje według ich wag, (3) obliczyć zgrupowaną sumę ważonych obserwacji i zgrupować suma wag, (4) znormalizować ważoną sumę obserwacji przez sumę wag. – kalu

+3

Co jeśli chcemy obliczyć wavg z wielu zmiennych (kolumn), np. wszystko oprócz df ["wag"]? – CPBL

+2

@Wers, czy jest jakiś sposób, aby zrobić to z 'agg()' i 'lambda' zbudowany wokół' np.average (... weights = ...) 'lub dowolnego nowego natywnego wsparcia w pandach dla ważonych znaczy od kiedy ten post pojawił się po raz pierwszy? –

3

Poniższe (w oparciu o odpowiedź Wesa McKinneya) spełniają dokładnie to, czego szukałem. Z przyjemnością dowiem się, czy jest prostszy sposób na wykonanie tego w ramach 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 

Funkcja df_wavg() zwraca dataframe który jest pogrupowany przez „GroupBy” kolumny, a zwraca sumę wag dla kolumny odważników. Pozostałe kolumny są albo ważonymi średnimi, albo, jeśli nie są liczbami, funkcja min() służy do agregacji.

3

zrobić to dużo i okazało się, że po dość poręczny:

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) 

Będzie to obliczyć średnią ważoną wszystkich kolumn liczbowych w df i upuść te non-numeryczne.

+0

To jest płonące szybko! Dobra robota! –

+0

To jest naprawdę słodkie, jeśli masz wiele kolumn. Miły! – Chris

+0

@santon, dzięki za odpowiedź. Czy możesz podać przykład swojego rozwiązania? Podczas próby skorzystania z rozwiązania dostałem komunikat "KeyError:" COUNT ". – Allen

1

Osiągnięcie tego poprzez groupby(...).apply(...) nie jest wydajne. Oto rozwiązanie, którego używam cały czas (zasadniczo używając logiki 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 
+0

Kiedy mówisz, że nie jest wydajna. Jaka jest różnica? Zmierzyłeś to? – Bouncner

1

Moje rozwiązanie jest podobne do rozwiązania Nathaniela, tylko to na jednej kolumnie, a ja nie głęboko skopiować całą ramkę danych za każdym razem, co może być zbyt powolny. Przyrost wydajności nad GroupBy rozwiązania (...). Stosuje się (...) wynosi około 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 
0

Jest możliwe, aby powrócić dowolną liczbę wartości zagregowanych z obiektu GroupBy z apply. Po prostu zwróć serię, a wartości indeksu staną się nowymi nazwami kolumn.

Zobaczmy szybki przykład:

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 

Definiowanie funkcji niestandardowej, która zostanie przekazana do apply. Niejawnie akceptuje DataFrame - co oznacza, że ​​parametr data to DataFrame. Zauważ, jak wykorzystuje wiele kolumn, co nie jest możliwe przy użyciu metody 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) 

wywołać metodę GroupBy apply z naszej funkcji niestandardowej:

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

     d1_wa d2_wa 
group    
a  9.0 2.2 
b  58.0 13.2 

można uzyskać lepszą wydajność przez precalculating ważoną sumuje się w nowych kolumnach DataFrame, jak wyjaśniono w innych odpowiedziach i całkowicie unika się używania apply.

Powiązane problemy