Застосувати декілька функцій до кількох групових колонок


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

Однак це працює лише на об'єкті групи об'єктів. І коли дикт аналогічно передається групі за допомогою DataFrame, він очікує, що ключі будуть назвами стовпців, до яких буде застосована функція.

Що я хочу зробити, це застосувати кілька функцій до декількох стовпців (але певні стовпці будуть працювати декількома разів). Також деякі функції залежатимуть від інших стовпців об'єкта groupby (як функції sumif). Моє поточне рішення - перейти колонку за стовпцем і робити щось на зразок коду вище, використовуючи лямбда для функцій, які залежать від інших рядків. Але це займає багато часу (я думаю, що для перегляду об’єкта через групу потрібно тривалий час). Мені доведеться змінити це так, щоб я повторював весь об'єкт 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).

Чи є якийсь вбудований спосіб робити те, що я хотів би зробити, або можливість, щоб ця функціональність була додана, або мені просто потрібно буде повторити групу вручну?

Дякую


2
Якщо ви надходили до цього питання у 2017 році або більше, перегляньте відповідь нижче, щоб побачити ідіоматичний спосіб об’єднання кількох стовпців разом. На даний момент обрана відповідь містить у собі кілька депресацій, а саме, що ви більше не можете використовувати словник словників для перейменування стовпців у результаті групи.
Тед Петру

Відповіді:


282

Друга половина прийнятої на даний момент відповіді застаріла і має дві депресії. По-перше, і найголовніше, ви більше не можете передавати словник словників 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, що неявно передає Серію функції зведення. При використанні 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
це єдиний спосіб, який я знайшов, щоб агрегувати кадр даних за допомогою декількох входів стовпців simulatneosly (приклад c_d вище)
Blake

2
Я збентежений результатами, якщо підсумовування aв групі не 0повинно бути цього 0.418500 + 0.446069 = 0.864569? Те ж саме стосується інших комірок, цифри, схоже, не підсумовуються. Чи може бути дещо інший базовий кадр даних використаний у наступних прикладах?
слакліній

Я часто використовую .size () з групою для перегляду кількості записів. Чи можливо це зробити за допомогою методу agg: dict? Я розумію, що я міг би порахувати певне поле, але я вважаю за краще, щоб кількість не залежала від поля.
Кріс Декер

1
@slackline так. Я щойно перевірив це, і він працює чудово. Тед, мабуть, щойно створив кадр кілька разів, і оскільки він був створений за допомогою генерації випадкових чисел, дані df для фактичного генерування даних були іншими, ніж ті, які в кінцевому рахунку використовувались у розрахунках
Лукас Х

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 буде Серією. Проходження g.indexдля df.ix[]вибору поточної групи з df. Потім я перевіряю, чи стовпець С менше 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

Цікаво, що я можу також пройти наказ про {funcname: func}значення як замість списків, щоб зберегти власні імена. Але в будь-якому випадку я не можу передавати таблицю, 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
Ви можете пропустити дікт за значенням стовпця, {'D': {'my name':lambda function}}і він зробить внутрішній ключ назвою назви стовпця.
Zelazny7

1
Я вважаю, що зараз панди підтримують декілька функцій, застосованих до згрупованого фрейму даних: pandas.pydata.org/pandas-docs/stable/…
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

2
Мені подобається ця відповідь найбільше. Це схоже на труби dplyr у Р.
Ренхуай

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

Мені подобаються ці названі агрегати, але я не міг зрозуміти, як ми повинні використовувати їх у кількох стовпцях?
Саймон Вудхед

Хороше запитання, не змогли цього розібратися, сумніватися в цьому можливо (поки що). Я відкрив на це квиток . Буде тримати моє запитання, і ви будете оновлені. Дякуємо, що вказали на @SimonWoodhead
Ерфан

4

Нове у версії 0.25.0.

Для підтримки специфічної для стовпців агрегації з керуванням вихідними іменами стовпців, панди приймає спеціальний синтаксис у GroupBy.agg () , відомий як "названа агрегація" , де

  • Ключові слова - це вихідні назви стовпців
  • Значення - кортежі, першим елементом яких є стовпець для вибору, а другий елемент - це агрегація, що застосовується до цього стовпця. Pandas надає pandas.NamedAgg з именемtuple полями ['стовпець', '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 - це лише nametuple. Допускаються також звичайні кортежі.

    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

Додаткові аргументи ключових слів не передаються функціям агрегації. Лише пари (стовпчик, аггфунк) повинні передаватися як ** kwargs. Якщо функції агрегації потребують додаткових аргументів, частково застосуйте їх за допомогою functools.partial ().

Названа агрегація також справедлива для групових агрегацій серії. У цьому випадку немає вибору стовпців, тому значення - це лише функції.

    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

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

Я сподіваюся, що це допомагає.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.