Есть ли способ написать функцию агрегирования, используемую в 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(...)
функцию, которая будет вызываться для каждого под-DataFrame
. Например:grouped = df.groupby(keys) def wavg(group): d = group['data'] w = group['weights'] return (d * w).sum() / w.sum() grouped.apply(wavg)
источник
agg()
помощьюlambda
встроеннойnp.average(...weights=...)
или какой-либо новой встроенной поддержки в пандах для взвешенных средств с момента первого появления этого сообщения?get_wavg = lambda g: np.average(g['data'], weights = g['weights'])
;grouped.apply(wavg)
Они взаимозаменяемы?Мое решение похоже на решение Натаниэля, только для одного столбца, и я не копирую каждый раз глубоко весь фрейм данных, что может быть чрезмерно медленным. Прирост производительности по сравнению с группой решений by (...). 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
источник
del
строку.del
Линия на самом деле не лишняя, так как я изменить входной DataFrame на месте для повышения производительности, так что я должен убирать.df = something
), он остается неглубокой копией и изменяется на месте. В этом случае столбцы будут добавлены в DataFrame. Попробуйте скопировать эту функцию и запустить ее безdel
строки, и убедитесь, что она изменяет данный DataFrame, добавляя столбцы.Можно вернуть любое количество агрегированных значений из объекта groupby с помощью
apply
. Просто верните 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)
Вызовите
apply
метод groupby с помощью нашей пользовательской функции:df.groupby('group').apply(weighted_average) d1_wa d2_wa group a 9.0 2.2 b 58.0 13.2
Вы можете повысить производительность, предварительно вычислив взвешенные итоги в новых столбцах DataFrame, как описано в других ответах, и
apply
вообще не использовать .источник
Следующее (на основе ответа Уэса МакКинни) выполняет именно то, что я искал. Я был бы рад узнать, есть ли внутри более простой способ сделать это
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(...)
. Вот решение, которое я использую все время (по сути, используя логику Калу).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
источник