Pandas dividem a coluna de listas em várias colunas


136

Eu tenho um DataFrame de pandas com uma coluna:

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)

Resultado:

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

Como pode dividir esta coluna de listas em 2 colunas?

Respostas:


243

Você pode usar o DataFrameconstrutor listscriado 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

E para novos 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

A solução com apply(pd.Series)é muito 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)

4
Advertência secundária, se você o estiver usando no quadro de dados existente, redefina o índice, caso contrário ele não será atribuído corretamente.
— user1700890

1
@ user1700890 - sim, ou especificar índice na trama de dados de construtordf2[['team1','team2']] = pd.DataFrame(df2.teams.values.tolist(), index= df2.index)
— Jezrael

1
@ Catbuilts - sim, se existir solução vetorizada, é melhor evitá-la.
— Jezrael

1
@ Catbuilts - sim, obviamente. Vetorizado geralmente significa que não há loops, portanto não se aplica, não se aplica, não há compreensão de lista. Mas isso depende exatamente do que precisa. Talvez também ajude isso
— jezrael 20/11

2
@Catbuilts Na verdade, apply()pode ser mais lento, mas é o método principal quando a string e os valores de entrada não são iguais nas linhas da série original!
— CheTesta

52

Solução muito mais simples:

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

Rendimentos,

  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

Se você quiser dividir uma coluna de cadeias delimitadas em vez de listas, faça o mesmo:

pd.DataFrame(df["teams"].str.split('<delim>', expand=True).values,
             columns=['team1', 'team2'])

5
e se cada lista tiver um número desigual de elementos?
— ikel 03/11/19

Se você deseja dividir uma coluna de seqüências delimitadas em vez de listas, da mesma forma: df["teams"].str.split('<delim>', expand=True) já retorna um DataFrame, portanto, provavelmente seria mais simples renomear as colunas.
— AMC

26

Esta solução preserva o índice do df2DataFrame, diferente de qualquer solução que use tolist():

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

Aqui está o resultado:

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

2
Também um dos mais lentos que applyvocê pode fazer em pandas. Você deve evitar esse método e usar a resposta aceita. Nos tempos de resposta superior, este método é de aproximadamente 1400 xmais lento @rajan
— Erfan

2
@ Erfan Sim, mas às vezes o usuário não se importa se uma operação leva 1s ou 1ms e, em vez disso, se preocupa mais em escrever o código mais simples e legível! Reconheço que a legibilidade / simplicidade é subjetiva, mas meu argumento é simplesmente que a velocidade não é uma prioridade para todos os usuários o tempo todo.
— Kevin Markham

1
Além disso, descobri que o applymétodo funciona de maneira mais confiável para expandir matrizes grandes (mais de 1.000 itens) em conjuntos de dados grandes. O tolist()método interrompeu meu processo quando o conjunto de dados excedeu 500k linhas.
— moritz

2
Esta é uma ótima solução, pois funciona bem com listas de tamanhos diferentes.
— dasilvadaniel 16/02

@ KevinMarkham eles se preocupam mais em escrever o código mais simples e legível É pd.DataFrame(df["teams"].to_list(), columns=["team_1", "team_2"])realmente muito mais complicado?
— AMC

15

Parece haver uma maneira sintaticamente mais simples e, portanto, mais fácil de lembrar, em oposição às soluções propostas. Estou assumindo que a coluna é chamada 'meta' em um dataframe df:

df2 = pd.DataFrame(df['meta'].str.split().values.tolist())

1
Ocorreu um erro, mas resolvi removendo o arquivo str.split(). Isso foi muito mais simples e tem a vantagem se você não souber o número de itens em sua lista.
— otteheng 11/01/19

Parece haver uma maneira sintaticamente mais simples e, portanto, mais fácil de lembrar, em oposição às soluções propostas. Realmente? Porque isso é praticamente idêntico à resposta principal que foi publicada anos antes. A única diferença é a parte que não está relacionada a essa pergunta específica.
— AMC

Funciona para mim !!
— EduardoUstarez 27/06

3

Com base nas respostas anteriores, aqui está outra solução que retorna o mesmo resultado que df2.teams.apply (pd.Series) com um tempo de execução muito mais rápido:

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

Horários:

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)

3

As soluções acima não funcionaram para mim, pois tenho nanobservações no meu dataframe. No meu caso, df2[['team1','team2']] = pd.DataFrame(df2.teams.values.tolist(), index= df2.index)produz:

object of type 'float' has no len()

Eu resolvo isso usando a compreensão da lista. Aqui o exemplo replicável:

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

resultado:

        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

resolução com compreensão de 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

rendimentos:

    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

1

compreensão da lista

implementação simples com compreensão de lista (o meu favorito)

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

tempo na saída:

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

resultado:

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

Esse tipo de manipula listas de diferentes comprimentos - o que é uma melhoria em relação a muitas outras respostas, mas resulta em itens que não estão em suas próprias colunas.
— Isaac

0

Aqui está outra solução usando df.transforme 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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.