Dividir (explotar) entrada de cadena de marco de datos de pandas en filas separadas

200

Tengo un pandas dataframeen el que una columna de cadenas de texto contiene valores separados por comas. Quiero dividir cada campo CSV y crear una nueva fila por entrada (suponga que CSV está limpio y solo necesita dividirse en ','). Por ejemplo, adebería convertirse en b:

In [7]: a
Out[7]: 
    var1  var2
0  a,b,c     1
1  d,e,f     2

In [8]: b
Out[8]: 
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2

Hasta ahora, he probado varias funciones simples, pero el .applymétodo parece aceptar solo una fila como valor de retorno cuando se usa en un eje, y no puedo ponerme .transforma trabajar. ¡Cualquier sugerencia sería muy apreciada!

Datos de ejemplo:

from pandas import DataFrame
import numpy as np
a = DataFrame([{'var1': 'a,b,c', 'var2': 1},
               {'var1': 'd,e,f', 'var2': 2}])
b = DataFrame([{'var1': 'a', 'var2': 1},
               {'var1': 'b', 'var2': 1},
               {'var1': 'c', 'var2': 1},
               {'var1': 'd', 'var2': 2},
               {'var1': 'e', 'var2': 2},
               {'var1': 'f', 'var2': 2}])

Sé que esto no funcionará porque perdemos metadatos de DataFrame al pasar por numpy, pero debería darle una idea de lo que intenté hacer:

def fun(row):
    letters = row['var1']
    letters = letters.split(',')
    out = np.array([row] * len(letters))
    out['var1'] = letters
a['idx'] = range(a.shape[0])
z = a.groupby('idx')
z.transform(fun)
Vincent
fuente
2
otras soluciones en esta página están funcionando pero encontré que seguir una breve y efectiva. stackoverflow.com/questions/27263805/…
desaiankitb
1
Para otros que llegan a esta página y buscan una solución que mantenga varias columnas, eche un vistazo a esta pregunta: stackoverflow.com/questions/17116814/…
Sos

Respuestas:

81

Qué tal algo como esto:

In [55]: pd.concat([Series(row['var2'], row['var1'].split(','))              
                    for _, row in a.iterrows()]).reset_index()
Out[55]: 
  index  0
0     a  1
1     b  1
2     c  1
3     d  2
4     e  2
5     f  2

Entonces solo tienes que cambiar el nombre de las columnas

Chang She
fuente
1
Parece que esto va a funcionar. ¡Gracias por tu ayuda! En general, sin embargo, ¿existe un enfoque preferido para Split-Apply-Combine donde Apply devuelve un marco de datos de tamaño arbitrario (pero consistente para todos los fragmentos), y Combine solo apila los DF devueltos?
Vincent
GroupBy.apply debería funcionar (solo lo intenté contra master). Sin embargo, en este caso, realmente no necesita pasar por el paso adicional de agrupación, ya que está generando los datos por fila, ¿verdad?
Chang She
1
Hola chicos. Lamento entrar en esto tan tarde, pero me pregunto si no hay una mejor solución para esto. Estoy tratando de experimentar con iterrows por primera vez, ya que parece ser el boleto para esto. También estoy confundido por la solución propuesta. ¿Qué representa la "_"? ¿Puedes explicar cómo funciona la solución? --Gracias
horatio1701d
11
¿Se puede extender la solución a más de dos columnas?
horatio1701d
1
compruebe este enfoque vectorizado ...
MaxU
147

ACTUALIZACIÓN2: función vectorizada más genérica, que funcionará para columnas múltiples normaly múltipleslist

def explode(df, lst_cols, fill_value='', preserve_index=False):
    # make sure `lst_cols` is list-alike
    if (lst_cols is not None
        and len(lst_cols) > 0
        and not isinstance(lst_cols, (list, tuple, np.ndarray, pd.Series))):
        lst_cols = [lst_cols]
    # all columns except `lst_cols`
    idx_cols = df.columns.difference(lst_cols)
    # calculate lengths of lists
    lens = df[lst_cols[0]].str.len()
    # preserve original index values    
    idx = np.repeat(df.index.values, lens)
    # create "exploded" DF
    res = (pd.DataFrame({
                col:np.repeat(df[col].values, lens)
                for col in idx_cols},
                index=idx)
             .assign(**{col:np.concatenate(df.loc[lens>0, col].values)
                            for col in lst_cols}))
    # append those rows that have empty lists
    if (lens == 0).any():
        # at least one list in cells is empty
        res = (res.append(df.loc[lens==0, idx_cols], sort=False)
                  .fillna(fill_value))
    # revert the original index order
    res = res.sort_index()
    # reset index if requested
    if not preserve_index:        
        res = res.reset_index(drop=True)
    return res

Manifestación:

Múltiples listcolumnas: todas las listcolumnas deben tener el mismo número de elementos en cada fila:

In [134]: df
Out[134]:
   aaa  myid        num          text
0   10     1  [1, 2, 3]  [aa, bb, cc]
1   11     2         []            []
2   12     3     [1, 2]      [cc, dd]
3   13     4         []            []

In [135]: explode(df, ['num','text'], fill_value='')
Out[135]:
   aaa  myid num text
0   10     1   1   aa
1   10     1   2   bb
2   10     1   3   cc
3   11     2
4   12     3   1   cc
5   12     3   2   dd
6   13     4

preservar los valores del índice original:

In [136]: explode(df, ['num','text'], fill_value='', preserve_index=True)
Out[136]:
   aaa  myid num text
0   10     1   1   aa
0   10     1   2   bb
0   10     1   3   cc
1   11     2
2   12     3   1   cc
2   12     3   2   dd
3   13     4

Preparar:

df = pd.DataFrame({
 'aaa': {0: 10, 1: 11, 2: 12, 3: 13},
 'myid': {0: 1, 1: 2, 2: 3, 3: 4},
 'num': {0: [1, 2, 3], 1: [], 2: [1, 2], 3: []},
 'text': {0: ['aa', 'bb', 'cc'], 1: [], 2: ['cc', 'dd'], 3: []}
})

Columna CSV:

In [46]: df
Out[46]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

In [47]: explode(df.assign(var1=df.var1.str.split(',')), 'var1')
Out[47]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ

Con este pequeño truco podemos convertir una columna similar a CSV en una listcolumna:

In [48]: df.assign(var1=df.var1.str.split(','))
Out[48]:
              var1  var2 var3
0        [a, b, c]     1   XX
1  [d, e, f, x, y]     2   ZZ

ACTUALIZACIÓN: enfoque vectorizado genérico (funcionará también para múltiples columnas):

DF original:

In [177]: df
Out[177]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

Solución:

primero convierta las cadenas CSV a listas:

In [178]: lst_col = 'var1' 

In [179]: x = df.assign(**{lst_col:df[lst_col].str.split(',')})

In [180]: x
Out[180]:
              var1  var2 var3
0        [a, b, c]     1   XX
1  [d, e, f, x, y]     2   ZZ

Ahora podemos hacer esto:

In [181]: pd.DataFrame({
     ...:     col:np.repeat(x[col].values, x[lst_col].str.len())
     ...:     for col in x.columns.difference([lst_col])
     ...: }).assign(**{lst_col:np.concatenate(x[lst_col].values)})[x.columns.tolist()]
     ...:
Out[181]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ

ANTIGUA respuesta:

Inspirado por la solución @AFinkelstein , quería que fuera un poco más generalizado, lo que podría aplicarse al DF con más de dos columnas y tan rápido, bueno, casi tan rápido como la solución de AFinkelstein):

In [2]: df = pd.DataFrame(
   ...:    [{'var1': 'a,b,c', 'var2': 1, 'var3': 'XX'},
   ...:     {'var1': 'd,e,f,x,y', 'var2': 2, 'var3': 'ZZ'}]
   ...: )

In [3]: df
Out[3]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

In [4]: (df.set_index(df.columns.drop('var1',1).tolist())
   ...:    .var1.str.split(',', expand=True)
   ...:    .stack()
   ...:    .reset_index()
   ...:    .rename(columns={0:'var1'})
   ...:    .loc[:, df.columns]
   ...: )
Out[4]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ
MaxU
fuente
77
amigo, si puedes abrir una discusión en Git pandas, creo que necesitamos una función incorporada como esta. He visto tantas preguntas sobre la anulación de la lista y la falta de entusiasmo en SO para pandas
YOBEN_S
cómo usar esto para múltiples columnas. ¿Como si tengo datos separados por comas en 2 columnas y quiero hacerlo en secuencia?
Jaskaran Singh Puri
@JaskaranSinghPuri, primero desea convertir todas las columnas CSV en listas.
MaxU
1
Desafortunadamente, no funciona si los elementos de su lista son tuplas. Pero después de convertir toda la tupla en cuerda, ¡funciona de maravilla!
Guido
2
Parece que los dioses de los pandas escucharon la súplica de WenBen, han instalado un .explode()método en la API (también vea esta respuesta ).
cs95
117

Después de una dolorosa experimentación para encontrar algo más rápido que la respuesta aceptada, conseguí que esto funcionara. Funcionó alrededor de 100 veces más rápido en el conjunto de datos que lo probé.

Si alguien conoce una manera de hacer esto más elegante, modifique mi código. No pude encontrar una manera que funcione sin establecer las otras columnas que desea mantener como índice y luego restablecer el índice y cambiar el nombre de las columnas, pero me imagino que hay algo más que funciona.

b = DataFrame(a.var1.str.split(',').tolist(), index=a.var2).stack()
b = b.reset_index()[[0, 'var2']] # var1 variable is currently labeled 0
b.columns = ['var1', 'var2'] # renaming var1
DMulligan
fuente
2
Esta solución funcionó significativamente más rápido y parece usar menos memoria.
cyril
1
Esta es una buena solución de pandas vectorizados, estaba buscando eso. ¡Gracias!
Dennis Golomazov el
Cuando intento esto en mi propio conjunto de datos, sigo llegando TypeError: object of type 'float' has no len()al primer paso ( DataFrame(df.var1.str.split(',').tolist()))
user5359531
@ user5359531 su conjunto de datos probablemente tenga algo NaNen esa columna, por lo que el reemplazo esb = DataFrame(a.var1.str.split(',').values.tolist(), index=a.var2).stack()
Flair el
Para su información, aquí hay una buena redacción de esta solución con un ejemplo.
hhbilly
46

Aquí hay una función que escribí para esta tarea común. Es más eficiente que los métodos Series/ stack. Se conservan el orden de las columnas y los nombres.

def tidy_split(df, column, sep='|', keep=False):
    """
    Split the values of a column and expand so the new DataFrame has one split
    value per row. Filters rows where the column is missing.

    Params
    ------
    df : pandas.DataFrame
        dataframe with the column to split and expand
    column : str
        the column to split and expand
    sep : str
        the string used to split the column's values
    keep : bool
        whether to retain the presplit value as it's own row

    Returns
    -------
    pandas.DataFrame
        Returns a dataframe with the same columns as `df`.
    """
    indexes = list()
    new_values = list()
    df = df.dropna(subset=[column])
    for i, presplit in enumerate(df[column].astype(str)):
        values = presplit.split(sep)
        if keep and len(values) > 1:
            indexes.append(i)
            new_values.append(presplit)
        for value in values:
            indexes.append(i)
            new_values.append(value)
    new_df = df.iloc[indexes, :].copy()
    new_df[column] = new_values
    return new_df

Con esta función, la pregunta original es tan simple como:

tidy_split(a, 'var1', sep=',')
Daniel Himmelstein
fuente
1
¡Esto es increíblemente rápido! Muchas gracias por esto.
Anurag N. Sharma
42

Pandas> = 0.25

Los métodos Series y DataFrame definen un .explode()método que explota las listas en filas separadas. Consulte la sección de documentos sobre Desglosar una columna tipo lista .

Como tiene una lista de cadenas separadas por comas, divida la cadena en una coma para obtener una lista de elementos, luego llame explodea esa columna.

df = pd.DataFrame({'var1': ['a,b,c', 'd,e,f'], 'var2': [1, 2]})
df
    var1  var2
0  a,b,c     1
1  d,e,f     2

df.assign(var1=df['var1'].str.split(',')).explode('var1')

  var1  var2
0    a     1
0    b     1
0    c     1
1    d     2
1    e     2
1    f     2

Tenga en cuenta que explodesolo funciona en una sola columna (por ahora).


Las NaN y las listas vacías obtienen el tratamiento que se merecen sin que tenga que saltar por los aros para hacerlo bien.

df = pd.DataFrame({'var1': ['d,e,f', '', np.nan], 'var2': [1, 2, 3]})
df
    var1  var2
0  d,e,f     1
1            2
2    NaN     3

df['var1'].str.split(',')

0    [d, e, f]
1           []
2          NaN

df.assign(var1=df['var1'].str.split(',')).explode('var1')

  var1  var2
0    d     1
0    e     1
0    f     1
1          2  # empty list entry becomes empty string after exploding 
2  NaN     3  # NaN left un-touched

Esta es una gran ventaja sobre las soluciones basadas en ravel+repeat (que ignoran por completo las listas vacías y se ahogan con los NaN).

cs95
fuente
44
¡Este es el más fácil y se adapta mejor en mi caso! ¡Gracias!
Isaac Sim
14

Pregunta similar a: pandas: ¿Cómo divido el texto de una columna en varias filas?

Podrías hacerlo:

>> a=pd.DataFrame({"var1":"a,b,c d,e,f".split(),"var2":[1,2]})
>> s = a.var1.str.split(",").apply(pd.Series, 1).stack()
>> s.index = s.index.droplevel(-1)
>> del a['var1']
>> a.join(s)
   var2 var1
0     1    a
0     1    b
0     1    c
1     2    d
1     2    e
1     2    f
inodb
fuente
2
Funciona después de agregar un código de cambio de nombre más s.name = 'var1'
Jesse
14

TL; DR

import pandas as pd
import numpy as np

def explode_str(df, col, sep):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.count(sep) + 1)
    return df.iloc[i].assign(**{col: sep.join(s).split(sep)})

def explode_list(df, col):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.len())
    return df.iloc[i].assign(**{col: np.concatenate(s)})

Demostración

explode_str(a, 'var1', ',')

  var1  var2
0    a     1
0    b     1
0    c     1
1    d     2
1    e     2
1    f     2

Creemos un nuevo marco de datos dque tenga listas

d = a.assign(var1=lambda d: d.var1.str.split(','))

explode_list(d, 'var1')

  var1  var2
0    a     1
0    b     1
0    c     1
1    d     2
1    e     2
1    f     2

Comentarios generales

Voy a usar np.arangecon repeatproducir trama de datos de posiciones de índice que puedo usar con iloc.

Preguntas más frecuentes

¿Por qué no lo uso loc?

Debido a que el índice puede no ser único y usar locdevolverá cada fila que coincida con un índice consultado.

¿Por qué no usas el valuesatributo y lo cortas?

Al llamar values, si la totalidad del marco de datos está en un "bloque" cohesivo, Pandas devolverá una vista de la matriz que es el "bloque". De lo contrario, los pandas tendrán que improvisar una nueva matriz. Al enredar, esa matriz debe ser de un tipo uniforme. A menudo eso significa devolver una matriz con dtype que es object. Al usar en iloclugar de cortar el valuesatributo, me alivio de tener que lidiar con eso.

¿Por qué lo usas assign?

Cuando uso assignel mismo nombre de columna que estoy explotando, sobrescribo la columna existente y mantengo su posición en el marco de datos.

¿Por qué se repiten los valores del índice?

En virtud del uso ilocen posiciones repetidas, el índice resultante muestra el mismo patrón repetido. Una repetición para cada elemento de la lista o cadena.
Esto se puede restablecer conreset_index(drop=True)


Para cuerdas

No quiero tener que dividir las cuerdas prematuramente. Entonces, en cambio, cuento las ocurrencias del separgumento suponiendo que si me dividiera, la longitud de la lista resultante sería uno más que el número de separadores.

Entonces lo uso seppara joinlas cuerdas entonces split.

def explode_str(df, col, sep):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.count(sep) + 1)
    return df.iloc[i].assign(**{col: sep.join(s).split(sep)})

Para listas

Similar a las cadenas, excepto que no necesito contar las ocurrencias sepporque ya está dividido.

Yo uso Numpy's concatenatepara atascar las listas juntas.

import pandas as pd
import numpy as np

def explode_list(df, col):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.len())
    return df.iloc[i].assign(**{col: np.concatenate(s)})

piRSquared
fuente
Me gusta este. Muy conciso y el rendimiento también debería ser muy bueno. Sin embargo, una pregunta: ¿es df.iloc [i] lo mismo que repetir filas del marco de datos o es más eficiente que eso? ¡Gracias!
Tim
7

Existe la posibilidad de dividir y explotar el marco de datos sin cambiar la estructura del marco de datos

Dividir y expandir datos de columnas específicas.

Entrada:

    var1    var2
0   a,b,c   1
1   d,e,f   2



#Get the indexes which are repetative with the split 
temp = df['var1'].str.split(',')
df = df.reindex(df.index.repeat(temp.apply(len)))


df['var1'] = np.hstack(temp)

Fuera:

    var1    var2
0   a   1
0   b   1
0   c   1
1   d   2
1   e   2
1   f   2

Editar-1

Dividir y expandir filas para columnas múltiples

Filename    RGB                                             RGB_type
0   A   [[0, 1650, 6, 39], [0, 1691, 1, 59], [50, 1402...   [r, g, b]
1   B   [[0, 1423, 16, 38], [0, 1445, 16, 46], [0, 141...   [r, g, b]

Volver a indexar según la columna de referencia y alinear la información del valor de la columna con la pila

df = df.reindex(df.index.repeat(df['RGB_type'].apply(len)))
df = df.groupby('Filename').apply(lambda x:x.apply(lambda y: pd.Series(y.iloc[0])))
df.reset_index(drop=True).ffill()

Fuera:

                Filename    RGB_type    Top 1 colour    Top 1 frequency Top 2 colour    Top 2 frequency
    Filename                            
 A  0       A   r   0   1650    6   39
    1       A   g   0   1691    1   59
    2       A   b   50  1402    49  187
 B  0       B   r   0   1423    16  38
    1       B   g   0   1445    16  46
    2       B   b   0   1419    16  39
Naga kiran
fuente
5

Se me ocurrió una solución para los marcos de datos con números arbitrarios de columnas (mientras que solo separaba las entradas de una columna a la vez).

def splitDataFrameList(df,target_column,separator):
    ''' df = dataframe to split,
    target_column = the column containing the values to split
    separator = the symbol used to perform the split

    returns: a dataframe with each entry for the target column separated, with each element moved into a new row. 
    The values in the other columns are duplicated across the newly divided rows.
    '''
    def splitListToRows(row,row_accumulator,target_column,separator):
        split_row = row[target_column].split(separator)
        for s in split_row:
            new_row = row.to_dict()
            new_row[target_column] = s
            row_accumulator.append(new_row)
    new_rows = []
    df.apply(splitListToRows,axis=1,args = (new_rows,target_column,separator))
    new_df = pandas.DataFrame(new_rows)
    return new_df
jlln
fuente
2
agradable pero tristemente lento debido a esta conversión todict () :(
MAQ
4

Aquí hay un mensaje bastante sencillo que utiliza el splitmétodo de pandasstr accessor y luego usa NumPy para aplanar cada fila en una sola matriz.

Los valores correspondientes se recuperan repitiendo la columna no dividida con la cantidad correcta de veces np.repeat.

var1 = df.var1.str.split(',', expand=True).values.ravel()
var2 = np.repeat(df.var2.values, len(var1) / len(df))

pd.DataFrame({'var1': var1,
              'var2': var2})

  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2
Ted Petrou
fuente
1
Esa podría ser una respuesta muy hermosa. Desafortunadamente, no se escala para muchas columnas, ¿verdad?
Michael Dorner
3

He estado luchando con la experiencia de falta de memoria usando varias formas de explotar mis listas, así que preparé algunos puntos de referencia para ayudarme a decidir qué respuestas votar. Probé cinco escenarios con proporciones variables de la longitud de la lista al número de listas. Compartiendo los resultados a continuación:

Tiempo: (menos es mejor, haga clic para ver la versión grande)

Velocidad

Uso máximo de memoria: (menos es mejor)

Uso pico de memoria

Conclusiones :

  • La respuesta de @ MaxU (actualización 2), concatenate de nombre de código ofrece la mejor velocidad en casi todos los casos, mientras mantiene bajo el uso de memoria peek,
  • vea la respuesta de @ DMulligan ( pila de nombres en clave ) si necesita procesar muchas filas con listas relativamente pequeñas y puede permitirse un aumento de memoria pico,
  • la respuesta aceptada de @ Chang funciona bien para marcos de datos que tienen algunas filas pero listas muy grandes.

Los detalles completos (funciones y código de evaluación comparativa) se encuentran en esta esencia de GitHub . Tenga en cuenta que el problema de referencia se simplificó y no incluyó la división de cadenas en la lista, que la mayoría de las soluciones se realizaron de manera similar.

krassowski
fuente
Buena comparación! ¿Le importaría publicar un código que utilizó para trazar los puntos de referencia?
MaxU
1
Consulte este enlace: gist.github.com/krassowski/0259a2cd2ba774ccd9f69bbcc3187fbf (ya incluido en la respuesta): en mi opinión, sería demasiado largo para pegarlo todo aquí.
krassowski el
2

Basado en la excelente solución de @ DMulligan , aquí hay una función genérica vectorizada (sin bucles) que divide una columna de un marco de datos en varias filas y la fusiona nuevamente con el marco de datos original. También utiliza una gran change_column_orderfunción genérica de esta respuesta .

def change_column_order(df, col_name, index):
    cols = df.columns.tolist()
    cols.remove(col_name)
    cols.insert(index, col_name)
    return df[cols]

def split_df(dataframe, col_name, sep):
    orig_col_index = dataframe.columns.tolist().index(col_name)
    orig_index_name = dataframe.index.name
    orig_columns = dataframe.columns
    dataframe = dataframe.reset_index()  # we need a natural 0-based index for proper merge
    index_col_name = (set(dataframe.columns) - set(orig_columns)).pop()
    df_split = pd.DataFrame(
        pd.DataFrame(dataframe[col_name].str.split(sep).tolist())
        .stack().reset_index(level=1, drop=1), columns=[col_name])
    df = dataframe.drop(col_name, axis=1)
    df = pd.merge(df, df_split, left_index=True, right_index=True, how='inner')
    df = df.set_index(index_col_name)
    df.index.name = orig_index_name
    # merge adds the column to the last place, so we need to move it back
    return change_column_order(df, col_name, orig_col_index)

Ejemplo:

df = pd.DataFrame([['a:b', 1, 4], ['c:d', 2, 5], ['e:f:g:h', 3, 6]], 
                  columns=['Name', 'A', 'B'], index=[10, 12, 13])
df
        Name    A   B
    10   a:b     1   4
    12   c:d     2   5
    13   e:f:g:h 3   6

split_df(df, 'Name', ':')
    Name    A   B
10   a       1   4
10   b       1   4
12   c       2   5
12   d       2   5
13   e       3   6
13   f       3   6    
13   g       3   6    
13   h       3   6    

Tenga en cuenta que conserva el índice original y el orden de las columnas. También funciona con marcos de datos que tienen índice no secuencial.

Dennis Golomazov
fuente
2
esto me resquebrajó, buen trabajo: stackoverflow.com/a/48554655/6672746
Evan
2

La función de división de cadena puede tomar una opción de argumento booleano 'expandir'.

Aquí hay una solución usando este argumento:

(a.var1
  .str.split(",",expand=True)
  .set_index(a.var2)
  .stack()
  .reset_index(level=1, drop=True)
  .reset_index()
  .rename(columns={0:"var1"}))
cgels
fuente
1

Solo utilicé la excelente respuesta de jiln de arriba, pero necesitaba expandirme para dividir múltiples columnas. Pensé que iba a compartir.

def splitDataFrameList(df,target_column,separator):
''' df = dataframe to split,
target_column = the column containing the values to split
separator = the symbol used to perform the split

returns: a dataframe with each entry for the target column separated, with each element moved into a new row. 
The values in the other columns are duplicated across the newly divided rows.
'''
def splitListToRows(row, row_accumulator, target_columns, separator):
    split_rows = []
    for target_column in target_columns:
        split_rows.append(row[target_column].split(separator))
    # Seperate for multiple columns
    for i in range(len(split_rows[0])):
        new_row = row.to_dict()
        for j in range(len(split_rows)):
            new_row[target_columns[j]] = split_rows[j][i]
        row_accumulator.append(new_row)
new_rows = []
df.apply(splitListToRows,axis=1,args = (new_rows,target_column,separator))
new_df = pd.DataFrame(new_rows)
return new_df
Derryn Webster-Knife
fuente
1

la respuesta de MaxU actualizada con soporte MultiIndex

def explode(df, lst_cols, fill_value='', preserve_index=False):
    """
    usage:
        In [134]: df
        Out[134]:
           aaa  myid        num          text
        0   10     1  [1, 2, 3]  [aa, bb, cc]
        1   11     2         []            []
        2   12     3     [1, 2]      [cc, dd]
        3   13     4         []            []

        In [135]: explode(df, ['num','text'], fill_value='')
        Out[135]:
           aaa  myid num text
        0   10     1   1   aa
        1   10     1   2   bb
        2   10     1   3   cc
        3   11     2
        4   12     3   1   cc
        5   12     3   2   dd
        6   13     4
    """
    # make sure `lst_cols` is list-alike
    if (lst_cols is not None
        and len(lst_cols) > 0
        and not isinstance(lst_cols, (list, tuple, np.ndarray, pd.Series))):
        lst_cols = [lst_cols]
    # all columns except `lst_cols`
    idx_cols = df.columns.difference(lst_cols)
    # calculate lengths of lists
    lens = df[lst_cols[0]].str.len()
    # preserve original index values    
    idx = np.repeat(df.index.values, lens)
    res = (pd.DataFrame({
                col:np.repeat(df[col].values, lens)
                for col in idx_cols},
                index=idx)
             .assign(**{col:np.concatenate(df.loc[lens>0, col].values)
                            for col in lst_cols}))
    # append those rows that have empty lists
    if (lens == 0).any():
        # at least one list in cells is empty
        res = (res.append(df.loc[lens==0, idx_cols], sort=False)
                  .fillna(fill_value))
    # revert the original index order
    res = res.sort_index()
    # reset index if requested
    if not preserve_index:        
        res = res.reset_index(drop=True)

    # if original index is MultiIndex build the dataframe from the multiindex
    # create "exploded" DF
    if isinstance(df.index, pd.MultiIndex):
        res = res.reindex(
            index=pd.MultiIndex.from_tuples(
                res.index,
                names=['number', 'color']
            )
    )
    return res
Shahar Katz
fuente
1

One-liner usando split(___, expand=True)y los argumentos levely namepara reset_index():

>>> b = a.var1.str.split(',', expand=True).set_index(a.var2).stack().reset_index(level=0, name='var1')
>>> b
   var2 var1
0     1    a
1     1    b
2     1    c
0     2    d
1     2    e
2     2    f

Si necesita bverse exactamente como en la pregunta, también puede hacer:

>>> b = b.reset_index(drop=True)[['var1', 'var2']]
>>> b
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2
1 ''
fuente
0

Se me ocurrió la siguiente solución a este problema:

def iter_var1(d):
    for _, row in d.iterrows():
        for v in row["var1"].split(","):
            yield (v, row["var2"])

new_a = DataFrame.from_records([i for i in iter_var1(a)],
        columns=["var1", "var2"])
Pavel
fuente
0

Otra solución que usa el paquete de copia de Python

import copy
new_observations = list()
def pandas_explode(df, column_to_explode):
    new_observations = list()
    for row in df.to_dict(orient='records'):
        explode_values = row[column_to_explode]
        del row[column_to_explode]
        if type(explode_values) is list or type(explode_values) is tuple:
            for explode_value in explode_values:
                new_observation = copy.deepcopy(row)
                new_observation[column_to_explode] = explode_value
                new_observations.append(new_observation) 
        else:
            new_observation = copy.deepcopy(row)
            new_observation[column_to_explode] = explode_values
            new_observations.append(new_observation) 
    return_df = pd.DataFrame(new_observations)
    return return_df

df = pandas_explode(df, column_name)
Ankit Maheshwari
fuente
0

Aquí hay muchas respuestas, pero me sorprende que nadie haya mencionado la función de explosión de pandas incorporada. Consulte el siguiente enlace: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html#pandas.DataFrame.explode

Por alguna razón no pude acceder a esa función, así que utilicé el siguiente código:

import pandas_explode
pandas_explode.patch()
df_zlp_people_cnt3 = df_zlp_people_cnt2.explode('people')

ingrese la descripción de la imagen aquí

Arriba hay una muestra de mis datos. Como puede ver, la columna de personas tenía una serie de personas, y estaba tratando de explotarla. El código que he dado funciona para datos de tipo de lista. Así que trate de obtener sus datos de texto separados por comas en formato de lista. Además, dado que mi código utiliza funciones integradas, es mucho más rápido que las funciones personalizadas / de aplicación.

Nota: Es posible que deba instalar pandas_explode con pip.

Harsha Reddy
fuente
0

Tuve un problema similar, mi solución fue convertir el marco de datos a una lista de diccionarios primero, luego hacer la transición. Aquí está la función:

import copy
import re

def separate_row(df, column_name):
    ls = []
    for row_dict in df.to_dict('records'):
        for word in re.split(',', row_dict[column_name]):
            row = copy.deepcopy(row_dict)
            row[column_name]=word
            ls(row)
    return pd.DataFrame(ls)

Ejemplo:

>>> from pandas import DataFrame
>>> import numpy as np
>>> a = DataFrame([{'var1': 'a,b,c', 'var2': 1},
               {'var1': 'd,e,f', 'var2': 2}])
>>> a
    var1  var2
0  a,b,c     1
1  d,e,f     2
>>> separate_row(a, "var1")
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2

También puede cambiar un poco la función para admitir la separación de filas de tipo de lista.

Zhiwei
fuente