Cómo dividir el texto de una columna en varias filas

11 minutos de lectura

avatar de usuario
bradley

Estoy trabajando con un archivo csv grande y la penúltima columna tiene una cadena de texto que quiero dividir por un delimitador específico. Me preguntaba si hay una manera simple de hacer esto usando 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 partir por el espacio (' ') y luego el colón (':') en el Seatblocks columna, pero cada celda daría como resultado un número diferente de columnas. Tengo una función para reorganizar las columnas para que el Seatblocks la columna está al final de la hoja, pero no estoy seguro de qué hacer a partir de ahí. Puedo hacerlo en excel con el construido en text-to-columns función y una macro rápida, pero mi conjunto de datos tiene demasiados registros para que Excel los maneje.

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.

  • esta gran pregunta se relaciona con FlatMap en pandas, que actualmente no existe

    – cdarlint

    8 de noviembre de 2017 a las 2:13

  • @cdarlint Qué es FlatMap?

    – jtlz2

    21 de marzo a las 8:17

avatar de usuario
dan alan

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 intervenga con una solución más bonita.

  • @DanAllan proporciona un índice de la Serie cuando presenta la solicitud; se convertirán en nombres de columna

    – Jeff

    14 de junio de 2013 a las 21:48

  • Si bien esto responde a la pregunta, vale la pena mencionar que (probablemente) split() crea una lista para cada fila, lo que aumenta el tamaño de la DataFrame muy rápidamente. En mi caso, ejecutar el código en una tabla de ~200M resultó en un uso de memoria de ~10G (+swap…).

    –David Nemeskey

    24 de marzo de 2016 a las 16:13

  • Aunque no estoy seguro de que sea por split()porque simplemente reduce()‘ing a través de la columna funciona como un encanto. El problema entonces puede estar en stack()

    –David Nemeskey

    24/03/2016 a las 16:32

  • me sale el error NameError: name 'Series' is not defined para esto. dónde está Series se supone que viene? EDITAR: no importa, debería ser pandas.Series ya que se refiere al artículo de pandas

    – usuario5359531

    16 de agosto de 2016 a las 19:07


  • Sí, @usuario5359531. yo from pandas import Series por conveniencia/brevedad.

    – Dan Alan

    17 de agosto de 2016 a las 19:48

avatar de usuario
pietro battiston

A diferencia de Dan, considero que su respuesta es bastante elegante… pero desafortunadamente también es muy, muy ineficiente. Entonces, ya que la pregunta mencionada “un archivo csv grande”permítanme sugerir probar en un shell la solución 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 hacerlo alrededor de 10 veces más rápido. Pero la tercera solución, que irónicamente 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 alrededor 40 veces más rápido que el primero, porque incluso evita instanciar las 100 000 listas. Y sí, ciertamente es un poco feo…

EDITAR: esta respuesta sugiere cómo usar “to_list()” y evitar la necesidad de una lambda. El resultado es algo 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 aún más eficiente que la tercera solución, y ciertamente mucho más elegante.

EDITAR: el aun mas 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()"

también funciona 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()"

  • Tengo un pequeño problema con la cantidad de memoria que consume este método y me pregunto si podría darme un pequeño consejo. Tengo un DataFrame que contiene alrededor de 8000 filas, cada una con una cadena que contiene 9216 enteros de 8 bits delimitados por espacios. Esto es aproximadamente 75 MB, pero cuando aplico la última solución palabra por palabra, Python consume 2 GB de mi memoria. ¿Puede señalarme la dirección de alguna fuente que me diga por qué sucede esto y qué puedo hacer para evitarlo? Gracias.

    – castillo-bravo

    3 de julio de 2014 a las 4:59

  • Tiene muchas listas y cadenas muy pequeñas, que es más o menos el peor de los casos para el uso de memoria en python (y el paso intermedio “.split().tolist()” produce objetos de python puros). Lo que probablemente haría en su lugar sería volcar el DataFrame en un archivo y luego abrirlo como csv con read_csv(…, sep=’ ‘). Pero para permanecer en el tema: la primera solución (junto con la tercera, que sin embargo debería ser terriblemente lenta) puede ser la que le ofrezca el menor uso de memoria entre las 4, ya que tiene una cantidad relativamente pequeña de filas relativamente largas.

    – Pietro Battiston

    3 de julio de 2014 a las 12:42

  • Hola, Pietro, probé tu sugerencia de guardar en un archivo y volver a cargar, y funcionó bastante bien. Me encontré con algunos problemas cuando traté de hacer esto en un objeto StringIO, y aquí se ha publicado una buena solución a mi problema.

    – castillo-bravo

    3 de julio de 2014 a las 23:09


  • Su última sugerencia de tolist() es perfecto. En mi caso, solo quería uno de los datos de la lista y pude agregar directamente una sola columna a mi df existente usando .ix: df['newCol'] = pd.DataFrame(df.col.str.split().tolist()).ix[:,2]

    – fantabuloso

    21 de julio de 2014 a las 8:37

  • Ahh, estaba teniendo problemas para hacer que esto funcionara al principio, algo sobre obect of type 'float' has no len() lo cual fue desconcertante, hasta que me di cuenta alguno de mis filas tenía NaN en ellos, a diferencia de str.

    – dwanderson

    21 mayo 2016 a las 17:25

avatar de usuario
jezrael

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 encadenamiento es el uso 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 están NO NaN valores, la solución más rápida es usar list comprensión con DataFrame constructor:

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 parámetro expand=True que regresan DataFrame (documentación), 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

  • Tal vez valga la pena mencionar que necesariamente necesita el expand=True opción de trabajar con pandas.DataFrames durante el uso .str.split() por ejemplo.

    – holzkohlenggrill

    14 de septiembre de 2016 a las 8:44

  • @holzkohlengrill: gracias por el comentario, lo agrego para responder.

    – jezrael

    14 de septiembre de 2016 a las 8:47

  • @jezrael, me está tomando mucho tiempo ejecutar este código, es lo esperado. ¿Cómo exactamente lo hago más rápido? SI lo pongo en un bucle for como: for x in df[Seablocks][:100] hacerlo solo en un subconjunto y luego concatenar en estos subconjuntos, ¿funcionará?

    – bernando_vialli

    20 de diciembre de 2018 a las 16:36

Puede que sea tarde para responder a esta pregunta, pero espero documentar 2 buenas características de Pandas: pandas.Series.str.split() con expresión regular y pandas.Series.explode().

import pandas as pd
import numpy as np

df = pd.DataFrame(
    {'CustNum': [32363, 31316],
     'CustomerName': ['McCartney, Paul', 'Lennon, John'],
     'ItemQty': [3, 25],
     'Item': ['F04', 'F01'],
     'Seatblocks': ['2:218:10:4,6', '1:13:36:1,12 1:13:37:1,13'],
     'ItemExt': [60, 360]
    }
)

print(df)
print('-'*80+'\n')

df['Seatblocks'] = df['Seatblocks'].str.split('[ :]')
df = df.explode('Seatblocks').reset_index(drop=True)
cols = list(df.columns)
cols.append(cols.pop(cols.index('CustomerName')))
df = df[cols]


print(df)
print('='*80+'\n')
print(df[df['CustomerName'] == 'Lennon, John'])

La salida es:

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

    CustNum  ItemQty Item Seatblocks  ItemExt     CustomerName
0     32363        3  F04          2       60  McCartney, Paul
1     32363        3  F04        218       60  McCartney, Paul
2     32363        3  F04         10       60  McCartney, Paul
3     32363        3  F04        4,6       60  McCartney, Paul
4     31316       25  F01          1      360     Lennon, John
5     31316       25  F01         13      360     Lennon, John
6     31316       25  F01         36      360     Lennon, John
7     31316       25  F01       1,12      360     Lennon, John
8     31316       25  F01          1      360     Lennon, John
9     31316       25  F01         13      360     Lennon, John
10    31316       25  F01         37      360     Lennon, John
11    31316       25  F01       1,13      360     Lennon, John
================================================================================

    CustNum  ItemQty Item Seatblocks  ItemExt  CustomerName
4     31316       25  F01          1      360  Lennon, John
5     31316       25  F01         13      360  Lennon, John
6     31316       25  F01         36      360  Lennon, John
7     31316       25  F01       1,12      360  Lennon, John
8     31316       25  F01          1      360  Lennon, John
9     31316       25  F01         13      360  Lennon, John
10    31316       25  F01         37      360  Lennon, John
11    31316       25  F01       1,13      360  Lennon, John

Este parece un método mucho más fácil que los sugeridos en otras partes de este hilo.

filas divididas en el marco de datos de pandas

avatar de usuario
10 repeticiones

Otro enfoque sería así:

temp = df['Seatblocks'].str.split(' ')
data = data.reindex(data.index.repeat(temp.apply(len)))
data['new_Seatblocks'] = np.hstack(temp)

avatar de usuario
eyllanesc

También puede usar groupby() sin necesidad de unirse y apilar().

Utilice los datos del ejemplo anterior:

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

  • Gracias por adelantado. Cómo podría usar el código anterior dividiendo dos columnas correspondientemente. Por ejemplo: 0 31316 Lennon, John 25 F01 300 1:13:36:1,12 1:13:37:1,13 A,B. El resultado debería ser: 0 31316 Lennon, John 25 F01 300 1:13:36:1,12 A y siguiente línea 0 31316 Lennon, John 25 F01 300 1:13:37:1,13 B

    – Krithi.S

    14 de febrero de 2020 a las 4:48

  • @ Krithi.S, trato de entender la pregunta. ¿Quiere decir que las dos columnas deben tener el mismo número de miembros después de dividirse? ¿Cuáles son sus resultados esperados para 0 31316 Lennon, John 25 F01 300 1:13:36:1,12 1:13:37:1,13 A,B,C ?

    – Ben2018

    16 de febrero de 2020 a las 4:48

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad