Los pandas dividen la columna de listas en varias columnas

135

Tengo un DataFrame de pandas con una columna:

import pandas as pd

df = pd.DataFrame(
    data={
        "teams": [
            ["SF", "NYG"],
            ["SF", "NYG"],
            ["SF", "NYG"],
            ["SF", "NYG"],
            ["SF", "NYG"],
            ["SF", "NYG"],
            ["SF", "NYG"],
        ]
    }
)

print(df)

Salida:

       teams
0  [SF, NYG]
1  [SF, NYG]
2  [SF, NYG]
3  [SF, NYG]
4  [SF, NYG]
5  [SF, NYG]
6  [SF, NYG]

¿Cómo se puede dividir esta columna de listas en 2 columnas?

usuario2938093
fuente

Respuestas:

243

Puede usar el DataFrameconstructor listscreado por to_list:

import pandas as pd

d1 = {'teams': [['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG'],
                ['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG']]}
df2 = pd.DataFrame(d1)
print (df2)
       teams
0  [SF, NYG]
1  [SF, NYG]
2  [SF, NYG]
3  [SF, NYG]
4  [SF, NYG]
5  [SF, NYG]
6  [SF, NYG]

df2[['team1','team2']] = pd.DataFrame(df2.teams.tolist(), index= df2.index)
print (df2)
       teams team1 team2
0  [SF, NYG]    SF   NYG
1  [SF, NYG]    SF   NYG
2  [SF, NYG]    SF   NYG
3  [SF, NYG]    SF   NYG
4  [SF, NYG]    SF   NYG
5  [SF, NYG]    SF   NYG
6  [SF, NYG]    SF   NYG

Y para nuevo DataFrame:

df3 = pd.DataFrame(df2['teams'].to_list(), columns=['team1','team2'])
print (df3)
  team1 team2
0    SF   NYG
1    SF   NYG
2    SF   NYG
3    SF   NYG
4    SF   NYG
5    SF   NYG
6    SF   NYG

La solución con apply(pd.Series)es muy lenta:

#7k rows
df2 = pd.concat([df2]*1000).reset_index(drop=True)

In [121]: %timeit df2['teams'].apply(pd.Series)
1.79 s ± 52.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [122]: %timeit pd.DataFrame(df2['teams'].to_list(), columns=['team1','team2'])
1.63 ms ± 54.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
jezrael
fuente
44
Advertencia menor: si lo está utilizando en el marco de datos existente, asegúrese de restablecer el índice, de lo contrario no se asignará correctamente.
user1700890
1
@ user1700890 - sí, o especifique el índice en el constructor de df2[['team1','team2']] = pd.DataFrame(df2.teams.values.tolist(), index= df2.index)
DataFrame
1
@Catbuilts: sí, si existe una solución de vectorización, la mejor es evitarla.
Israel
1
@ Catbuilts: sí, obviamente. Vectorizado significa generalmente que no hay bucles, por lo que no se aplica, no se hace, no hay listas de comprensión. Pero depende de lo que necesite exactamente. Quizás también ayude a esto
jezrael
2
@Catbuilts Indeed apply()puede ser más lento, pero es el método de acceso cuando la cadena de entrada y los valores no son iguales en las filas de la serie original.
CheTesta
52

Solución mucho más simple:

pd.DataFrame(df2["teams"].to_list(), columns=['team1', 'team2'])

Rendimientos,

  team1 team2
-------------
0    SF   NYG
1    SF   NYG
2    SF   NYG
3    SF   NYG
4    SF   NYG
5    SF   NYG
6    SF   NYG
7    SF   NYG

Si desea dividir una columna de cadenas delimitadas en lugar de listas, podría hacer lo mismo:

pd.DataFrame(df["teams"].str.split('<delim>', expand=True).values,
             columns=['team1', 'team2'])
Joseph Davison
fuente
66
¿Qué pasa si cada lista tiene un número desigual de elementos?
ikel
Si desea dividir una columna de cadenas delimitadas en lugar de listas, podría hacer lo mismo: df["teams"].str.split('<delim>', expand=True) ya devuelve un DataFrame, por lo que probablemente sería más simple cambiar el nombre de las columnas.
AMC
26

Esta solución conserva el índice del df2DataFrame, a diferencia de cualquier solución que use tolist():

df3 = df2.teams.apply(pd.Series)
df3.columns = ['team1', 'team2']

Aquí está el resultado:

  team1 team2
0    SF   NYG
1    SF   NYG
2    SF   NYG
3    SF   NYG
4    SF   NYG
5    SF   NYG
6    SF   NYG
Kevin Markham
fuente
2
También uno de los más lentos applyque puedes hacer en pandas. Debe evitar este método y usar la respuesta aceptada. En los tiempos de la respuesta principal, este método es aproximadamente 1400 xmás lento @rajan
Erfan
2
@Erfan Sí, pero a veces al usuario no le importa si una operación dura 1s o 1ms, ¡y en cambio se preocupa más por escribir el código más simple y legible! Reconozco que la legibilidad / simplicidad es subjetiva, pero mi punto es simplemente que la velocidad no es una prioridad para todos los usuarios en todo momento.
Kevin Markham
1
Además, descubrí que el applymétodo funciona de manera más confiable para expandir matrices grandes (más de 1000 elementos) en grandes conjuntos de datos. El tolist()método eliminó mi proceso cuando el conjunto de datos superó las 500k filas.
Moritz
2
Esta es una gran solución porque funciona bien con listas de diferentes tamaños.
dasilvadaniel
@KevinMarkham les importa más escribir el código más simple y legible ¿Es pd.DataFrame(df["teams"].to_list(), columns=["team_1", "team_2"])realmente mucho más complicado?
AMC
15

Parece que hay una forma sintácticamente más simple y, por lo tanto, más fácil de recordar, a diferencia de las soluciones propuestas. Supongo que la columna se llama 'meta' en un marco de datos df:

df2 = pd.DataFrame(df['meta'].str.split().values.tolist())
mikkokotila
fuente
1
Obtuve un error pero lo resolví quitando el str.split(). Esto fue mucho más simple y tiene la ventaja si no conoce la cantidad de elementos en su lista.
otteheng
Parece que hay una forma sintácticamente más simple y, por lo tanto, más fácil de recordar, a diferencia de las soluciones propuestas. De Verdad? Porque esto es prácticamente idéntico a la respuesta principal que se publicó años antes. La única diferencia es la parte que no está relacionada con esta pregunta específica.
AMC
Me funciona !!
EduardoUstarez
3

Según las respuestas anteriores, aquí hay otra solución que devuelve el mismo resultado que df2.teams.apply (pd.Series) con un tiempo de ejecución mucho más rápido:

pd.DataFrame([{x: y for x, y in enumerate(item)} for item in df2['teams'].values.tolist()], index=df2.index)

Tiempos:

In [1]:
import pandas as pd
d1 = {'teams': [['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG'],
                ['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG']]}
df2 = pd.DataFrame(d1)
df2 = pd.concat([df2]*1000).reset_index(drop=True)

In [2]: %timeit df2['teams'].apply(pd.Series)

8.27 s ± 2.73 s per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [3]: %timeit pd.DataFrame([{x: y for x, y in enumerate(item)} for item in df2['teams'].values.tolist()], index=df2.index)

35.4 ms ± 5.22 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
ailurida
fuente
3

Las soluciones anteriores no funcionaron para mí ya que tengo nanobservaciones en mi dataframe. En mi caso df2[['team1','team2']] = pd.DataFrame(df2.teams.values.tolist(), index= df2.index)produce:

object of type 'float' has no len()

Resuelvo esto usando la comprensión de la lista. Aquí el ejemplo replicable:

import pandas as pd
import numpy as np
d1 = {'teams': [['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG'],
            ['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG'],['SF', 'NYG']]}
df2 = pd.DataFrame(d1)
df2.loc[2,'teams'] = np.nan
df2.loc[4,'teams'] = np.nan
df2

salida:

        teams
0   [SF, NYG]
1   [SF, NYG]
2   NaN
3   [SF, NYG]
4   NaN
5   [SF, NYG]
6   [SF, NYG]

df2['team1']=np.nan
df2['team2']=np.nan

resolviendo con la comprensión de la lista:

for i in [0,1]:
    df2['team{}'.format(str(i+1))]=[k[i] if isinstance(k,list) else k for k in df2['teams']]

df2

rendimientos:

    teams   team1   team2
0   [SF, NYG]   SF  NYG
1   [SF, NYG]   SF  NYG
2   NaN        NaN  NaN
3   [SF, NYG]   SF  NYG
4   NaN        NaN  NaN
5   [SF, NYG]   SF  NYG
6   [SF, NYG]   SF  NYG
Lucas
fuente
1

lista de comprensión

Implementación simple con comprensión de listas (mi favorita)

df = pd.DataFrame([pd.Series(x) for x in df.teams])
df.columns = ['team_{}'.format(x+1) for x in df.columns]

sincronización en la salida:

CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 2.71 ms

salida:

team_1  team_2
0   SF  NYG
1   SF  NYG
2   SF  NYG
3   SF  NYG
4   SF  NYG
5   SF  NYG
6   SF  NYG
Talis
fuente
Este tipo de maneja listas de diferentes longitudes, lo cual es una mejora sobre muchas otras respuestas, pero da como resultado que los elementos no estén en sus propias columnas.
Isaac
0

Aquí hay otra solución usando df.transformy df.set_index:

>>> (df['teams']
       .transform([lambda x:x[0], lambda x:x[1]])
       .set_axis(['team1','team2'],
                  axis=1,
                  inplace=False)
    )

  team1 team2
0    SF   NYG
1    SF   NYG
2    SF   NYG
3    SF   NYG
4    SF   NYG
5    SF   NYG
6    SF   NYG
Sayandip Dutta
fuente