web-dev-qa-db-fra.com

comment diviser une colonne de tuples dans pandas dataframe?

J'ai un pandas dataframe (ce n'est qu'un petit morceau)

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

Je veux fractionner toutes les colonnes qui contiennent des n-uplets. Par exemple, je veux remplacer la colonne LCV par les colonnes LCV-a et LCV-b.

Comment puis je faire ça?

62
Donbeo

Vous pouvez faire cela en faisant pd.DataFrame(col.tolist()) sur cette colonne:

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

Remarque: dans une version antérieure, cette réponse recommandait d'utiliser df['b'].apply(pd.Series) au lieu de pd.DataFrame(df['b'].tolist(), index=df.index). Cela fonctionne aussi bien (parce que chaque tuple est une série, qui est alors vue comme une ligne d’un cadre de données), mais est plus lent/utilise plus de mémoire que la version tolist, comme l’indiquent les autres réponses ici. (merci à @denfromufa).
J'ai mis à jour cette réponse pour que la réponse la plus visible soit celle qui convient le mieux.

113
joris

Sur des jeux de données beaucoup plus volumineux, j’ai constaté que .apply() est quelques ordres plus lent que pd.DataFrame(df['b'].values.tolist(), index=df.index)

Ce problème de performance a été résolu dans GitHub, bien que je ne sois pas d’accord avec cette décision:

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

EDIT: basé sur cette réponse: https://stackoverflow.com/a/44196843/2230844

23
denfromufa

Je sais que cela a été fait il y a quelque temps, mais une mise en garde de la deuxième solution:

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

est qu'il va explicitement ignorer l'index et ajouter un index séquentiel par défaut, alors que la réponse acceptée

apply(pd.Series)

ne sera pas, puisque le résultat de apply conservera l'index de ligne. Tandis que l'ordre est initialement conservé de la matrice d'origine, pandas essaiera de faire correspondre les indications des deux cadres de données.

Cela peut être très important si vous essayez de définir les lignes dans un tableau indexé numériquement et que pandas essaiera automatiquement de faire correspondre l'index du nouveau tableau à l'ancien et causera une certaine distorsion dans l'ordre.

Une meilleure solution hybride consisterait à définir l’index du cadre de données d’origine sur le nouveau, c’est-à-dire.

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

Ce qui conservera la rapidité d'utilisation de la deuxième méthode tout en veillant à ce que l'ordre et l'indexation soient conservés sur le résultat.

8
Mike

Je pense qu'un moyen plus simple est:

>>> 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
6
Jinhua Wang

L'accesseur str disponible pour pandas.Series objets de dtype == object est en fait un itératif.

Supposons un pandas.DataFramedf:

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)

Nous pouvons tester si c'est un itérable

from collections import Iterable

isinstance(df.col.str, Iterable)

True

Nous pouvons ensuite en assigner comme d’autres iterables:

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

x y

Solution la plus simple

Donc, dans une ligne, nous pouvons affecter les deux colonnes

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

Solution plus rapide

Seulement un peu plus compliqué, nous pouvons utiliser Zip pour créer un itérable similaire

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

En ligne

Signification, ne mute pas l'existant df
Cela fonctionne parce que assign prend des arguments de mot-clé, les mots-clés correspondant aux noms de colonne nouveaux (ou existants) et les valeurs correspondant aux valeurs de la nouvelle colonne. Vous pouvez utiliser un dictionnaire et le décompresser avec ** et le faire agir comme argument de mot clé. Il s’agit donc d’une manière intelligente d’affecter une nouvelle colonne nommée 'g' c'est le premier élément de la df.col.str itérable et 'h' c'est le deuxième élément de la df.col.str itérable.

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

Ma version de l'approche list

Avec une compréhension de liste moderne et un déballage variable.
Note: également en ligne avec join

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 version en mutation serait

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

Test du temps naïf

Utilisez celui défini ci-dessus

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

10 ^ 3 fois plus grand

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)
1
piRSquared