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

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


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

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

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

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

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

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

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

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

df.groupby('c')['a'].aggregate(lambda x: x[x>1].mean())

доступ ко всем столбцам, так как выбор в конце концов магия!--20-->
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())

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