Gibt es eine Möglichkeit, eine Aggregationsfunktion zu schreiben, wie sie in der DataFrame.agg
Methode verwendet wird und die Zugriff auf mehr als eine Spalte der Daten hat, die aggregiert werden? Typische Anwendungsfälle wären gewichtete durchschnittliche, gewichtete Standardabweichungsfunktionen.
Ich würde gerne so etwas schreiben können
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 ...
Antworten:
Ja; Verwenden Sie die
.apply(...)
Funktion, die auf jedem Sub aufgerufen wirdDataFrame
. Zum Beispiel:grouped = df.groupby(keys) def wavg(group): d = group['data'] w = group['weights'] return (d * w).sum() / w.sum() grouped.apply(wavg)
quelle
agg()
undlambda
um diese herum zu tunnp.average(...weights=...)
, oder eine neue native Unterstützung in Pandas für gewichtete Mittel, seit dieser Beitrag zum ersten Mal veröffentlicht wurde?get_wavg = lambda g: np.average(g['data'], weights = g['weights'])
;grouped.apply(wavg)
Sind die beiden austauschbar?Meine Lösung ähnelt der von Nathaniel, nur für eine einzelne Spalte, und ich kopiere nicht jedes Mal den gesamten Datenrahmen tief, was unerschwinglich langsam sein kann. Der Leistungsgewinn gegenüber der Lösungsgruppe durch (...). Apply (...) beträgt ca. 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
quelle
del
Zeile entfernen würden .del
Zeile ist eigentlich nicht überflüssig, da ich den eingegebenen DataFrame an Ort und Stelle ändere, um die Leistung zu verbessern, und daher aufräumen muss.df = something
), bleibt es eine flache Kopie und wird an Ort und Stelle geändert. In diesem Fall werden dem DataFrame Spalten hinzugefügt. Versuchen Sie, diese Funktion zu kopieren und ohne diedel
Zeile auszuführen, und stellen Sie sicher, dass der angegebene DataFrame durch Hinzufügen von Spalten geändert wird.Es ist möglich, eine beliebige Anzahl von aggregierten Werten von einem groupby-Objekt mit zurückzugeben
apply
. Geben Sie einfach eine Serie zurück und die Indexwerte werden zu den neuen Spaltennamen.Sehen wir uns ein kurzes Beispiel an:
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
Definieren Sie eine benutzerdefinierte Funktion, an die übergeben wird
apply
. Es akzeptiert implizit einen DataFrame - was bedeutet, dass derdata
Parameter ein DataFrame ist. Beachten Sie, wie mehrere Spalten verwendet werden, was mit deragg
groupby-Methode nicht möglich ist :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)
Rufen Sie die groupby-
apply
Methode mit unserer benutzerdefinierten Funktion auf:df.groupby('group').apply(weighted_average) d1_wa d2_wa group a 9.0 2.2 b 58.0 13.2
Sie können eine bessere Leistung erzielen, indem Sie die gewichteten Summen wie in anderen Antworten erläutert in neue DataFrame-Spalten vorberechnen und die Verwendung
apply
insgesamt vermeiden .quelle
Das Folgende (basierend auf Wes McKinneys Antwort) erreicht genau das, wonach ich gesucht habe. Ich würde mich freuen zu erfahren, ob es einen einfacheren Weg gibt, dies innerhalb zu tun
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
Die Funktion
df_wavg()
gibt einen Datenrahmen zurück, der nach der Spalte "groupby" gruppiert ist und die Summe der Gewichte für die Spalte "weight" zurückgibt. Andere Spalten sind entweder die gewichteten Durchschnittswerte oder, wenn sie nicht numerisch sind, wird diemin()
Funktion zur Aggregation verwendet.quelle
Ich mache das oft und fand Folgendes ziemlich praktisch:
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)
Dadurch wird der gewichtete Durchschnitt aller numerischen Spalten in den
df
und nicht numerischen Spalten berechnet .quelle
Dies zu erreichen
groupby(...).apply(...)
ist nicht performant. Hier ist eine Lösung, die ich ständig benutze (im Wesentlichen unter Verwendung der Kalu-Logik).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
quelle