Pandy dzielą kolumnę list na wiele kolumn

135

Mam pandy DataFrame z jedną kolumną:

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)

Wynik:

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

Jak podzielić tę kolumnę list na dwie kolumny?

user2938093
źródło

Odpowiedzi:

243

Możesz użyć DataFramekonstruktora z listsutworzonym przez 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

A dla nowych 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

Rozwiązanie z apply(pd.Series)jest bardzo powolne:

#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
źródło
4
Drobne zastrzeżenie, jeśli używasz go na istniejącej ramce danych, pamiętaj, aby zresetować indeks, w przeciwnym razie nie zostanie on poprawnie przypisany.
user1700890
1
@ user1700890 - tak, lub określ indeks w konstruktorze df2[['team1','team2']] = pd.DataFrame(df2.teams.values.tolist(), index= df2.index)
DataFrame
1
@Catbuilts - tak, jeśli istnieje rozwiązanie wektoryzacji, najlepiej tego unikać.
jezrael
1
@Catbuilts - tak, oczywiście. Wektoryzacja oznacza generalnie brak pętli, więc nie ma zastosowania, nie dla, nie ma list złożonych. Ale to zależy dokładnie od potrzeb. Może też w tym
jezrael
2
@Catbuilts Rzeczywiście apply()może być wolniejsze, ale jest metodą przechodzenia do, gdy ciąg wejściowy i wartości nie są równe w wierszach oryginalnej serii!
CheTesta
52

Dużo prostsze rozwiązanie:

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

Plony,

  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

Jeśli chcesz podzielić kolumnę rozdzielonych ciągów znaków zamiast list, możesz zrobić podobnie:

pd.DataFrame(df["teams"].str.split('<delim>', expand=True).values,
             columns=['team1', 'team2'])
Joseph Davison
źródło
6
co, jeśli każda lista ma nieparzystą liczbę elementów?
ikel
Jeśli chcesz podzielić kolumnę rozdzielanych ciągów zamiast list, możesz zrobić podobnie: df["teams"].str.split('<delim>', expand=True) już zwraca DataFrame, więc prawdopodobnie prościej byłoby po prostu zmienić nazwy kolumn.
AMC
26

To rozwiązanie zachowuje indeks df2DataFrame, w przeciwieństwie do innych rozwiązań, które używają tolist():

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

Oto wynik:

  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
źródło
2
Również jeden z najwolniejszych, applyjakie możesz zrobić w pandach. Powinieneś unikać tej metody i używać zaakceptowanej odpowiedzi. W czasie odpowiadającym najlepszej odpowiedzi ta metoda jest 1400 xmniej więcej wolniejsza @rajan
Erfan
2
@Erfan Tak, ale czasami użytkownik nie dba o to, czy operacja zajmuje 1s czy 1ms, a zamiast tego najbardziej zależy mu na napisaniu najprostszego, najbardziej czytelnego kodu! Przyznaję, że czytelność / prostota jest subiektywna, ale chodzi mi po prostu o to, że szybkość nie jest priorytetem dla wszystkich użytkowników przez cały czas.
Kevin Markham
1
Ponadto odkryłem, że applymetoda działa bardziej niezawodnie w przypadku rozszerzania dużych tablic (ponad 1000 pozycji) na dużych zestawach danych. Ta tolist()metoda zabiła mój proces, gdy zestaw danych przekroczył 500 tys. Wierszy.
moritz
2
To świetne rozwiązanie, ponieważ działa dobrze z listami o różnych rozmiarach.
dasilvadaniel
@KevinMarkham najbardziej zależy im na napisaniu najprostszego, najbardziej czytelnego kodu. Czy pd.DataFrame(df["teams"].to_list(), columns=["team_1", "team_2"])naprawdę jest o wiele bardziej skomplikowane?
AMC
15

Wydaje się, że istnieje prostszy syntaktycznie sposób, a zatem łatwiejszy do zapamiętania, w przeciwieństwie do proponowanych rozwiązań. Zakładam, że kolumna nazywa się „meta” w df ramki danych:

df2 = pd.DataFrame(df['meta'].str.split().values.tolist())
mikkokotila
źródło
1
Wystąpił błąd, ale rozwiązałem go, usuwając plik str.split(). Było to znacznie prostsze i ma tę zaletę, że nie znasz liczby pozycji na liście.
otteheng,
Wydaje się, że istnieje prostszy syntaktycznie sposób, a zatem łatwiejszy do zapamiętania, w przeciwieństwie do proponowanych rozwiązań. Naprawdę? Ponieważ jest to praktycznie identyczne z najlepszą odpowiedzią, która została opublikowana lata wcześniej. Jedyną różnicą jest część, która nie jest związana z tym konkretnym pytaniem.
AMC
Na mnie to działa !!
EduardoUstarez
3

W oparciu o poprzednie odpowiedzi, oto inne rozwiązanie, które zwraca ten sam wynik co df2.teams.apply (pd.Series) przy znacznie szybszym czasie działania:

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

Czasy:

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)
ailurid
źródło
3

Powyższe rozwiązania nie zadziałały, ponieważ mam nanspostrzeżenia w moim dataframe. W moim przypadku df2[['team1','team2']] = pd.DataFrame(df2.teams.values.tolist(), index= df2.index)daje:

object of type 'float' has no len()

Rozwiązuję to używając rozumienia listy. Oto przykład do odtworzenia:

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

wynik:

        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

rozwiązywanie ze zrozumieniem listy:

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

plony:

    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
źródło
1

rozumienie listy

prosta implementacja ze zrozumieniem listy (mój ulubiony)

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

czas na wyjściu:

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

wynik:

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
źródło
Ten rodzaj obsługuje listy o różnej długości - co stanowi poprawę w stosunku do wielu innych odpowiedzi, ale powoduje, że pozycje nie znajdują się we własnych kolumnach.
Izaak
0

Oto inne rozwiązanie wykorzystujące df.transformi 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
źródło