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?

user2938093
fonte

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)
jezrael
fonte
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'])
Joseph Davison
fonte
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
Kevin Markham
fonte
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())
mikkokotila
fonte
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)
ailurida
fonte
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
Lucas
fonte
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
Talis
fonte
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
Sayandip Dutta
fonte