pandas: ¿Cómo divido el texto en una columna en varias filas?

Estoy trabajando con un archivo csv grande y la siguiente a la última columna tiene una cadena de texto que quiero dividir por un delimitador específico. Me preguntaba si hay una forma sencilla de hacerlo utilizando pandas o 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 

Quiero dividir por el espacio (' ') y luego los dos puntos (':') en la columna Seatblocks , pero cada celda resultará en un número diferente de columnas. Tengo una función para reorganizar las columnas de modo que la columna de 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 integrada de text-to-columns y una macro rápida, pero mi conjunto de datos tiene demasiados registros para que Excel pueda manejarlos.

En última instancia, quiero tomar registros como los de John Lennon y crear varias líneas, con la información de cada conjunto de asientos en una línea separada.

Esto divide los bloques de asientos 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 se involucre con una solución más bonita.

A diferencia de Dan, considero que su respuesta es bastante elegante … pero, desafortunadamente, también es muy ineficiente. Entonces, ya que la pregunta mencionó “un gran archivo csv” , permítame sugerir que intente en una solución de shell de Dan:

 time python -c "import pandas as pd; df = pd.DataFrame(['ab c']*100000, columns=['col']); print df['col'].apply(lambda x : pd.Series(x.split(' '))).head()" 

… comparado con esta alternativa:

 time python -c "import pandas as pd; from scipy import array, concatenate; df = pd.DataFrame(['ab 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(['ab 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 10 veces más rápido. Pero la tercera solución, que de manera un tanto irónica desperdicia muchas llamadas a str.split () (se llama una vez por columna por fila, por lo que es tres veces más que para las otras dos soluciones), es aproximadamente 40 veces más rápida que la primera. Porque incluso evita la instancia de 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(['ab 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(['ab c']*100000, columns=['col']); print pd.DataFrame(list(df.col.str.split())).head()" 

Funciona también, y es casi tan eficiente.

EDIT: ¡ aún más sencillo ! Y maneja NaNs (pero menos eficiente):

 time python -c "import pandas as pd; df = pd.DataFrame(['ab 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 

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 hay valores de NaN , la solución más rápida es utilizar la comprensión de list con el constructor DataFrame :

 df = pd.DataFrame(['ab 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 ( documentación ), y explica por qué es más lento:

 df = pd.DataFrame(['ab c']*10, columns=['col']) df.loc[0] = np.nan print (df.head()) col 0 NaN 1 abc 2 abc 3 abc 4 abc print (df.col.str.split(expand=True)) 0 1 2 0 NaN None None 1 abc 2 abc 3 abc 4 abc 5 abc 6 abc 7 abc 8 abc 9 abc