Como usuario de ambos R
y python
, he visto este tipo de preguntas un par de veces.
En R, tienen la función incorporada del paquete tidyr
llamado unnest
. Pero en Python
( pandas
) no hay una función incorporada para este tipo de pregunta.
Sé que las object
columnas type
siempre hacen que los datos sean difíciles de convertir con una pandas
función '. Cuando recibí los datos de esta manera, lo primero que me vino a la mente fue 'aplanar' o desanidar las columnas.
Estoy usando pandas
y python
funciones para este tipo de pregunta. Si le preocupa la velocidad de las soluciones anteriores, verifique la respuesta del usuario3483203, ya que está usando numpy
y la mayoría de las veces numpy
es más rápido. Lo recomiendo Cpython
y numba
si la velocidad importa en tu caso.
Método 0 [pandas> = 0.25]
A partir de pandas 0.25 , si solo necesita expandir una columna, puede usar la explode
función:
df.explode('B')
A B
0 1 1
1 1 2
0 2 1
1 2 2
Método 1
apply + pd.Series
(fácil de entender, pero no se recomienda en términos de rendimiento).
df.set_index('A').B.apply(pd.Series).stack().reset_index(level=0).rename(columns={0:'B'})
Out[463]:
A B
0 1 1
1 1 2
0 2 1
1 2 2
Método 2
Usando repeat
con el DataFrame
constructor, vuelva a crear su marco de datos (bueno en rendimiento, no bueno en múltiples columnas)
df=pd.DataFrame({'A':df.A.repeat(df.B.str.len()),'B':np.concatenate(df.B.values)})
df
Out[465]:
A B
0 1 1
0 1 2
1 2 1
1 2 2
Método 2.1,
por ejemplo, además de A, tenemos A.1 ..... An Si todavía 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 'desanidar' 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]:
B A
0 1 1
0 2 1
1 1 2
1 2 2
Si necesita el orden de las columnas exactamente igual que antes, agréguelo reindex
al final.
s.join(df.drop('B',1),how='left').reindex(columns=df.columns)
Método 3
recrear ellist
pd.DataFrame([[x] + [z] for x, y in df.values for z in y],columns=df.columns)
Out[488]:
A B
0 1 1
1 1 2
2 2 1
3 2 2
Si hay más de dos columnas, utilice
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 A B
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
oloc
df.reindex(df.index.repeat(df.B.str.len())).assign(B=np.concatenate(df.B.values))
Out[554]:
A B
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]:
B A
0 1 1
1 2 1
2 3 2
3 4 2
Método 6 que
utiliza numpy
para un 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)
A B
0 1 1
1 1 2
2 2 1
3 2 2
Método 7
usando la función base itertools
cycle
y chain
: 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)
A B
0 1 1
1 1 2
2 2 1
3 2 2
Generalizando a múltiples columnas
df=pd.DataFrame({'A':[1,2],'B':[[1,2],[3,4]],'C':[[1,2],[3,4]]})
df
Out[592]:
A B C
0 1 [1, 2] [1, 2]
1 2 [3, 4] [3, 4]
Función de autodefinición:
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]:
B C A
0 1 1 1
0 2 2 1
1 3 3 2
1 4 4 2
Desanidación por columnas
Todo el método anterior está hablando de la eliminación vertical y la explosión.Si necesita gastar la lista horizontal , consulte con el pd.DataFrame
constructor.
df.join(pd.DataFrame(df.B.tolist(),index=df.index).add_prefix('B_'))
Out[33]:
A B C B_0 B_1
0 1 [1, 2] [1, 2] 1 2
1 2 [3, 4] [3, 4] 3 4
Función actualizada
def unnesting(df, explode, axis):
if axis==1:
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')
else :
df1 = pd.concat([
pd.DataFrame(df[x].tolist(), index=df.index).add_prefix(x) for x in explode], axis=1)
return df1.join(df.drop(explode, 1), how='left')
Salida de prueba
unnesting(df, ['B','C'], axis=0)
Out[36]:
B0 B1 C0 C1 A
0 1 2 1 2 1
1 3 4 3 4 2