varias una seleccionar manejo insertar eliminar documentacion data como columnas columna agregar python pandas dataframe

python - seleccionar - ¿Cómo deshacer(explotar) una columna en un marco de datos de pandas?



seleccionar columnas pandas python (11)

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]: A B 0 1 [1, 2] 1 2 [1, 2]

Mi salida esperada es:

A B 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? )


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:

A B 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:

A B 0 1 1 1 1 2 2 2 1 3 2 2


Como usuario tanto de R como de 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.

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.

Método 0 [pandas <= 0.25]
A partir de pandas 0.25 , si solo necesitas explotar una columna, puedes usar la función de explode :

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 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]: A B 0 1 1 1 1 2 0 2 1 1 2 2

Método 2
Usando la repeat con el constructor DataFrame , vuelva a crear su marco de datos (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]: 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 aún usamos el método ( Método 2 ) anterior, es difícil para nosotros recrear 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]: 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, 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]: A B 0 1 1 1 1 2 2 2 1 3 2 2

Si hay más de dos columnas, use

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 o loc

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
utilizando 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) A B 0 1 1 1 1 2 2 2 1 3 2 2

Método 7
usando la función de base itertools cycle y chain : solución de python puro 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 columnas múltiples

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 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]: B C A 0 1 1 1 0 2 2 1 1 3 3 2 1 4 4 2

Columna sabio Unnesting

Todo el método anterior se refiere a la descomposición vertical y la explosión. Si necesita gastar la lista horizontalmente , pd.DataFrame con el constructor pd.DataFrame

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


Debido a que normalmente la longitud de la lista secundaria es diferente y la combinación / combinación es mucho más costosa computacionalmente. Volví a probar el método para diferentes sublistas de longitud y columnas más normales.

MultiIndex también debería ser una forma más fácil de escribir y tiene casi las mismas interpretaciones que las de numpy.

Sorprendentemente, en mi implementación, la forma de comprensión tiene el mejor rendimiento.

def stack(df): return df.set_index([''A'', ''C'']).B.apply(pd.Series).stack() def comprehension(df): return pd.DataFrame([x + [z] for x, y in zip(df[[''A'', ''C'']].values.tolist(), df.B) for z in y]) def multiindex(df): return pd.DataFrame(np.concatenate(df.B.values), index=df.set_index([''A'', ''C'']).index.repeat(df.B.str.len())) def array(df): return pd.DataFrame( np.column_stack(( np.repeat(df[[''A'', ''C'']].values, df.B.str.len(), axis=0), np.concatenate(df.B.values) )) ) import pandas as pd import matplotlib.pyplot as plt import numpy as np from timeit import timeit res = pd.DataFrame( index=[ ''stack'', ''comprehension'', ''multiindex'', ''array'', ], columns=[1000, 2000, 5000, 10000, 20000, 50000], dtype=float ) for f in res.index: for c in res.columns: df = pd.DataFrame({''A'': list(''abc''), ''C'': list(''def''), ''B'': [[''g'', ''h'', ''i''], [''j'', ''k''], [''l'']]}) df = pd.concat([df] * c) stmt = ''{}(df)''.format(f) setp = ''from __main__ import df, {}''.format(f) res.at[f, c] = timeit(stmt, setp, number=20) ax = res.div(res.min()).T.plot(loglog=True) ax.set_xlabel("N") ax.set_ylabel("time (relative)")

Actuación

Tiempo relativo de cada método.


Generalicé el problema un poco para ser aplicable a más columnas.

Resumen de lo que hace mi solución:

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

Ejemplo completo:

La explosión real se realiza en 3 líneas. El resto son cosméticos (explosión en varias columnas, manejo de cuerdas en lugar de listas en la columna de explosión, ...).

A B 0 1 1 0 1 2 1 2 1 1 2 2

Créditos a la respuesta de WeNYoBen.


La explosión de una columna similar a una lista se ha simplificado significativamente en los pandas 0.25 con la adición del método explode() :

df=pd.DataFrame({''A'':[1,2],''B'':[[1,2],[1,2]]}) out = pd.concat([df.loc[:,''A''],(df.B.apply(pd.Series))], axis=1, sort=False) out = out.set_index(''A'').stack().droplevel(level=1).reset_index().rename(columns={0:"B"}) A B 0 1 1 1 1 2 2 2 1 3 2 2

Afuera:

# Here''s the answer to the related question in: # https://.com/q/56708671/11426125 # initial dataframe df12=pd.DataFrame({''Date'':[''2007-12-03'',''2008-09-07''],''names'': [[''Peter'',''Alex''],[''Donald'',''Stan'']]}) # convert dataframe to array for indexing list values (names) a = np.array(df12.values) # create a new, dataframe with dimensions for unnested b = np.ndarray(shape = (4,2)) df2 = pd.DataFrame(b, columns = ["Date", "names"], dtype = str) # implement loops to assign date/name values as required i = range(len(a[0])) j = range(len(a[0])) for x in i: for y in j: df2.iat[2*x+y, 0] = a[x][0] df2.iat[2*x+y, 1] = a[x][1][y] # set Date column as Index df2.Date=pd.to_datetime(df2.Date) df2.index=df2.Date df2.drop(''Date'',axis=1,inplace =True)


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

A B 0 1 1 1 2 1 2 1 2 3 2 2


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

A B 0 1 1 1 1 2 2 2 1 3 2 2 A B C 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 A B 0 1 1 1 1 2 2 2 1 3 2 2 4 2 3 5 3 1 D B 0 A 1 1 A 2 2 B 1 3 B 2 4 B 3 5 C 1


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)

A B 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)

A B 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'']})

A B C D 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''])

A D 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


import pandas as pd import numpy as np df=pd.DataFrame( {''A'': [''A1'',''A2'',''A3''], ''B'': [''B1'',''B2'',''B3''], ''C'': [ [''C1.1'',''C1.2''],[''C2.1'',''C2.2''],''C3''], ''columnD'': [ ''D1'',[''D2.1'',''D2.2'', ''D2.3''],[''D3.1'',''D3.2'']], }) print(''df'',df, sep=''/n'') def dfListExplode(df, explodeKeys): if not isinstance(explodeKeys, list): explodeKeys=[explodeKeys] # recursive handling of explodeKeys if len(explodeKeys)==0: return df elif len(explodeKeys)==1: explodeKey=explodeKeys[0] else: return dfListExplode( dfListExplode(df, explodeKeys[:1]), explodeKeys[1:]) # perform explosion/unnesting for key: explodeKey dfPrep=df[explodeKey].apply(lambda x: x if isinstance(x,list) else [x]) #casts all elements to a list dfIndExpl=pd.DataFrame([[x] + [z] for x, y in zip(dfPrep.index,dfPrep.values) for z in y ], columns=[''explodedIndex'',explodeKey]) dfMerged=dfIndExpl.merge(df.drop(explodeKey, axis=1), left_on=''explodedIndex'', right_index=True) dfReind=dfMerged.reindex(columns=list(df)) return dfReind dfExpl=dfListExplode(df,[''C'',''columnD'']) print(''dfExpl'',dfExpl, sep=''/n'')


df=pd.DataFrame({''A'':[1,2],''B'':[[1,2],[1,2]]}) pd.concat([df[''A''], pd.DataFrame(df[''B''].values.tolist())], axis = 1)/ .melt(id_vars = ''A'', value_name = ''B'')/ .dropna()/ .drop(''variable'', axis = 1) A B 0 1 1 1 2 1 2 1 2 3 2 2

¿Alguna opinión sobre este método que pensé? ¿O es tanto "concat" como "melt" considerado demasiado "caro"?


In[74]: df Out[74]: A B C columnD 0 A1 B1 [C1.1, C1.2] D1 1 A2 B2 [C2.1, C2.2] [D2.1, D2.2, D2.3] 2 A3 B3 C3 [D3.1, D3.2] In[75]: dfListExplode(df,[''C'',''columnD'']) Out[75]: A B C columnD 0 A1 B1 C1.1 D1 1 A1 B1 C1.2 D1 2 A2 B2 C2.1 D2.1 3 A2 B2 C2.1 D2.2 4 A2 B2 C2.1 D2.3 5 A2 B2 C2.2 D2.1 6 A2 B2 C2.2 D2.2 7 A2 B2 C2.2 D2.3 8 A3 B3 C3 D3.1 9 A3 B3 C3 D3.2

  • Puede implementar esto como un forro, si no desea crear un objeto intermedio.