Применение нескольких функций к нескольким столбцам groupby
на 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). Мое текущее решение - перейти столбец за столбцом и сделать что-то вроде кода выше, используя lambdas для функций, зависящих от других строк. Но это занимает много времени (я думаю, что требуется много времени для итерации через объект 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
to 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
метод, который имплицитно передает фрейм данных прикладной функции. Давайте использовать аналогичный dataframe как один из выше
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
группировка и агрегирование с помощью apply (с использованием нескольких столбцов)
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. Возможно люди сообщат мне, почему apply необходим для получения нескольких столбцов при выполнении агрегаций по группам.
теперь это кажется очевидным, но до тех пор, пока вы не выберете столбец интереса сразу после groupby, у вас будет доступ ко всем столбцам фрейма данных из вашей функции агрегации.
доступ только к выбранному столбцу
df.groupby('c')['a'].aggregate(lambda x: x[x>1].mean())
доступ ко всем столбцам, так как выбор в конце концов магия!--20-->
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())
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())
надеюсь, это поможет.