seleccionar - separar por caracter python
pandas: ¿cómo puedo dividir texto en una columna en varias filas? (3)
Estoy trabajando con un gran archivo csv y la penúltima columna tiene una cadena de texto que quiero dividir por un delimitador específico. Me preguntaba si existe una forma simple de hacerlo con pandas o pitón.
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
Quiero dividir por el espacio ('' '')
y luego los dos puntos ('':'')
en la columna Seatblocks
, pero cada celda daría como resultado un número diferente de columnas. Tengo una función para reorganizar las columnas para que la columna Seatblocks
esté al final de la hoja, pero no estoy seguro de qué hacer a partir de ahí. Puedo hacerlo en Excel con la función incorporada de text-to-columns
y una macro rápida, pero mi conjunto de datos tiene demasiados registros para que los maneje Excel.
En última instancia, quiero tomar registros como el de John Lennon y crear varias líneas, con la información de cada conjunto de asientos en una línea separada.
A diferencia de Dan, considero su respuesta bastante elegante ... pero desafortunadamente también es muy muy ineficiente. Entonces, como la pregunta mencionaba "un gran archivo csv" , permítanme sugerirle que intente con una solución de shell de 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()"
... en comparación con esta alternativa:
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()"
... y esto:
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()"
El segundo simplemente se abstiene de asignar 100 000 Series, y esto es suficiente para que sea alrededor de 10 veces más rápido. Pero la tercera solución, que de manera irónica desperdicia muchas llamadas a str.split () (se llama una vez por columna por fila, por lo que tres veces más que las otras dos soluciones), es alrededor de 40 veces más rápida que la primera, porque incluso evita incluir las 100 000 listas. Y sí, sin duda es un poco feo ...
EDITAR: esta respuesta sugiere cómo usar "to_list ()" y evitar la necesidad de un lambda. El resultado es algo así como
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()"
que es incluso más eficiente que la tercera solución, y ciertamente mucho más elegante.
EDITAR: el aún más simple
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()"
funciona también, y es casi tan eficiente.
EDITAR: ¡ aún más simple ! Y maneja NaNs (pero menos eficiente):
time python -c "import pandas as pd;
df = pd.DataFrame([''a b c'']*100000, columns=[''col'']);
print df.col.str.split(expand=True).head()"
Esto divide los Seatblocks por espacio y le da a cada uno su propia fila.
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
O, para dar a cada cadena separada por dos puntos en su propia columna:
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
Esto es un poco feo, pero tal vez alguien repita con una solución más bonita.
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
Otra solución similar con el encadenamiento es usar reset_index
y 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 en la columna NO están los valores NaN
, la solución más rápida es la comprensión de la list
uso con el constructor de 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
Pero si la columna contiene NaN
solo funciona str.split
con el parámetro expand=True
que devuelve DataFrame
( documentation ), y explica por qué es más lento:
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