Menerapkan banyak fungsi ke beberapa grup dengan kolom

221

The docs menunjukkan bagaimana menerapkan beberapa fungsi pada objek groupby pada waktu menggunakan dict dengan nama kolom output sebagai kunci:

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

Namun, ini hanya berfungsi pada objek Series group oleh. Dan ketika sebuah dikt sama dilewatkan ke grup oleh DataFrame, itu mengharapkan kunci untuk menjadi nama kolom yang fungsi akan diterapkan.

Yang ingin saya lakukan adalah menerapkan beberapa fungsi ke beberapa kolom (tetapi kolom tertentu akan dioperasikan beberapa kali). Juga, beberapa fungsi akan tergantung pada kolom lain di objek groupby (seperti fungsi sumif). Solusi saya saat ini adalah pergi kolom demi kolom, dan melakukan sesuatu seperti kode di atas, menggunakan lambdas untuk fungsi yang bergantung pada baris lain. Tapi ini butuh waktu lama, (saya pikir butuh waktu lama untuk beralih melalui objek grup). Saya harus mengubahnya sehingga saya mengulangi seluruh objek grup dengan sekali jalan, tapi saya ingin tahu apakah ada cara panda untuk melakukan ini dengan agak bersih.

Misalnya, saya sudah mencoba sesuatu seperti

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

tapi seperti yang diharapkan saya mendapatkan KeyError (karena kunci harus berupa kolom jika aggdipanggil dari DataFrame).

Apakah ada cara dibangun untuk melakukan apa yang ingin saya lakukan, atau kemungkinan bahwa fungsi ini dapat ditambahkan, atau apakah saya hanya perlu mengulang melalui grup secara manual?

Terima kasih

jenggot
sumber
2
Jika Anda datang ke pertanyaan ini di 2017+, silakan lihat jawaban di bawah ini untuk melihat cara idiomatis untuk menggabungkan beberapa kolom bersamaan. Jawaban yang dipilih saat ini memiliki beberapa penghentian di dalamnya, yaitu bahwa Anda tidak dapat menggunakan kamus kamus lagi untuk mengubah nama kolom di hasil dari groupby.
Ted Petrou

Jawaban:

282

Bagian kedua dari jawaban yang saat ini diterima sudah usang dan memiliki dua penghinaan. Pertama dan paling penting, Anda tidak bisa lagi meneruskan kamus kamus ke aggmetode groupby. Kedua, jangan pernah gunakan .ix.

Jika Anda ingin bekerja dengan dua kolom terpisah pada saat yang sama saya akan menyarankan menggunakan applymetode yang secara implisit meneruskan DataFrame ke fungsi yang diterapkan. Mari kita gunakan kerangka data yang sama dengan yang ada di atas

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

Kamus yang dipetakan dari nama kolom ke fungsi agregasi masih merupakan cara yang sangat baik untuk melakukan agregasi.

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

Jika Anda tidak menyukai nama kolom lambda yang jelek itu, Anda dapat menggunakan fungsi normal dan memberikan nama khusus ke __name__atribut khusus seperti ini:

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

Menggunakan applydan mengembalikan Seri

Sekarang, jika Anda memiliki beberapa kolom yang perlu berinteraksi bersama maka Anda tidak dapat menggunakan agg, yang secara implisit meneruskan Seri ke fungsi agregasi. Saat menggunakan applyseluruh grup sebagai DataFrame masuk ke fungsi.

Saya sarankan membuat fungsi kustom tunggal yang mengembalikan Seri semua agregasi. Gunakan indeks Seri sebagai label untuk kolom baru:

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

Jika Anda jatuh cinta dengan MultiIndexes, Anda masih dapat mengembalikan Seri dengan yang seperti ini:

    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
Ted Petrou
sumber
3
Saya suka pola menggunakan fungsi yang mengembalikan seri. Sangat rapi.
Stephen McAteer
2
ini adalah satu-satunya cara yang saya temukan untuk mengagregasi dataframe melalui beberapa input kolom secara simulatneosly (contoh c_d di atas)
Blake
2
Saya bingung dengan hasilnya, mengambil penjumlahan dari adalam kelompok 0seharusnya tidak 0.418500 + 0.446069 = 0.864569? Hal yang sama berlaku untuk sel-sel lain, jumlahnya tampaknya tidak bertambah. Mungkinkah kerangka data dasar yang sedikit berbeda digunakan dalam contoh-contoh selanjutnya?
slackline
Saya sering menggunakan .size () dengan groupby untuk melihat jumlah catatan. Apakah ada cara untuk melakukan ini menggunakan metode agg: dict? Saya mengerti bahwa saya dapat menghitung bidang tertentu, tetapi preferensi saya adalah penghitungan untuk menjadi bidang-independen.
Chris Decker
1
@ slackline ya. Saya baru saja mengujinya dan berfungsi dengan baik. Ted pasti baru saja membuat bingkai beberapa waktu yang berbeda dan karena itu dibuat melalui generasi nomor acak, data df untuk benar-benar menghasilkan data berbeda dari yang akhirnya digunakan dalam perhitungan
Lucas H
166

Untuk bagian pertama, Anda dapat melewati dict nama kolom untuk kunci dan daftar fungsi untuk nilai:

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

PEMBARUAN 1:

Karena fungsi agregat bekerja pada Seri, referensi ke nama kolom lainnya hilang. Untuk menyiasatinya, Anda dapat mereferensikan dataframe lengkap dan mengindeksnya menggunakan indeks grup dalam fungsi lambda.

Berikut ini solusinya:

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

Di sini, kolom 'D' yang dihasilkan terdiri dari nilai-nilai 'E' yang dijumlahkan.

PEMBARUAN 2:

Inilah metode yang saya pikir akan melakukan semua yang Anda minta. Pertama buat fungsi lambda khusus. Di bawah, g referensi grup. Saat menjumlahkan, g akan menjadi sebuah Seri. Lulus g.indexuntuk df.ix[]memilih grup saat ini dari df. Saya kemudian menguji apakah kolom C kurang dari 0,5. Seri boolean yang dikembalikan dilewatkan ke g[]yang memilih hanya baris yang memenuhi kriteria.

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
sumber
Menarik, saya juga bisa memberikan dict {funcname: func}sebagai nilai alih-alih daftar untuk menjaga nama kustom saya. Tetapi dalam kedua kasus saya tidak bisa melewati lambdayang menggunakan kolom lain (seperti di lambda x: x['D'][x['C'] < 3].sum()atas: "KeyError: 'D'"). Adakah ide jika itu mungkin?
beardc
Saya sudah mencoba melakukan hal itu, dan saya mendapatkan kesalahanKeyError: 'D'
Zelazny7
Keren, aku harus bekerja dengannya df['A'].ix[g.index][df['C'] < 0].sum(). Ini mulai menjadi sangat berantakan, meskipun - saya pikir untuk pengacakan manual keterbacaan mungkin lebih disukai, ditambah saya tidak yakin ada cara untuk memberikan nama pilihan saya dalam aggargumen (bukan <lambda>). Saya akan mengulurkan harapan bahwa seseorang mungkin tahu cara yang lebih mudah ...
beardc
3
Anda dapat melewati dict untuk nilai kolom {'D': {'my name':lambda function}}dan itu akan membuat kunci dict bagian dalam nama kolom.
Zelazny7
1
Saya percaya bahwa panda sekarang mendukung banyak fungsi yang diterapkan pada kerangka data yang dikelompokkan: pandas.pydata.org/pandas-docs/stable/…
IanS
22

Sebagai alternatif (sebagian besar pada estetika) untuk jawaban Ted Petrou, saya menemukan saya lebih suka daftar yang sedikit lebih kompak. Tolong jangan mempertimbangkan untuk menerimanya, itu hanya komentar yang jauh lebih rinci tentang jawaban Ted, ditambah kode / data. Python / panda bukan yang pertama / terbaik saya, tetapi saya menemukan ini untuk dibaca dengan baik:

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

Saya merasa lebih mirip dplyrpipa dan data.tableperintah dirantai. Bukan untuk mengatakan mereka lebih baik, hanya lebih akrab bagi saya. (Saya tentu mengenali kekuatan dan, bagi banyak orang, preferensi untuk menggunakan deffungsi yang lebih formal untuk jenis operasi ini. Ini hanya sebuah alternatif, belum tentu lebih baik.)


Saya menghasilkan data dengan cara yang sama seperti Ted, saya akan menambahkan benih untuk reproduktifitas.

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
sumber
2
Saya paling suka jawaban ini. Ini mirip dengan pipa dplyr di R.
Renhuai
18

Pandas >= 0.25.0, bernama agregasi

Sejak versi panda 0.25.0atau lebih tinggi, kami menjauh dari agregasi dan penamaan ulang berdasarkan kamus, dan bergerak menuju agregasi bernama yang menerima a tuple. Sekarang kita dapat secara bersamaan mengumpulkan + mengubah nama menjadi nama kolom yang lebih informatif:

Contoh :

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

Terapkan GroupBy.aggdengan agregasi bernama:

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
sumber
Saya suka agregasi bernama ini, tetapi saya tidak bisa melihat bagaimana kita seharusnya menggunakannya dengan banyak kolom?
Simon Woodhead
Pertanyaan bagus, tidak dapat menemukan ini, meragukan ini mungkin (belum). Saya membuka tiket untuk ini. Akan menyimpan pertanyaan saya dan Anda diperbarui. Terima kasih telah menunjukkan @SimonWoodhead
Erfan
4

Baru dalam versi 0.25.0.

Untuk mendukung agregasi khusus kolom dengan kontrol atas nama kolom output, panda menerima sintaks khusus di GroupBy.agg () , yang dikenal sebagai "agregasi bernama" , di mana

  • Kata kunci adalah nama kolom output
  • Nilai-nilai adalah tupel yang elemen pertama adalah kolom untuk dipilih dan elemen kedua adalah agregasi untuk diterapkan ke kolom itu. Pandas menyediakan panda.NamedAgg namedtuple dengan bidang ['kolom', 'aggfunc'] untuk membuatnya lebih jelas apa argumennya. Seperti biasa, agregasi dapat berupa callable atau string alias.
    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

panda.NamedAgg hanyalah sebuah namedTuple. Tuple polos juga diizinkan.

    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

Argumen kata kunci tambahan tidak diteruskan ke fungsi agregasi. Hanya pasangan (kolom, aggfunc) yang harus dilewatkan sebagai ** kwargs. Jika fungsi agregasi Anda memerlukan argumen tambahan, terapkan sebagian dengan functools.partial ().

Agregasi yang dinamai juga berlaku untuk grup Agregasi oleh agregasi. Dalam hal ini tidak ada pemilihan kolom, jadi nilainya hanya fungsi.

    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
sumber
3

Jawaban Ted luar biasa. Saya akhirnya menggunakan versi yang lebih kecil kalau-kalau ada yang tertarik. Berguna saat Anda mencari satu agregasi yang bergantung pada nilai dari beberapa kolom:

buat dataframe

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

pengelompokan dan agregasi dengan berlaku (menggunakan beberapa kolom)

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

pengelompokan dan agregasi dengan agregat (menggunakan beberapa kolom)

Saya suka pendekatan ini karena saya masih bisa menggunakan agregat. Mungkin orang akan memberi tahu saya mengapa pendaftaran diperlukan untuk mendapatkan beberapa kolom saat melakukan agregasi pada grup.

Tampaknya sudah jelas sekarang, tetapi selama Anda tidak memilih kolom yang diinginkan langsung setelah grup oleh , Anda akan memiliki akses ke semua kolom dari kerangka data dari dalam fungsi agregasi Anda.

hanya akses ke kolom yang dipilih

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

akses ke semua kolom karena seleksi adalah keajaiban

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

atau serupa

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

Saya harap ini membantu.

campo
sumber