Je travaille avec un gros fichier csv et l'avant-dernière colonne contient une chaîne de texte que je souhaite scinder en un délimiteur spécifique. Je me demandais s'il y avait un moyen simple de faire cela en utilisant pandas ou en python?
CustNum CustomerName ItemQty Item Seatblocks ItemExt
32363 McCartney, Paul 3 F04 2:218:10:4,6 60
31316 Lennon, John 25 F01 1:13:36:1,12 1:13:37:1,13 300
Je souhaite scinder l’espace(' ')
, puis le colon(':')
dans la colonne Seatblocks
, mais chaque cellule génère un nombre différent de colonnes. J'ai une fonction pour réorganiser les colonnes afin que la colonne Seatblocks
se trouve à la fin de la feuille, mais je ne sais pas quoi faire à partir de là. Je peux le faire dans Excel avec la fonction intégrée text-to-columns
et une macro rapide, mais mon jeu de données contient trop d'enregistrements à gérer par Excel.
En fin de compte, je veux prendre des disques tels que John Lennon et créer plusieurs lignes, avec les informations de chaque ensemble de sièges sur une ligne distincte.
Cela divise les sièges par espace et donne à chacun sa propre rangée.
In [43]: df
Out[43]:
CustNum CustomerName ItemQty Item Seatblocks ItemExt
0 32363 McCartney, Paul 3 F04 2:218:10:4,6 60
1 31316 Lennon, John 25 F01 1:13:36:1,12 1:13:37:1,13 300
In [44]: s = df['Seatblocks'].str.split(' ').apply(Series, 1).stack()
In [45]: s.index = s.index.droplevel(-1) # to line up with df's index
In [46]: s.name = 'Seatblocks' # needs a name to join
In [47]: s
Out[47]:
0 2:218:10:4,6
1 1:13:36:1,12
1 1:13:37:1,13
Name: Seatblocks, dtype: object
In [48]: del df['Seatblocks']
In [49]: df.join(s)
Out[49]:
CustNum CustomerName ItemQty Item ItemExt Seatblocks
0 32363 McCartney, Paul 3 F04 60 2:218:10:4,6
1 31316 Lennon, John 25 F01 300 1:13:36:1,12
1 31316 Lennon, John 25 F01 300 1:13:37:1,13
Ou, pour donner chaque chaîne séparée par deux-points dans sa propre colonne:
In [50]: df.join(s.apply(lambda x: Series(x.split(':'))))
Out[50]:
CustNum CustomerName ItemQty Item ItemExt 0 1 2 3
0 32363 McCartney, Paul 3 F04 60 2 218 10 4,6
1 31316 Lennon, John 25 F01 300 1 13 36 1,12
1 31316 Lennon, John 25 F01 300 1 13 37 1,13
C’est un peu moche, mais peut-être que quelqu'un va nous donner une solution plus jolie.
Contrairement à Dan, je trouve sa réponse assez élégante ... mais malheureusement, elle est également très très inefficace. Donc, puisque la question mentionnait "un gros fichier csv" , permettez-moi de vous suggérer d'essayer une solution de Shell Dan:
time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print df['col'].apply(lambda x : pd.Series(x.split(' '))).head()"
... par rapport à cette alternative:
time python -c "import pandas as pd;
from scipy import array, concatenate;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print pd.DataFrame(concatenate(df['col'].apply( lambda x : [x.split(' ')]))).head()"
... et ça:
time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print pd.DataFrame(dict(Zip(range(3), [df['col'].apply(lambda x : x.split(' ')[i]) for i in range(3)]))).head()"
La seconde s’abstient tout simplement d’allouer 100 000 séries, ce qui suffit à la rendre environ 10 fois plus rapide. Mais la troisième solution, qui gaspille ironiquement beaucoup d'appels à str.split () (elle est appelée une fois par colonne et par ligne, donc trois fois plus que pour les deux autres solutions), est proche 40 fois plus rapide que le premier, car il évite même d’instituer les 100 000 listes. Et oui, c'est certainement un peu moche ...
EDIT: cette réponse suggère comment utiliser "to_list ()" et éviter la nécessité d'un lambda. Le résultat est quelque chose comme
time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print pd.DataFrame(df.col.str.split().tolist()).head()"
ce qui est encore plus efficace que la troisième solution, et certainement beaucoup plus élégant.
EDIT: le plus simple encore
time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print pd.DataFrame(list(df.col.str.split())).head()"
fonctionne aussi, et est presque aussi efficace.
EDIT: encore plus simple ! Et gère NaNs (mais moins efficace):
time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print df.col.str.split(expand=True).head()"
_import pandas as pd
import numpy as np
df = pd.DataFrame({'ItemQty': {0: 3, 1: 25},
'Seatblocks': {0: '2:218:10:4,6', 1: '1:13:36:1,12 1:13:37:1,13'},
'ItemExt': {0: 60, 1: 300},
'CustomerName': {0: 'McCartney, Paul', 1: 'Lennon, John'},
'CustNum': {0: 32363, 1: 31316},
'Item': {0: 'F04', 1: 'F01'}},
columns=['CustNum','CustomerName','ItemQty','Item','Seatblocks','ItemExt'])
print (df)
CustNum CustomerName ItemQty Item Seatblocks ItemExt
0 32363 McCartney, Paul 3 F04 2:218:10:4,6 60
1 31316 Lennon, John 25 F01 1:13:36:1,12 1:13:37:1,13 300
_
Une autre solution similaire avec chaînage consiste à utiliser reset_index
et rename
:
_print (df.drop('Seatblocks', axis=1)
.join
(
df.Seatblocks
.str
.split(expand=True)
.stack()
.reset_index(drop=True, level=1)
.rename('Seatblocks')
))
CustNum CustomerName ItemQty Item ItemExt Seatblocks
0 32363 McCartney, Paul 3 F04 60 2:218:10:4,6
1 31316 Lennon, John 25 F01 300 1:13:36:1,12
1 31316 Lennon, John 25 F01 300 1:13:37:1,13
_
Si dans la colonne sont PAS les valeurs NaN
, la solution la plus rapide est d'utiliser la compréhension list
avec le constructeur DataFrame
:
_df = pd.DataFrame(['a b c']*100000, columns=['col'])
In [141]: %timeit (pd.DataFrame(dict(Zip(range(3), [df['col'].apply(lambda x : x.split(' ')[i]) for i in range(3)]))))
1 loop, best of 3: 211 ms per loop
In [142]: %timeit (pd.DataFrame(df.col.str.split().tolist()))
10 loops, best of 3: 87.8 ms per loop
In [143]: %timeit (pd.DataFrame(list(df.col.str.split())))
10 loops, best of 3: 86.1 ms per loop
In [144]: %timeit (df.col.str.split(expand=True))
10 loops, best of 3: 156 ms per loop
In [145]: %timeit (pd.DataFrame([ x.split() for x in df['col'].tolist()]))
10 loops, best of 3: 54.1 ms per loop
_
Mais si la colonne contient NaN
ne fonctionne que str.split
avec le paramètre _expand=True
_ qui renvoie DataFrame
( documentation ), et explique pourquoi il est plus lent:
_df = pd.DataFrame(['a b c']*10, columns=['col'])
df.loc[0] = np.nan
print (df.head())
col
0 NaN
1 a b c
2 a b c
3 a b c
4 a b c
print (df.col.str.split(expand=True))
0 1 2
0 NaN None None
1 a b c
2 a b c
3 a b c
4 a b c
5 a b c
6 a b c
7 a b c
8 a b c
9 a b c
_
Peut aussi utiliser groupby () sans avoir besoin de rejoindre et de stack ().
Utilisez les exemples ci-dessus:
import pandas as pd
import numpy as np
df = pd.DataFrame({'ItemQty': {0: 3, 1: 25},
'Seatblocks': {0: '2:218:10:4,6', 1: '1:13:36:1,12 1:13:37:1,13'},
'ItemExt': {0: 60, 1: 300},
'CustomerName': {0: 'McCartney, Paul', 1: 'Lennon, John'},
'CustNum': {0: 32363, 1: 31316},
'Item': {0: 'F04', 1: 'F01'}},
columns=['CustNum','CustomerName','ItemQty','Item','Seatblocks','ItemExt'])
print(df)
CustNum CustomerName ItemQty Item Seatblocks ItemExt
0 32363 McCartney, Paul 3 F04 2:218:10:4,6 60
1 31316 Lennon, John 25 F01 1:13:36:1,12 1:13:37:1,13 300
#first define a function: given a Series of string, split each element into a new series
def split_series(ser,sep):
return pd.Series(ser.str.cat(sep=sep).split(sep=sep))
#test the function,
split_series(pd.Series(['a b','c']),sep=' ')
0 a
1 b
2 c
dtype: object
df2=(df.groupby(df.columns.drop('Seatblocks').tolist()) #group by all but one column
['Seatblocks'] #select the column to be split
.apply(split_series,sep=' ') # split 'Seatblocks' in each group
.reset_index(drop=True,level=-1).reset_index()) #remove extra index created
print(df2)
CustNum CustomerName ItemQty Item ItemExt Seatblocks
0 31316 Lennon, John 25 F01 300 1:13:36:1,12
1 31316 Lennon, John 25 F01 300 1:13:37:1,13
2 32363 McCartney, Paul 3 F04 60 2:218:10:4,6