bagaimana cara membagi kolom tupel di pandas dataframe?

91

Saya memiliki kerangka data panda (ini hanya sebagian kecil)

>>> d1
   y norm test  y norm train  len(y_train)  len(y_test)  \
0    64.904368    116.151232          1645          549   
1    70.852681    112.639876          1645          549   

                                    SVR RBF  \
0   (35.652207342877873, 22.95533537448393)   
1  (39.563683797747622, 27.382483096332511)   

                                        LCV  \
0  (19.365430594452338, 13.880062435173587)   
1  (19.099614489458364, 14.018867136617146)   

                                   RIDGE CV  \
0  (4.2907610988480362, 12.416745648065584)   
1    (4.18864306788194, 12.980833914392477)   

                                         RF  \
0   (9.9484841581029428, 16.46902345373697)   
1  (10.139848213735391, 16.282141345406522)   

                                           GB  \
0  (0.012816232716538605, 15.950164822266007)   
1  (0.012814519804493328, 15.305745202851712)   

                                             ET DATA  
0  (0.00034337162272515505, 16.284800366214057)  j2m  
1  (0.00024811554516431878, 15.556506191784194)  j2m  
>>> 

Saya ingin membagi semua kolom yang berisi tupel. Misalnya saya ingin mengganti kolom LCVdengan kolom LCV-adan LCV-b.

Bagaimana saya bisa melakukan itu?

Donbeo
sumber

Jawaban:

167

Anda dapat melakukan ini dengan melakukan pd.DataFrame(col.tolist())di kolom itu:

In [2]: df = pd.DataFrame({'a':[1,2], 'b':[(1,2), (3,4)]})                                                                                                                      

In [3]: df                                                                                                                                                                      
Out[3]: 
   a       b
0  1  (1, 2)
1  2  (3, 4)

In [4]: df['b'].tolist()                                                                                                                                                        
Out[4]: [(1, 2), (3, 4)]

In [5]: pd.DataFrame(df['b'].tolist(), index=df.index)                                                                                                                                          
Out[5]: 
   0  1
0  1  2
1  3  4

In [6]: df[['b1', 'b2']] = pd.DataFrame(df['b'].tolist(), index=df.index)                                                                                                                       

In [7]: df                                                                                                                                                                      
Out[7]: 
   a       b  b1  b2
0  1  (1, 2)   1   2
1  2  (3, 4)   3   4

Catatan: di versi sebelumnya, jawaban ini disarankan untuk digunakan df['b'].apply(pd.Series)sebagai pengganti pd.DataFrame(df['b'].tolist(), index=df.index). Itu berfungsi juga (karena itu membuat setiap tupel Seri, yang kemudian dilihat sebagai deretan kerangka data), tetapi lebih lambat / menggunakan lebih banyak memori daripada tolistversinya, seperti yang dicatat oleh jawaban lain di sini (terima kasih kepada @denfromufa) .
Saya memperbarui jawaban ini untuk memastikan jawaban yang paling terlihat memiliki solusi terbaik.

joris
sumber
2
apakah ada cara untuk mengotomatiskannya karena banyaknya kolom?
Donbeo
Tidak secara langsung menurut saya. Tetapi Anda dapat dengan mudah menulis fungsi untuk itu menggunakan kode di atas (+ menghapus yang asli)
joris
Jika Anda memiliki banyak kolom, Anda mungkin ingin mempertimbangkan untuk 'merapikan' data Anda: vita.had.co.nz/papers/tidy-data.html Anda dapat melakukan ini menggunakan fungsi melt.
Axel
.apply (pd.Series) berfungsi dengan baik, tetapi untuk kumpulan data besar menghabiskan banyak memori dan dapat menyebabkan Kesalahan Memori
Yury Wallet
27

Pada kumpulan data yang jauh lebih besar, saya menemukan bahwa .apply()beberapa pesanan lebih lambat daripd.DataFrame(df['b'].values.tolist(), index=df.index)

Masalah kinerja ini ditutup di GitHub, meskipun saya tidak setuju dengan keputusan ini:

https://github.com/pandas-dev/pandas/issues/11615

EDIT: berdasarkan jawaban ini: https://stackoverflow.com/a/44196843/2230844

denfromufa
sumber
5
pd.DataFrame(df['b'].tolist())tanpa .valuestampaknya bekerja dengan baik juga. (Dan terima kasih, solusi Anda jauh lebih cepat daripada .apply())
Swier
Saya khawatir tentang menangkap indeks, karenanya penggunaan .values ​​eksplisit.
denfromufa
1
solusi oleh @denfromufa bekerja super cepat df [['b1', 'b2']] = pd.DataFrame (df ['b']. values.tolist (), index = df.index) dan tidak menyebabkan Kesalahan Memori (seperti dibandingkan dengan .apply (pd.Series))
Yury Wallet
22

The straccessor yang tersedia untuk pandas.Seriesobjek dtype == objectsebenarnya merupakan iterable.

Asumsikan pandas.DataFrame df:

df = pd.DataFrame(dict(col=[*zip('abcdefghij', range(10, 101, 10))]))

df

        col
0   (a, 10)
1   (b, 20)
2   (c, 30)
3   (d, 40)
4   (e, 50)
5   (f, 60)
6   (g, 70)
7   (h, 80)
8   (i, 90)
9  (j, 100)

Kami dapat menguji apakah itu dapat diulang

from collections import Iterable

isinstance(df.col.str, Iterable)

True

Kami kemudian dapat menetapkan darinya seperti kami melakukan iterable lainnya:

var0, var1 = 'xy'
print(var0, var1)

x y

Solusi paling sederhana

Jadi dalam satu baris kita dapat menetapkan kedua kolom tersebut

df['a'], df['b'] = df.col.str

df

        col  a    b
0   (a, 10)  a   10
1   (b, 20)  b   20
2   (c, 30)  c   30
3   (d, 40)  d   40
4   (e, 50)  e   50
5   (f, 60)  f   60
6   (g, 70)  g   70
7   (h, 80)  h   80
8   (i, 90)  i   90
9  (j, 100)  j  100

Solusi lebih cepat

Hanya sedikit lebih rumit, kita dapat menggunakan zipuntuk membuat iterable serupa

df['c'], df['d'] = zip(*df.col)

df

        col  a    b  c    d
0   (a, 10)  a   10  a   10
1   (b, 20)  b   20  b   20
2   (c, 30)  c   30  c   30
3   (d, 40)  d   40  d   40
4   (e, 50)  e   50  e   50
5   (f, 60)  f   60  f   60
6   (g, 70)  g   70  g   70
7   (h, 80)  h   80  h   80
8   (i, 90)  i   90  i   90
9  (j, 100)  j  100  j  100

Di barisan

Artinya, jangan mutasi yang sudah ada df
Ini berfungsi karena assignmengambil argumen kata kunci di mana kata kuncinya adalah nama kolom baru (atau yang sudah ada) dan nilainya akan menjadi nilai kolom baru. Anda dapat menggunakan kamus dan mengekstraknya **serta bertindak sebagai argumen kata kunci. Jadi ini adalah cara cerdas untuk menetapkan kolom baru bernama 'g'item pertama di df.col.striterable dan 'h'itu adalah item kedua di df.col.striterable.

df.assign(**dict(zip('gh', df.col.str)))

        col  g    h
0   (a, 10)  a   10
1   (b, 20)  b   20
2   (c, 30)  c   30
3   (d, 40)  d   40
4   (e, 50)  e   50
5   (f, 60)  f   60
6   (g, 70)  g   70
7   (h, 80)  h   80
8   (i, 90)  i   90
9  (j, 100)  j  100

listPendekatan versi saya

Dengan pemahaman daftar modern dan pembongkaran variabel.
Catatan: juga menggunakan inlinejoin

df.join(pd.DataFrame([*df.col], df.index, [*'ef']))

        col  g    h
0   (a, 10)  a   10
1   (b, 20)  b   20
2   (c, 30)  c   30
3   (d, 40)  d   40
4   (e, 50)  e   50
5   (f, 60)  f   60
6   (g, 70)  g   70
7   (h, 80)  h   80
8   (i, 90)  i   90
9  (j, 100)  j  100

Versi mutasi akan menjadi

df[['e', 'f']] = pd.DataFrame([*df.col], df.index)

Tes Waktu yang Naif

DataFrame pendek

Gunakan salah satu yang ditentukan di atas

%timeit df.assign(**dict(zip('gh', df.col.str)))
%timeit df.assign(**dict(zip('gh', zip(*df.col))))
%timeit df.join(pd.DataFrame([*df.col], df.index, [*'gh']))

1.16 ms ± 21.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
635 µs ± 18.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
795 µs ± 42.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
DataFrame Panjang

10 ^ 3 kali lebih besar

df = pd.concat([df] * 1000, ignore_index=True)

%timeit df.assign(**dict(zip('gh', df.col.str)))
%timeit df.assign(**dict(zip('gh', zip(*df.col))))
%timeit df.join(pd.DataFrame([*df.col], df.index, [*'gh']))

11.4 ms ± 1.53 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.1 ms ± 41.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.33 ms ± 35.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
piRSquared
sumber
2
Pertimbangkan untuk menambahkan TL; DR: df['a'], df['b'] = df.col.str:)
mirekphd
11

Menurut saya cara yang lebih sederhana adalah:

>>> import pandas as pd
>>> df = pd.DataFrame({'a':[1,2], 'b':[(1,2), (3,4)]}) 
>>> df
   a       b
0  1  (1, 2)
1  2  (3, 4)
>>> df['b_a']=df['b'].str[0]
>>> df['b_b']=df['b'].str[1]
>>> df
   a       b  b_a  b_b
0  1  (1, 2)    1    2
1  2  (3, 4)    3    4
Jinhua Wang
sumber
1
Solusi ini memang jauh lebih sederhana
ApplePie
@jinhuawang tampaknya ini di-hack di atas strrepresentasi dari sebuah pd.Seriesobjek. Bisakah Anda menjelaskan bagaimana ini bekerja ?!
denfromufa
Saya pikir itu hanya bagaimana objek str bekerja? Anda dapat mengakses objek array dengan str
Jinhua Wang
Bagaimana jika beberapa baris memiliki tupel dengan jumlah nilai berbeda?
mammykins
Saya pikir ini harus diterima. Ini lebih 'panda-onic' ... jika itu masalahnya.
Natacha
8

Saya tahu ini dari beberapa waktu yang lalu, tetapi peringatan dari solusi kedua:

pd.DataFrame(df['b'].values.tolist())

adalah bahwa ia akan secara eksplisit membuang indeks, dan menambahkan indeks sekuensial default, sedangkan jawaban yang diterima

apply(pd.Series)

tidak akan, karena hasil penerapan akan mempertahankan indeks baris. Sementara urutan awalnya dipertahankan dari larik asli, panda akan mencoba mencocokkan indikasi dari dua kerangka data.

Ini bisa menjadi sangat penting jika Anda mencoba menyetel baris ke dalam larik yang diindeks secara numerik, dan panda akan secara otomatis mencoba mencocokkan indeks larik baru dengan yang lama, dan menyebabkan distorsi dalam urutan.

Solusi hibrid yang lebih baik adalah menyetel indeks kerangka data asli ke yang baru, yaitu

pd.DataFrame(df['b'].values.tolist(), index=df.index)

Yang akan mempertahankan kecepatan menggunakan metode kedua sambil memastikan urutan dan pengindeksan dipertahankan pada hasil.

Mike
sumber
Saya mengedit jawaban saya berdasarkan pengamatan pengindeksan Anda, terima kasih!
denfromufa