Применение нескольких функций к нескольким столбцам groupby
The docs показать, как применять несколько функций к объекту groupby одновременно, используя dict с именами выходных столбцов в качестве ключей:
In [563]: grouped['D'].agg({'result1' : np.sum,
.....: 'result2' : np.mean})
.....:
Out[563]:
result2 result1
A
bar -0.579846 -1.739537
foo -0.280588 -1.402938
однако это работает только на объекте серии groupby. И когда dict аналогично передается в фрейм данных groupby, он ожидает, что ключи будут именами столбцов, к которым будет применена функция.
что я хочу сделать, это применить несколько функций к нескольким столбцам (но некоторые столбцы будут оперировали несколько раз). Кроме того,некоторые функции будут зависеть от других столбцов в объекте groupby (как функции sumif). Мое текущее решение - идти столбец за столбцом и делать что-то вроде кода выше, используя лямбды для функций, которые зависят от других строк. Но это занимает много времени (я думаю, что требуется много времени для итерации через объект groupby). Мне придется изменить его так, чтобы я перебирал весь объект groupby за один прогон, но мне интересно, если есть встроенный способ в панд, чтобы сделать это немного чище.
например, я пробовал что-то вроде
grouped.agg({'C_sum' : lambda x: x['C'].sum(),
'C_std': lambda x: x['C'].std(),
'D_sum' : lambda x: x['D'].sum()},
'D_sumifC3': lambda x: x['D'][x['C'] == 3].sum(), ...)
но, как и ожидалось, я получаю KeyError (так как ключи должны быть колонки если agg
вызывается из фрейма данных).
есть ли встроенный способ сделать то, что я хотел бы сделать, или возможность того, что эта функциональность может быть добавлена, или мне просто нужно будет перебирать groupby вручную?
спасибо
3 ответа:
для первой части вы можете передать дикт имен столбцов для ключей и список функций для значений:
In [28]: df Out[28]: A B C D E GRP 0 0.395670 0.219560 0.600644 0.613445 0.242893 0 1 0.323911 0.464584 0.107215 0.204072 0.927325 0 2 0.321358 0.076037 0.166946 0.439661 0.914612 1 3 0.133466 0.447946 0.014815 0.130781 0.268290 1 In [26]: f = {'A':['sum','mean'], 'B':['prod']} In [27]: df.groupby('GRP').agg(f) Out[27]: A B sum mean prod GRP 0 0.719580 0.359790 0.102004 1 0.454824 0.227412 0.034060
обновление 1:
поскольку агрегатная функция работает с рядами, ссылки на другие имена столбцов теряются. Чтобы обойти это, вы можете ссылаться на полный фрейм данных и индексировать его с помощью групповых индексов в лямбда-функции.
вот хаки обходной путь:
In [67]: f = {'A':['sum','mean'], 'B':['prod'], 'D': lambda g: df.loc[g.index].E.sum()} In [69]: df.groupby('GRP').agg(f) Out[69]: A B D sum mean prod <lambda> GRP 0 0.719580 0.359790 0.102004 1.170219 1 0.454824 0.227412 0.034060 1.182901
здесь результирующий столбец " D " составлен из суммированных значений 'E'.
обновление 2:
вот метод, который, я думаю, будет делать все, что вы просите. Сначала сделайте пользовательскую лямбда-функцию. Ниже g ссылается на группу. При агрегировании g будет серией. Передает
g.index
доdf.ix[]
выбирает текущую группу из df. Затем я проверяю, если столбец C меньше 0,5. Возвращаемый логический ряд передается вg[]
который выбирает только те строки, соответствующие критериям.In [95]: cust = lambda g: g[df.loc[g.index]['C'] < 0.5].sum() In [96]: f = {'A':['sum','mean'], 'B':['prod'], 'D': {'my name': cust}} In [97]: df.groupby('GRP').agg(f) Out[97]: A B D sum mean prod my name GRP 0 0.719580 0.359790 0.102004 0.204072 1 0.454824 0.227412 0.034060 0.570441
вторая половина принятого в настоящее время ответа устарела и имеет два устаревания. Во-первых и самое главное, вы больше не можете передать словарь словарей в
agg
метод groupby. Во-вторых, никогда не используйте.ix
.если вы хотите работать с двумя отдельными столбцами одновременно, я бы предложил использовать
apply
метод, который имплицитно передает фрейм данных прикладной функции. Давайте использовать аналогичный фрейм данных, как один из вышеdf = pd.DataFrame(np.random.rand(4,4), columns=list('abcd')) df['group'] = [0, 0, 1, 1] df a b c d group 0 0.418500 0.030955 0.874869 0.145641 0 1 0.446069 0.901153 0.095052 0.487040 0 2 0.843026 0.936169 0.926090 0.041722 1 3 0.635846 0.439175 0.828787 0.714123 1
словарь, отображаемый из имен столбцов в функции агрегации, по-прежнему является отличным способом выполнения агрегации.
df.groupby('group').agg({'a':['sum', 'max'], 'b':'mean', 'c':'sum', 'd': lambda x: x.max() - x.min()}) a b c d sum max mean sum <lambda> group 0 0.560541 0.507058 0.418546 1.707651 0.129667 1 0.187757 0.157958 0.887315 0.533531 0.652427
Если вам не нравится это уродливое имя столбца лямбда, вы можете использовать обычную функцию и предоставить специальное имя для специального такой:
def max_min(x): return x.max() - x.min() max_min.__name__ = 'Max minus Min' df.groupby('group').agg({'a':['sum', 'max'], 'b':'mean', 'c':'sum', 'd': max_min}) a b c d sum max mean sum Max minus Min group 0 0.560541 0.507058 0.418546 1.707651 0.129667 1 0.187757 0.157958 0.887315 0.533531 0.652427
используя
apply
и возвращение сериитеперь, если у вас есть несколько столбцов, которые должны взаимодействовать вместе вы не можете использовать
agg
, который неявно передает ряд в агрегирующую функцию. При использованииapply
вся группа в виде фрейма данных передается в функцию.я рекомендую сделать одну пользовательскую функцию, которая возвращает ряд всех агрегатов. Используйте индекс серии в качестве меток для новых столбцов:
def f(x): d = {} d['a_sum'] = x['a'].sum() d['a_max'] = x['a'].max() d['b_mean'] = x['b'].mean() d['c_d_prodsum'] = (x['c'] * x['d']).sum() return pd.Series(d, index=['a_sum', 'a_max', 'b_mean', 'c_d_prodsum']) df.groupby('group').apply(f) a_sum a_max b_mean c_d_prodsum group 0 0.560541 0.507058 0.418546 0.118106 1 0.187757 0.157958 0.887315 0.276808
если вы влюблены в Мультииндексы, вы все равно можете вернуть серию с таким:
def f_mi(x): d = [] d.append(x['a'].sum()) d.append(x['a'].max()) d.append(x['b'].mean()) d.append((x['c'] * x['d']).sum()) return pd.Series(d, index=[['a', 'a', 'b', 'c_d'], ['sum', 'max', 'mean', 'prodsum']]) df.groupby('group').apply(f_mi) a b c_d sum max mean prodsum group 0 0.560541 0.507058 0.418546 0.118106 1 0.187757 0.157958 0.887315 0.276808
ответ Теда удивителен. Я закончил тем, что использовал меньшую версию этого в случае, если кто-то заинтересован. Полезно, когда вы ищете одну агрегацию, которая зависит от значений из нескольких столбцов:
создать таблицу данных
df=pd.DataFrame({'a': [1,2,3,4,5,6], 'b': [1,1,0,1,1,0], 'c': ['x','x','y','y','z','z']}) a b c 0 1 1 x 1 2 1 x 2 3 0 y 3 4 1 y 4 5 1 z 5 6 0 z
группировка и агрегирование с применением (с использованием нескольких столбцов)
df.groupby('c').apply(lambda x: x['a'][(x['a']>1) & (x['b']==1)].mean()) c x 2.0 y 4.0 z 5.0
группировка и агрегирование с помощью aggregate (с использованием нескольких столбцов)
мне нравится этот подход, так как я все еще могу использовать aggregate. Возможно люди дадут мне знать, почему приложение необходимо для получения нескольких столбцов при выполнении агрегации по группам.
теперь это кажется очевидным, но до тех пор, пока вы не выберете столбец интереса сразу после groupby, вы будете иметь доступ ко всем столбцам таблицы данных из функции агрегирования.
только доступ к выбранному столбцу
df.groupby('c')['a'].aggregate(lambda x: x[x>1].mean())
доступ ко всем столбцам, так как выбор после всего магия
df.groupby('c').aggregate(lambda x: x[(x['a']>1) & (x['b']==1)].mean())['a']
или же
df.groupby('c').aggregate(lambda x: x['a'][(x['a']>1) & (x['b']==1)].mean())
Я надеюсь, что это помогает.