Manera eficiente de desanidar (explotar) múltiples columnas de lista en un DataFrame de pandas
Estoy leyendo varios objetos JSON en un DataFrame. El problema es que algunas de las columnas son listas. Además, los datos son muy grandes y por eso no puedo utilizar las soluciones disponibles en Internet. Son muy lentos y consumen poca memoria.
Así es como se ven mis datos:
df = pd.DataFrame({'A': ['x1','x2','x3', 'x4'], 'B':[['v1','v2'],['v3','v4'],['v5','v6'],['v7','v8']], 'C':[['c1','c2'],['c3','c4'],['c5','c6'],['c7','c8']],'D':[['d1','d2'],['d3','d4'],['d5','d6'],['d7','d8']], 'E':[['e1','e2'],['e3','e4'],['e5','e6'],['e7','e8']]})
A B C D E
0 x1 [v1, v2] [c1, c2] [d1, d2] [e1, e2]
1 x2 [v3, v4] [c3, c4] [d3, d4] [e3, e4]
2 x3 [v5, v6] [c5, c6] [d5, d6] [e5, e6]
3 x4 [v7, v8] [c7, c8] [d7, d8] [e7, e8]
Y esta es la forma de mis datos: (441079, 12)
Mi resultado deseado es:
A B C D E
0 x1 v1 c1 d1 e1
0 x1 v2 c2 d2 e2
1 x2 v3 c3 d3 e3
1 x2 v4 c4 d4 e4
.....
EDITAR: Después de marcarlo como duplicado, me gustaría enfatizar el hecho de que en esta pregunta estaba buscando un método eficiente para explotar varias columnas. Por lo tanto, la respuesta aprobada puede explotar de manera eficiente un número arbitrario de columnas en conjuntos de datos muy grandes. Algo que las respuestas a la otra pregunta no lograron (y esa fue la razón por la que hice esta pregunta después de probar esas soluciones).
pandas >= 1.3
En versiones más recientes, pandas le permite explotar varias columnas a la vez usando DataFrame.explode
, siempre que todos los valores tengan listas del mismo tamaño. Por lo tanto, puedes utilizar esto:
df.explode(['B', 'C', 'D', 'E']).reset_index(drop=True)
A B C D E
0 x1 v1 c1 d1 e1
1 x1 v2 c2 d2 e2
2 x2 v3 c3 d3 e3
3 x2 v4 c4 d4 e4
4 x3 v5 c5 d5 e5
5 x3 v6 c6 d6 e6
6 x4 v7 c7 d7 e7
7 x4 v8 c8 d8 e8
pandas >= 0,25
Para versiones ligeramente anteriores, puede aplicar Series.explode
en cada columna.
df.set_index(['A']).apply(pd.Series.explode).reset_index()
A B C D E
0 x1 v1 c1 d1 e1
1 x1 v2 c2 d2 e2
2 x2 v3 c3 d3 e3
3 x2 v4 c4 d4 e4
4 x3 v5 c5 d5 e5
5 x3 v6 c6 d6 e6
6 x4 v7 c7 d7 e7
7 x4 v8 c8 d8 e8
La idea es establecer como índice todas las columnas que NO deben explotarse primero y luego restablecer el índice.
df.explode
Curiosamente, según mis pruebas, esto es más rápido que llamar . YMMV.
explode
Los métodos son bastante eficaces en general:
df2 = pd.concat([df] * 100, ignore_index=True)
%timeit df2.explode(['B', 'C', 'D', 'E']).reset_index(drop=True)
%timeit df2.set_index(['A']).apply(pd.Series.explode).reset_index() # fastest
%%timeit
(df2.set_index('A')
.apply(lambda x: x.apply(pd.Series).stack())
.reset_index()
.drop('level_1', axis=1))
2.59 ms ± 112 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
1.27 ms ± 239 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
120 ms ± 9.48 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Utilice set_index
una A
y otra vez las columnas restantes apply
y stack
los valores. Todo ello condensado en un solo cartel.
In [1253]: (df.set_index('A')
.apply(lambda x: x.apply(pd.Series).stack())
.reset_index()
.drop('level_1', 1))
Out[1253]:
A B C D E
0 x1 v1 c1 d1 e1
1 x1 v2 c2 d2 e2
2 x2 v3 c3 d3 e3
3 x2 v4 c4 d4 e4
4 x3 v5 c5 d5 e5
5 x3 v6 c6 d6 e6
6 x4 v7 c7 d7 e7
7 x4 v8 c8 d8 e8
def explode(df, lst_cols, fill_value=''):
# make sure `lst_cols` is a list
if lst_cols and not isinstance(lst_cols, list):
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()
if (lens > 0).all():
# ALL lists in cells aren't empty
return pd.DataFrame({
col:np.repeat(df[col].values, df[lst_cols[0]].str.len())
for col in idx_cols
}).assign(**{col:np.concatenate(df[col].values) for col in lst_cols}) \
.loc[:, df.columns]
else:
# at least one list in cells is empty
return pd.DataFrame({
col:np.repeat(df[col].values, df[lst_cols[0]].str.len())
for col in idx_cols
}).assign(**{col:np.concatenate(df[col].values) for col in lst_cols}) \
.append(df.loc[lens==0, idx_cols]).fillna(fill_value) \
.loc[:, df.columns]
Uso:
In [82]: explode(df, lst_cols=list('BCDE'))
Out[82]:
A B C D E
0 x1 v1 c1 d1 e1
1 x1 v2 c2 d2 e2
2 x2 v3 c3 d3 e3
3 x2 v4 c4 d4 e4
4 x3 v5 c5 d5 e5
5 x3 v6 c6 d6 e6
6 x4 v7 c7 d7 e7
7 x4 v8 c8 d8 e8