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


The 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). Мое текущее решение - идти столбец за столбцом и делать что-то вроде кода выше, используя лямбды для функций, которые зависят от других строк. Но это занимает много времени (я думаю, что требуется много времени для итерации через объект 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 106

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 до 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 метод, который имплицитно передает фрейм данных прикладной функции. Давайте использовать аналогичный фрейм данных, как один из выше

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

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

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

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

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

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

Я надеюсь, что это помогает.