Les pandas divisent la colonne de listes en plusieurs colonnes


135

J'ai un pandas DataFrame avec une colonne:

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)

Production:

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

Comment diviser cette colonne de listes en 2 colonnes?

Réponses:


243

Vous pouvez utiliser le DataFrameconstructeur avec listscréé par 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

Et pour nouveau 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 solution avec apply(pd.Series)est très lente:

#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
Mise en garde mineure, si vous l'utilisez sur une trame de données existante, assurez-vous de réinitialiser l'index, sinon il ne sera pas attribué correctement.
user1700890

1
@ user1700890 - oui, ou spécifier l'index dans le constructeur df2[['team1','team2']] = pd.DataFrame(df2.teams.values.tolist(), index= df2.index)
DataFrame

1
@Catbuilts - oui, s'il existe une solution de vectorisation, le mieux est de l'éviter.
jezrael

1
@Catbuilts - oui, évidemment. Vectorisé signifie généralement pas de boucles, donc pas d'application, non pour, pas de compréhension de liste. Mais cela dépend exactement du besoin. Peut-être aussi aider ça
jezrael

2
@Catbuilts Indeed apply()peut être plus lent, mais c'est la méthode idéale lorsque la chaîne d'entrée et les valeurs ne sont pas égales sur les lignes de la série d'origine!
CheTesta

52

Solution beaucoup plus simple:

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

Rendements,

  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 vous souhaitez diviser une colonne de chaînes délimitées plutôt que de listes, vous pouvez procéder de la même manière:

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

6
Et si chaque liste a un nombre impair d'éléments?
ikel

Si vous vouliez diviser une colonne de chaînes délimitées plutôt que de listes, vous pourriez faire de même: df["teams"].str.split('<delim>', expand=True) renvoie déjà un DataFrame, il serait donc probablement plus simple de simplement renommer les colonnes.
AMC

26

Cette solution préserve l'index du df2DataFrame, contrairement à toute solution qui utilise tolist():

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

Voici le résultat:

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

2
Aussi l'un des plus lents que applyvous puissiez faire chez les pandas. Vous devez éviter cette méthode et utiliser la réponse acceptée. Dans les délais de la première réponse, cette méthode est environ 1400 xplus lente @rajan
Erfan

2
@Erfan Oui, mais parfois l'utilisateur ne se soucie pas de savoir si une opération prend 1s ou 1ms, et au lieu de cela, il se soucie surtout d'écrire le code le plus simple et le plus lisible! Je reconnais que la lisibilité / simplicité est subjective, mais je veux simplement dire que la vitesse n'est pas une priorité pour tous les utilisateurs à tout moment.
Kevin Markham

1
De plus, j'ai découvert que la applyméthode fonctionne de manière plus fiable pour étendre de grandes baies (plus de 1000 éléments) sur de grands ensembles de données. La tolist()méthode a tué mon processus lorsque l'ensemble de données dépassait 500 000 lignes.
moritz le

2
C'est une excellente solution car elle fonctionne bien avec des listes de différentes tailles.
dasilvadaniel le

@KevinMarkham ils se soucient le plus d'écrire le code le plus simple et le plus lisible. Est-ce pd.DataFrame(df["teams"].to_list(), columns=["team_1", "team_2"])vraiment beaucoup plus compliqué?
AMC

15

Il semble y avoir une manière syntaxiquement plus simple, et donc plus facile à retenir, par opposition aux solutions proposées. Je suppose que la colonne est appelée `` meta '' dans un dataframe df:

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

1
J'ai eu une erreur mais je l'ai résolue en supprimant le fichier str.split(). C'était beaucoup plus simple et a l'avantage si vous ne connaissez pas le nombre d'articles dans votre liste.
otteheng

Il semble y avoir une manière syntaxiquement plus simple, et donc plus facile à retenir, par opposition aux solutions proposées. Vraiment? Parce que c'est pratiquement identique à la première réponse publiée des années plus tôt. La seule différence est la partie qui n'est pas liée à cette question spécifique.
AMC

Cela fonctionne pour moi !!
EduardoUstarez le

3

Sur la base des réponses précédentes, voici une autre solution qui renvoie le même résultat que df2.teams.apply (pd.Series) avec un temps d'exécution beaucoup plus rapide:

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

Horaires:

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

Les solutions ci-dessus n'ont pas fonctionné pour moi car j'ai des nanobservations dans mon dataframe. Dans mon cas, df2[['team1','team2']] = pd.DataFrame(df2.teams.values.tolist(), index= df2.index)cède:

object of type 'float' has no len()

Je résous cela en utilisant la compréhension de liste. Voici l'exemple réplicable:

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

production:

        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

résolution avec compréhension de liste:

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

donne:

    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

compréhension de liste

mise en œuvre simple avec compréhension de liste (mon préféré)

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

synchronisation en sortie:

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

production:

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

Ce type de gestion des listes de différentes longueurs - ce qui est une amélioration par rapport à de nombreuses autres réponses, mais a pour résultat que les éléments ne sont pas dans leurs propres colonnes.
Isaac

0

Voici une autre solution utilisant df.transformet 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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.