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

221

В документах показывают , как применить несколько функций на объекте 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

Однако это работает только для объекта Seriesby. И когда dict аналогично передается в групповой DataFrame, он ожидает, что ключами будут имена столбцов, к которым будет применена функция.

Я хочу применить несколько функций к нескольким столбцам (но некоторые столбцы будут работать несколько раз). Кроме того, некоторые функции будут зависеть от других столбцов в объекте 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вызывается из DataFrame).

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

Спасибо

beardc
источник
2
Если вы подходите к этому вопросу в 2017 году +, см. Ответ ниже, чтобы увидеть идиоматический способ объединения нескольких столбцов вместе. В текущем выбранном ответе содержится несколько устаревших слов, а именно то, что вы больше не можете использовать словарь словарей для переименования столбцов в результате группировки.
Тед Петру

Ответы:

283

Вторая половина принятого в настоящее время ответа устарела и имеет две оценки. Первое и самое важное, вы больше не можете передавать словарь словарей в 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.864569  0.446069  0.466054  0.969921  0.341399
1      1.478872  0.843026  0.687672  1.754877  0.672401

Если вам не нравится это уродливое имя лямбда-столбца, вы можете использовать обычную функцию и указать свое имя для специального __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.864569  0.446069  0.466054  0.969921      0.341399
1      1.478872  0.843026  0.687672  1.754877      0.672401

Использование applyи возврат серии

Теперь, если у вас было несколько столбцов, которые должны были взаимодействовать друг с другом, вы не можете использовать agg, что неявно передает Series в функцию агрегирования. При использовании 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.864569  0.446069  0.466054     0.173711
1      1.478872  0.843026  0.687672     0.630494

Если вы влюблены в 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.864569  0.446069  0.466054  0.173711
1      1.478872  0.843026  0.687672  0.630494
Тед Петру
источник
3
Мне нравится шаблон использования функции, которая возвращает серию. Очень аккуратный.
Стивен
2
это единственный способ, который я нашел для агрегации данных через несколько входов столбцов одновременно (пример c_d выше)
Блейк,
2
Я смущен результатами, принимая суммирование aвнутри группы, не 0должно ли это быть 0.418500 + 0.446069 = 0.864569? То же самое относится и к другим ячейкам, цифры не складываются. Может ли быть немного другой базовый фрейм данных использовался в последующих примерах?
слеклайн
Я часто использую .size () с groupby, чтобы увидеть количество записей. Есть ли способ сделать это с помощью метода agg: dict? Я понимаю, что могу сосчитать определенное поле, но я бы предпочел, чтобы подсчет был независимым от поля.
Крис Декер
1
@ Слэклайн да. Я только что проверил это, и он отлично работает. Тед, должно быть, только что создал кадр несколько раз, и, поскольку он был создан с помощью генерации случайных чисел, данные df для фактической генерации данных отличались от тех, которые в конечном итоге использовались в вычислениях
Лукас H
166

В первой части вы можете передать слова имен столбцов для ключей и список функций для значений:

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.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.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
Zelazny7
источник
Интересно, что я также могу передать в {funcname: func}качестве значения вместо списков свои собственные имена. Но в любом случае я не могу передать a, lambdaкоторый использует другие столбцы (как lambda x: x['D'][x['C'] < 3].sum()выше: "KeyError: 'D'"). Есть идеи, если это возможно?
beardc
Я пытался сделать именно это, и я получаю ошибкуKeyError: 'D'
Zelazny7
Круто, я получил его для работы df['A'].ix[g.index][df['C'] < 0].sum(). Это начинает становиться довольно грязным - я думаю, что для удобства чтения ручное зацикливание может быть предпочтительным, плюс я не уверен, что есть способ дать ему мое предпочтительное имя в aggаргументе (вместо <lambda>). Я буду надеяться, что кто-то может знать более простой способ ...
beardc
3
Вы можете передать dict для значения столбца, {'D': {'my name':lambda function}}и он сделает внутренний ключ dict именем столбца.
Zelazny7
1
Я считаю, что pandas теперь поддерживает несколько функций, примененных к сгруппированному кадру данных
IanS
22

В качестве альтернативы (в основном по эстетике) ответу Теда Петру я обнаружил, что предпочитаю чуть более компактный список. Пожалуйста, не рассматривайте возможность его принятия, это просто более подробный комментарий к ответу Теда плюс код / ​​данные. Python / pandas не мой первый / лучший, но я нашел это, чтобы прочитать хорошо:

df.groupby('group') \
  .apply(lambda x: pd.Series({
      'a_sum'       : x['a'].sum(),
      'a_max'       : x['a'].max(),
      'b_mean'      : x['b'].mean(),
      'c_d_prodsum' : (x['c'] * x['d']).sum()
  })
)

          a_sum     a_max    b_mean  c_d_prodsum
group                                           
0      0.530559  0.374540  0.553354     0.488525
1      1.433558  0.832443  0.460206     0.053313

Я нахожу это больше напоминающим dplyrканалы и data.tableцепочечные команды. Не сказать, что они лучше, просто мне знакомее. (Я, конечно, признаю силу и для многих предпочтение использовать более формализованные defфункции для операций такого типа. Это всего лишь альтернатива, не обязательно лучшая.)


Я сгенерировал данные так же, как Тед, я добавлю семя для воспроизводимости.

import numpy as np
np.random.seed(42)
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.374540  0.950714  0.731994  0.598658      0
1  0.156019  0.155995  0.058084  0.866176      0
2  0.601115  0.708073  0.020584  0.969910      1
3  0.832443  0.212339  0.181825  0.183405      1
r2evans
источник
2
Мне нравится этот ответ больше всего. Это похоже на dplyr трубы в R.
Renhuai
18

Pandas >= 0.25.0названные скопления

Начиная с версии pandas 0.25.0или выше, мы отходим от словарного агрегирования и переименования и движемся к именованным агрегациям, которые принимают a tuple. Теперь мы можем одновременно агрегировать + переименовывать в более информативное имя столбца:

Пример :

df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]

          a         b         c         d  group
0  0.521279  0.914988  0.054057  0.125668      0
1  0.426058  0.828890  0.784093  0.446211      0
2  0.363136  0.843751  0.184967  0.467351      1
3  0.241012  0.470053  0.358018  0.525032      1

Применить GroupBy.aggс именованным агрегацией:

df.groupby('group').agg(
             a_sum=('a', 'sum'),
             a_mean=('a', 'mean'),
             b_mean=('b', 'mean'),
             c_sum=('c', 'sum'),
             d_range=('d', lambda x: x.max() - x.min())
)

          a_sum    a_mean    b_mean     c_sum   d_range
group                                                  
0      0.947337  0.473668  0.871939  0.838150  0.320543
1      0.604149  0.302074  0.656902  0.542985  0.057681
Erfan
источник
Мне нравятся эти именованные агрегаты, но я не мог понять, как мы должны использовать их с несколькими столбцами?
Саймон Вудхед
Хороший вопрос, не смог разобраться, сомневаюсь, что это возможно (пока). Я открыл билет на это. Буду держать мой вопрос и вы в курсе. Спасибо за указание @SimonWoodhead
Erfan
4

Новое в версии 0.25.0.

Для поддержки агрегирования по конкретным столбцам с контролем над именами выходных столбцов pandas принимает специальный синтаксис в GroupBy.agg () , известный как «именованная агрегация» , где

  • Ключевыми словами являются имена выходных столбцов.
  • Значения - это кортежи, первый элемент которых является столбцом для выбора, а второй элемент - это агрегирование, которое будет применяться к этому столбцу. Pandas предоставляет именованный корте pandas.NamedAgg с полями ['column', 'aggfunc'], чтобы прояснить аргументы. Как обычно, агрегация может быть вызываемым или строковым псевдонимом.
    In [79]: animals = pd.DataFrame({'kind': ['cat', 'dog', 'cat', 'dog'],
       ....:                         'height': [9.1, 6.0, 9.5, 34.0],
       ....:                         'weight': [7.9, 7.5, 9.9, 198.0]})
       ....: 

    In [80]: animals
    Out[80]: 
      kind  height  weight
    0  cat     9.1     7.9
    1  dog     6.0     7.5
    2  cat     9.5     9.9
    3  dog    34.0   198.0

    In [81]: animals.groupby("kind").agg(
       ....:     min_height=pd.NamedAgg(column='height', aggfunc='min'),
       ....:     max_height=pd.NamedAgg(column='height', aggfunc='max'),
       ....:     average_weight=pd.NamedAgg(column='weight', aggfunc=np.mean),
       ....: )
       ....: 
    Out[81]: 
          min_height  max_height  average_weight
    kind                                        
    cat          9.1         9.5            8.90
    dog          6.0        34.0          102.75

pandas.NamedAgg - это просто именованный кортеж. Простые кортежи также допускаются.

    In [82]: animals.groupby("kind").agg(
       ....:     min_height=('height', 'min'),
       ....:     max_height=('height', 'max'),
       ....:     average_weight=('weight', np.mean),
       ....: )
       ....: 
    Out[82]: 
          min_height  max_height  average_weight
    kind                                        
    cat          9.1         9.5            8.90
    dog          6.0        34.0          102.75

Дополнительные ключевые аргументы не передаются в функции агрегации. Только пары (column, aggfunc) должны быть переданы как ** kwargs. Если ваши функции агрегирования требуют дополнительных аргументов, частично примените их с помощью functools.partial ().

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

    In [84]: animals.groupby("kind").height.agg(
       ....:     min_height='min',
       ....:     max_height='max',
       ....: )
       ....: 
    Out[84]: 
          min_height  max_height
    kind                        
    cat          9.1         9.5
    dog          6.0        34.0
Exan
источник
3

Ответ Теда удивителен. Я закончил тем, что использовал уменьшенную версию этого на тот случай, если кому-то будет интересно. Полезно, когда вы ищете одну агрегацию, которая зависит от значений из нескольких столбцов:

создать фрейм данных

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

группировка и агрегирование с использованием агрегата (с использованием нескольких столбцов)

Мне нравится этот подход, так как я все еще могу использовать агрегат. Возможно, люди дадут мне знать, почему применяется применение для получения нескольких столбцов при агрегировании групп.

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

доступ только к выбранному столбцу

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())

Надеюсь, это поможет.

кампо
источник