come dividere la colonna di tuple in dataframe panda?


91

Ho un dataframe panda (questo è solo un piccolo pezzo)

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

Voglio dividere tutte le colonne che contengono tuple. Ad esempio, voglio sostituire la colonna LCVcon le colonne LCV-ae LCV-b.

Come lo posso fare?

Risposte:


167

Puoi farlo facendo pd.DataFrame(col.tolist())su quella colonna:

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

Nota: in una versione precedente, questa risposta consigliava di utilizzare al df['b'].apply(pd.Series)posto di pd.DataFrame(df['b'].tolist(), index=df.index). Funziona anche questo (perché fa di ogni tupla una serie, che viene quindi vista come una riga di un dataframe), ma è più lenta / utilizza più memoria della tolistversione, come notato dalle altre risposte qui (grazie a @denfromufa) .
Ho aggiornato questa risposta per assicurarmi che la risposta più visibile abbia la soluzione migliore.


2
c'è un modo per automatizzarlo a causa del gran numero di colonne?
Donbeo

Non direttamente credo. Ma puoi facilmente scrivere una funzione per esso usando il codice sopra (+ rimuovendo quello originale)
joris

Se hai un gran numero di colonne, potresti prendere in considerazione l' idea di "riordinare" i tuoi dati: vita.had.co.nz/papers/tidy-data.html Puoi farlo utilizzando la funzione di fusione.
Axel

.apply (pd.Series) funziona bene, ma per set di dati di grandi dimensioni consuma molta memoria e può causare errori di memoria
Yury Wallet

27

Su set di dati molto più grandi, ho scoperto che .apply()sono pochi gli ordini più lenti dipd.DataFrame(df['b'].values.tolist(), index=df.index)

Questo problema di prestazioni è stato risolto in GitHub, anche se non sono d'accordo con questa decisione:

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

EDIT: in base a questa risposta: https://stackoverflow.com/a/44196843/2230844


5
pd.DataFrame(df['b'].tolist())anche senza .valuessembra funzionare bene. (E grazie, la tua soluzione è molto più veloce di .apply())
Swier

Ero preoccupato per l'acquisizione dell'indice, quindi l'uso esplicito di .values.
denfromufa

1
soluzione di @denfromufa funziona super veloce df [['b1', 'b2']] = pd.DataFrame (df ['b']. values.tolist (), index = df.index) e non causa errori di memoria (come rispetto a .apply (pd.Series))
Yury Wallet

22

La funzione di straccesso disponibile per gli pandas.Seriesoggetti di dtype == objectè effettivamente un iterabile.

Supponiamo che 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)

Possiamo verificare se è un iterabile

from collections import Iterable

isinstance(df.col.str, Iterable)

True

Possiamo quindi assegnare da esso come facciamo con altri iterabili:

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

x y

La soluzione più semplice

Quindi in una riga possiamo assegnare entrambe le colonne

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

Soluzione più veloce

Solo leggermente più complicato, possiamo usare zipper creare un iterabile simile

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

In linea

Significato, non modificare l'esistente df
Questo funziona perché assignaccetta argomenti di parole chiave dove le parole chiave sono i nomi di colonna nuovi (o esistenti) ei valori saranno i valori della nuova colonna. È possibile utilizzare un dizionario e decomprimerlo **e farlo funzionare come argomento della parola chiave. Quindi questo è un modo intelligente per assegnare una nuova colonna denominata 'g'che è il primo elemento df.col.strnell'iterabile e 'h'che è il secondo elemento df.col.strnell'iterabile.

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

La mia versione listdell'approccio

Con moderna comprensione delle liste e spacchettamento delle variabili.
Nota: anche inline utilizzandojoin

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

La versione mutante sarebbe

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

Naive Time Test

DataFrame breve

Usane uno definito sopra

%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 lungo

10 ^ 3 volte più grande

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)

2
Considera l' df['a'], df['b'] = df.col.str
idea di

11

Penso che un modo più semplice sia:

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

1
Questa soluzione è davvero molto più semplice
ApplePie

@jinhuawang sembra che questo sia un trucco sopra la strrappresentazione di un pd.Seriesoggetto. Puoi spiegare come funziona ?!
denfromufa

Penso che sia proprio come funziona l'oggetto str? puoi accedere all'oggetto dell'array con str
Jinhua Wang

E se alcune delle righe hanno tuple con un diverso numero di valori?
mammykins

Penso che questo dovrebbe essere quello accettato. È più "panda-onico" ... se è una cosa.
Natacha

8

So che è di tempo fa, ma un avvertimento sulla seconda soluzione:

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

è che eliminerà esplicitamente l'indice e aggiungerà un indice sequenziale predefinito, mentre la risposta accettata

apply(pd.Series)

non lo farà, poiché il risultato di apply manterrà l'indice di riga. Sebbene l'ordine sia inizialmente mantenuto dall'array originale, i panda cercheranno di abbinare i valori dei due frame di dati.

Questo può essere molto importante se stai cercando di impostare le righe in un array indicizzato numericamente, ei panda cercheranno automaticamente di far corrispondere l'indice del nuovo array al vecchio, causando una certa distorsione nell'ordinamento.

Una soluzione ibrida migliore sarebbe impostare l'indice del dataframe originale sul nuovo, ad es

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

Che manterrà la velocità di utilizzo del secondo metodo assicurando che l'ordine e l'indicizzazione vengano mantenuti sul risultato.


Ho modificato la mia risposta in base alla tua osservazione di indicizzazione, grazie!
denfromufa
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.