В 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
Однако это работает только на объекте Series groupby. И когда dict аналогично передается группе DataFrame, он ожидает, что ключи будут именами столбцов, к которым будет применена функция.
Я хочу сделать несколько функций для нескольких столбцов (но некоторые столбцы будут работать несколько раз). Кроме того, некоторые функции будут зависеть от других столбцов в объекте groupby (например, функции sumif). Мое текущее решение состоит в том, чтобы перейти от столбца к столбцу и сделать что-то вроде кода выше, используя lambdas для функций, которые зависят от других строк. Но это занимает много времени (я думаю, что для прохождения через объект groupby требуется много времени). Мне придется изменить его так, чтобы я перебирал весь объект groupby за один проход, но мне интересно, если это сделано в pandas, чтобы сделать это несколько чисто.
Например, я пробовал что-то вроде
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
вызывается из DataFrame).
Есть ли встроенный способ сделать то, что я хотел бы сделать, или возможность добавления этой функциональности или мне просто нужно выполнить итерацию через группу вручную?
Спасибо
Ответ 1
В первой части вы можете передать имя столбца для ключей и список функций для значений:
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:
Поскольку агрегатная функция работает в Series, ссылки на другие имена столбцов теряются. Чтобы обойти это, вы можете ссылаться на полный блок данных и индексировать его, используя индексы группы в лямбда-функции.
Вот хакерское обходное решение:
In [67]: f = {'A':['sum','mean'], 'B':['prod'], 'D': lambda g: df.ix[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.ix[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
Ответ 2
Вторая половина принятого в настоящее время ответа устарела и имеет два отклонения. Прежде всего, вы больше не можете передавать словарь словарей методу agg
groupby. Во-вторых, никогда не используйте .ix
.
Если вы хотите работать с двумя отдельными столбцами одновременно, я бы предложил использовать метод apply
, который implicity передает 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
Если вам не нравится это уродливое имя столбца лямбда, вы можете использовать обычную функцию и указать специальное имя для специального атрибута __name__
следующим образом:
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
вся группа в качестве DataFrame передается в функцию.
Я рекомендую создать единую настраиваемую функцию, которая возвращает Серию всех агрегатов. Используйте индекс Series как метки для новых столбцов:
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
Если вы влюблены в MultiIndexes, вы все равно можете вернуть серию с таким же образом:
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