Подтвердить что ты не робот

Применение нескольких функций к нескольким столбцам группы

В 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).

Есть ли встроенный способ сделать то, что я хотел бы сделать, или возможность добавления этой функциональности или мне просто нужно выполнить итерацию через группу вручную?

Спасибо

4b9b3361

Ответ 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