¿Cómo deshago (exploto) una columna en un DataFrame de pandas?

Tengo el siguiente DataFrame donde una de las columnas es un objeto (celda de tipo de lista):

df=pd.DataFrame({'A':[1,2],'B':[[1,2],[1,2]]}) df Out[458]: AB 0 1 [1, 2] 1 2 [1, 2] 

Mi salida esperada es:

  AB 0 1 1 1 1 2 3 2 1 4 2 2 

¿Qué debo hacer para lograr esto?


Pregunta relacionada

pandas: cuando el contenido de las celdas son listas, cree una fila para cada elemento de la lista

Buena pregunta y respuesta, pero solo maneja una columna con una lista (en mi respuesta, la función de autodefensa funcionará para varias columnas, también la respuesta aceptada es el uso que más tiempo consume, lo que no se recomienda, verifique más información ¿ Cuándo debería hacerlo? ¿Quieres usar pandas apply () en mi código? )


Como usuario con R y python , he visto este tipo de pregunta un par de veces.


En R, tienen la función incorporada del paquete tidyr llamada unnest . Pero en Python ( pandas ) no hay una función integrada para este tipo de pregunta.


object type columnas de object siempre hace que los datos sean difíciles de convertir con una función de pandas . Cuando recibí los datos de esta manera, lo primero que me vino a la mente fue “aplanar” o desensamblar las columnas.


El método 1 apply + pd.Series (fácil de entender pero en términos de rendimiento no recomendado).

 df.set_index('A').B.apply(pd.Series).stack().reset_index(level=0).rename(columns={0:'B'}) Out[463]: AB 0 1 1 1 1 2 0 2 1 1 2 2 

Método 2 utilizando repeat con el constructor DataFrame , vuelva a crear su dataframe (bueno en el rendimiento, no bueno en varias columnas)

 df=pd.DataFrame({'A':df.A.repeat(df.B.str.len()),'B':np.concatenate(df.B.values)}) df Out[465]: AB 0 1 1 0 1 2 1 2 1 1 2 2 

El Método 2.1, por ejemplo, además de A, tenemos A.1 … An. Si aún usamos el método ( Método 2 ) anterior, es difícil para nosotros volver a crear las columnas una por una.

Solución: join o merge con el index después de ‘desobedecer’ las columnas individuales

 s=pd.DataFrame({'B':np.concatenate(df.B.values)},index=df.index.repeat(df.B.str.len())) s.join(df.drop('B',1),how='left') Out[477]: BA 0 1 1 0 2 1 1 1 2 1 2 2 

Si necesita el orden de las columnas exactamente igual que antes, agregue reindex al final.

 s.join(df.drop('B',1),how='left').reindex(columns=df.columns) 

Método 3 recrear la list

 pd.DataFrame([[x] + [z] for x, y in df.values for z in y],columns=df.columns) Out[488]: AB 0 1 1 1 1 2 2 2 1 3 2 2 

Si hay más de dos columnas.

 s=pd.DataFrame([[x] + [z] for x, y in zip(df.index,df.B) for z in y]) s.merge(df,left_on=0,right_index=True) Out[491]: 0 1 AB 0 0 1 1 [1, 2] 1 0 2 1 [1, 2] 2 1 1 2 [1, 2] 3 1 2 2 [1, 2] 

Método 4 usando reindex o loc

 df.reindex(df.index.repeat(df.B.str.len())).assign(B=np.concatenate(df.B.values)) Out[554]: AB 0 1 1 0 1 2 1 2 1 1 2 2 #df.loc[df.index.repeat(df.B.str.len())].assign(B=np.concatenate(df.B.values)) 

Método 5 cuando la lista solo contiene valores únicos:

 df=pd.DataFrame({'A':[1,2],'B':[[1,2],[3,4]]}) from collections import ChainMap d = dict(ChainMap(*map(dict.fromkeys, df['B'], df['A']))) pd.DataFrame(list(d.items()),columns=df.columns[::-1]) Out[574]: BA 0 1 1 1 2 1 2 3 2 3 4 2 

Método 6 usando numpy para alto rendimiento:

 newvalues=np.dstack((np.repeat(df.A.values,list(map(len,df.B.values))),np.concatenate(df.B.values))) pd.DataFrame(data=newvalues[0],columns=df.columns) AB 0 1 1 1 1 2 2 2 1 3 2 2 

Método 7 : uso de la función de base cycle y chain itertools : solución de python pura solo por diversión

 from itertools import cycle,chain l=df.values.tolist() l1=[list(zip([x[0]], cycle(x[1])) if len([x[0]]) > len(x[1]) else list(zip(cycle([x[0]]), x[1]))) for x in l] pd.DataFrame(list(chain.from_iterable(l1)),columns=df.columns) AB 0 1 1 1 1 2 2 2 1 3 2 2 

Caso especial (dos columnas tipo objeto)

 df=pd.DataFrame({'A':[1,2],'B':[[1,2],[3,4]],'C':[[1,2],[3,4]]}) df Out[592]: ABC 0 1 [1, 2] [1, 2] 1 2 [3, 4] [3, 4] 

Función de autodefensa

 def unnesting(df, explode): idx = df.index.repeat(df[explode[0]].str.len()) df1 = pd.concat([ pd.DataFrame({x: np.concatenate(df[x].values)}) for x in explode], axis=1) df1.index = idx return df1.join(df.drop(explode, 1), how='left') unnesting(df,['B','C']) Out[609]: BCA 0 1 1 1 0 2 2 1 1 3 3 2 1 4 4 2 

Resumen :

Estoy usando las funciones de pandas y python para este tipo de pregunta. Si le preocupa la velocidad de las soluciones anteriores, verifique la respuesta del usuario 3483203, ya que está usando el numpy y la mayoría de las veces el numpy es más rápido. Recomiendo Cpython y numba si la velocidad es importante en tu caso.

Opción 1

Si todas las numpy en la otra columna tienen la misma longitud, el valor numpy puede ser una opción eficiente aquí:

 vals = np.array(df.B.values.tolist()) a = np.repeat(df.A, vals.shape[1]) pd.DataFrame(np.column_stack((a, vals.ravel())), columns=df.columns) 

  AB 0 1 1 1 1 2 2 2 1 3 2 2 

opcion 2

Si las listas secundarias tienen una longitud diferente, necesita un paso adicional:

 vals = df.B.values.tolist() rs = [len(r) for r in vals] a = np.repeat(df.A, rs) pd.DataFrame(np.column_stack((a, np.concatenate(vals))), columns=df.columns) 

  AB 0 1 1 1 1 2 2 2 1 3 2 2 

Opcion 3

Intenté generalizar esto para aplanar las columnas N y las columnas M azulejos. Más adelante trabajaré para hacerlo más eficiente:

 df = pd.DataFrame({'A': [1,2,3], 'B': [[1,2], [1,2,3], [1]], 'C': [[1,2,3], [1,2], [1,2]], 'D': ['A', 'B', 'C']}) 

  ABCD 0 1 [1, 2] [1, 2, 3] A 1 2 [1, 2, 3] [1, 2] B 2 3 [1] [1, 2] C 

 def unnest(df, tile, explode): vals = df[explode].sum(1) rs = [len(r) for r in vals] a = np.repeat(df[tile].values, rs, axis=0) b = np.concatenate(vals.values) d = np.column_stack((a, b)) return pd.DataFrame(d, columns = tile + ['_'.join(explode)]) unnest(df, ['A', 'D'], ['B', 'C']) 

  AD B_C 0 1 A 1 1 1 A 2 2 1 A 1 3 1 A 2 4 1 A 3 5 2 B 1 6 2 B 2 7 2 B 3 8 2 B 1 9 2 B 2 10 3 C 1 11 3 C 1 12 3 C 2 

Funciones

 def wen1(df): return df.set_index('A').B.apply(pd.Series).stack().reset_index(level=0).rename(columns={0: 'B'}) def wen2(df): return pd.DataFrame({'A':df.A.repeat(df.B.str.len()),'B':np.concatenate(df.B.values)}) def wen3(df): s = pd.DataFrame({'B': np.concatenate(df.B.values)}, index=df.index.repeat(df.B.str.len())) return s.join(df.drop('B', 1), how='left') def wen4(df): return pd.DataFrame([[x] + [z] for x, y in df.values for z in y],columns=df.columns) def chris1(df): vals = np.array(df.B.values.tolist()) a = np.repeat(df.A, vals.shape[1]) return pd.DataFrame(np.column_stack((a, vals.ravel())), columns=df.columns) def chris2(df): vals = df.B.values.tolist() rs = [len(r) for r in vals] a = np.repeat(df.A.values, rs) return pd.DataFrame(np.column_stack((a, np.concatenate(vals))), columns=df.columns) 

Tiempos

 import pandas as pd import matplotlib.pyplot as plt import numpy as np from timeit import timeit res = pd.DataFrame( index=['wen1', 'wen2', 'wen3', 'wen4', 'chris1', 'chris2'], columns=[10, 50, 100, 500, 1000, 5000, 10000], dtype=float ) for f in res.index: for c in res.columns: df = pd.DataFrame({'A': [1, 2], 'B': [[1, 2], [1, 2]]}) df = pd.concat([df]*c) stmt = '{}(df)'.format(f) setp = 'from __main__ import df, {}'.format(f) res.at[f, c] = timeit(stmt, setp, number=50) ax = res.div(res.min()).T.plot(loglog=True) ax.set_xlabel("N") ax.set_ylabel("time (relative)") 

Actuación

introduzca la descripción de la imagen aquí

Una alternativa es aplicar la receta de malla de malla sobre las filas de las columnas para anular:

 import numpy as np import pandas as pd def unnest(frame, explode): def mesh(values): return np.array(np.meshgrid(*values)).T.reshape(-1, len(values)) data = np.vstack(mesh(row) for row in frame[explode].values) return pd.DataFrame(data=data, columns=explode) df = pd.DataFrame({'A': [1, 2], 'B': [[1, 2], [1, 2]]}) print(unnest(df, ['A', 'B'])) # base print() df = pd.DataFrame({'A': [1, 2], 'B': [[1, 2], [3, 4]], 'C': [[1, 2], [3, 4]]}) print(unnest(df, ['A', 'B', 'C'])) # multiple columns print() df = pd.DataFrame({'A': [1, 2, 3], 'B': [[1, 2], [1, 2, 3], [1]], 'C': [[1, 2, 3], [1, 2], [1, 2]], 'D': ['A', 'B', 'C']}) print(unnest(df, ['A', 'B'])) # uneven length lists print() print(unnest(df, ['D', 'B'])) # different types print() 

Salida

  AB 0 1 1 1 1 2 2 2 1 3 2 2 ABC 0 1 1 1 1 1 2 1 2 1 1 2 3 1 2 2 4 2 3 3 5 2 4 3 6 2 3 4 7 2 4 4 AB 0 1 1 1 1 2 2 2 1 3 2 2 4 2 3 5 3 1 DB 0 A 1 1 A 2 2 B 1 3 B 2 4 B 3 5 C 1 

Mis 5 centavos:

 df[['B', 'B2']] = pd.DataFrame(df['B'].values.tolist()) df[['A', 'B']].append(df[['A', 'B2']].rename(columns={'B2': 'B'}), ignore_index=True) 

y otro 5

 df[['B1', 'B2']] = pd.DataFrame([*df['B']]) # if values.tolist() is too boring (pd.wide_to_long(df.drop('B', 1), 'B', 'A', '') .reset_index(level=1, drop=True) .reset_index()) 

ambos resultando en el mismo

  AB 0 1 1 1 2 1 2 1 2 3 2 2 

Algo bastante no recomendado (al menos funciona en este caso):

 df=pd.concat([df]*2).sort_index() it=iter(df['B'].tolist()[0]+df['B'].tolist()[0]) df['B']=df['B'].apply(lambda x:next(it)) 

concat + sort_index + iter + apply + next .

Ahora:

 print(df) 

Es:

  AB 0 1 1 0 1 2 1 2 1 1 2 2 

Si te importa el índice:

 df=df.reset_index(drop=True) 

Ahora:

 print(df) 

Es:

  AB 0 1 1 1 1 2 2 2 1 3 2 2