Pandas groupby: Cara mendapatkan penyatuan string

122

Saya memiliki dataframe seperti ini:

   A         B       C
0  1  0.749065    This
1  2  0.301084      is
2  3  0.463468       a
3  4  0.643961  random
4  1  0.866521  string
5  2  0.120737       !

Panggilan

In [10]: print df.groupby("A")["B"].sum()

akan kembali

A
1    1.615586
2    0.421821
3    0.463468
4    0.643961

Sekarang saya ingin melakukan "hal yang sama" untuk kolom "C". Karena kolom itu berisi string, sum () tidak berfungsi (meskipun Anda mungkin berpikir bahwa itu akan menggabungkan string). Apa yang benar-benar ingin saya lihat adalah daftar atau kumpulan string untuk setiap kelompok, yaitu

A
1    {This, string}
2    {is, !}
3    {a}
4    {random}

Saya telah mencoba menemukan cara untuk melakukan ini.

Series.unique () ( http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.unique.html ) tidak berfungsi, meskipun

df.groupby("A")["B"]

adalah

pandas.core.groupby.SeriesGroupBy object

jadi saya berharap metode Seri apa pun akan berhasil. Ada ide?

Anne
sumber

Jawaban:

178
In [4]: df = read_csv(StringIO(data),sep='\s+')

In [5]: df
Out[5]: 
   A         B       C
0  1  0.749065    This
1  2  0.301084      is
2  3  0.463468       a
3  4  0.643961  random
4  1  0.866521  string
5  2  0.120737       !

In [6]: df.dtypes
Out[6]: 
A      int64
B    float64
C     object
dtype: object

Saat Anda menerapkan fungsi Anda sendiri, tidak ada pengecualian otomatis untuk kolom non-numerik. Namun, ini lebih lambat daripada penerapan .sum()kegroupby

In [8]: df.groupby('A').apply(lambda x: x.sum())
Out[8]: 
   A         B           C
A                         
1  2  1.615586  Thisstring
2  4  0.421821         is!
3  3  0.463468           a
4  4  0.643961      random

sum secara default menggabungkan

In [9]: df.groupby('A')['C'].apply(lambda x: x.sum())
Out[9]: 
A
1    Thisstring
2           is!
3             a
4        random
dtype: object

Anda dapat melakukan apa saja yang Anda inginkan

In [11]: df.groupby('A')['C'].apply(lambda x: "{%s}" % ', '.join(x))
Out[11]: 
A
1    {This, string}
2           {is, !}
3               {a}
4          {random}
dtype: object

Melakukan ini di seluruh bingkai, satu kelompok pada satu waktu. Kuncinya adalah mengembalikan aSeries

def f(x):
     return Series(dict(A = x['A'].sum(), 
                        B = x['B'].sum(), 
                        C = "{%s}" % ', '.join(x['C'])))

In [14]: df.groupby('A').apply(f)
Out[14]: 
   A         B               C
A                             
1  2  1.615586  {This, string}
2  4  0.421821         {is, !}
3  3  0.463468             {a}
4  4  0.643961        {random}
Jeff
sumber
Tampaknya operasi ini sekarang di-vectorisasi sehingga tidak perlu applydan lambdas. Saya datang ke sini bertanya-tanya mengapa pandassebenarnya concats dan tidak mengembalikan kesalahan pada string penjumlahan.
NelsonGon
1
Jika Anda mencoba menggabungkan string dan menambahkan karakter di antaranya, solusi .agg yang direkomendasikan oleh @voithos di bawah ini jauh lebih cepat daripada .apply yang direkomendasikan di sini. Dalam pengujian saya, saya mendapatkan 5-10x lebih cepat.
Doubledown
70

Anda dapat menggunakan applymetode ini untuk menerapkan fungsi arbitrer ke data yang dikelompokkan. Jadi jika Anda menginginkan satu set, terapkan set. Jika Anda menginginkan daftar, terapkan list.

>>> d
   A       B
0  1    This
1  2      is
2  3       a
3  4  random
4  1  string
5  2       !
>>> d.groupby('A')['B'].apply(list)
A
1    [This, string]
2           [is, !]
3               [a]
4          [random]
dtype: object

Jika Anda menginginkan sesuatu yang lain, cukup tulis fungsi yang melakukan apa yang Anda inginkan dan kemudian applyitu.

BrenBarn
sumber
Bekerja dengan baik, tetapi Kolom A hilang.
Vineesh TP
@VineeshTP: Kolom A digunakan sebagai kolom pengelompokan, jadi berada di indeks, seperti yang Anda lihat di contoh. Anda bisa mendapatkannya kembali sebagai kolom dengan menggunakan .reset_index().
BrenBarn
30

Anda mungkin dapat menggunakan fungsi aggregate(atau agg) untuk menggabungkan nilai. (Kode belum teruji)

df.groupby('A')['B'].agg(lambda col: ''.join(col))
voithos
sumber
Ini benar-benar berhasil. Luar biasa. Seperti yang disebut @voithos "belum teruji", saya tidak terlalu optimis. Sedikit saya menguji versinya sebagai entri dalam kamus agg dan berfungsi sebagaimana mestinya: .agg ({'tp': 'sum', 'BaseWgt': 'max', 'TP_short': lambda col: ',' .join (col)}) Membuat hari saya
matthhias
2
Jika Anda mencoba menggabungkan string dengan beberapa jenis pemisah, menurut saya saran .agg ini jauh lebih cepat daripada .apply. Untuk kumpulan data 600k + string teks, saya mendapatkan hasil yang identik 5-10x lebih cepat.
Doubledown
14

Anda bisa mencoba ini:

df.groupby('A').agg({'B':'sum','C':'-'.join})
pengguna3241146
sumber
2
Dari ulasan: bisakah Anda menambahkan lebih banyak penjelasan untuk jawaban Anda?
toti08
1
Groupby diterapkan pada kolom 'A' dan dengan fungsi agg saya dapat menggunakan fungsi yang berbeda pada kolom yang berbeda katakanlah jumlah elemen di kolom 'C', gabungkan elemen di kolom 'C' sambil menyisipkan '-' di antara kata-kata
user3241146
8

solusi sederhana adalah:

>>> df.groupby(['A','B']).c.unique().reset_index()
UserYmY
sumber
ini seharusnya jawaban yang benar. membuat Anda menjawab dengan bersih. Terima kasih banyak!
imsrgadich
Jika seandainya seseorang tertarik untuk menggabungkan konten daftar menjadi string df.groupby(['A','B']).c.unique().apply(lambda x: ';'.join(x)).reset_index()
Vivek-Ananth
8

Agregasi bernama dengan pandas >= 0.25.0

Sejak pandas versi 0.25.0 kami telah menamai agregasi di mana kami dapat mengelompokkan, menggabungkan, dan pada saat yang sama menetapkan nama baru ke kolom kami. Dengan cara ini kita tidak akan mendapatkan kolom MultiIndex, dan nama kolom lebih masuk akal mengingat data di dalamnya:


mengumpulkan dan mendapatkan daftar string

grp = df.groupby('A').agg(B_sum=('B','sum'),
                          C=('C', list)).reset_index()

print(grp)
   A     B_sum               C
0  1  1.615586  [This, string]
1  2  0.421821         [is, !]
2  3  0.463468             [a]
3  4  0.643961        [random]

menggabungkan dan menggabungkan string

grp = df.groupby('A').agg(B_sum=('B','sum'),
                          C=('C', ', '.join)).reset_index()

print(grp)
   A     B_sum             C
0  1  1.615586  This, string
1  2  0.421821         is, !
2  3  0.463468             a
3  4  0.643961        random
Erfan
sumber
6

Jika Anda ingin menimpa kolom B di dataframe, ini seharusnya berfungsi:

    df = df.groupby('A',as_index=False).agg(lambda x:'\n'.join(x))
Amit
sumber
2

Mengikuti jawaban bagus @ Erfan, sebagian besar waktu dalam analisis nilai agregat Anda menginginkan kombinasi unik yang mungkin dari nilai karakter yang ada ini:

unique_chars = lambda x: ', '.join(x.unique())
(df
 .groupby(['A'])
 .agg({'C': unique_chars}))
Paul Rougieux
sumber