Панды разделяют столбец списков на несколько столбцов

135

У меня есть DataFrame pandas с одним столбцом:

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)

Вывод:

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

Как разделить этот столбец списков на 2 столбца?

user2938093
источник

Ответы:

243

Вы можете использовать DataFrameконструктор, listsсозданный 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

И для новых 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

Решение с apply(pd.Series)очень медленным:

#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
источник
4
Незначительное предостережение: если вы используете его на существующем фрейме данных, обязательно сбросьте индекс, иначе он не будет назначен правильно.
user1700890 06
1
@ user1700890 - да, или укажите индекс в конструкторе df2[['team1','team2']] = pd.DataFrame(df2.teams.values.tolist(), index= df2.index)
DataFrame
1
@Catbuilts - да, если существует решение векторизации, лучше его избегать.
jezrael
1
@Catbuilts - да, очевидно. Векторизация означает, что, как правило, нет циклов, поэтому не нужно применять, нет, нет понимания списка. Но это смотря что именно нужно. Может быть, также поможет это
Джезраэль
2
@Catbuilts Действительно, apply()может быть медленнее, но это лучший метод, когда входная строка и значения не равны в строках исходной серии!
CheTesta
52

Гораздо более простое решение:

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

Урожайность,

  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

Если вы хотите разделить столбец строк с разделителями, а не списков, вы можете сделать то же самое:

pd.DataFrame(df["teams"].str.split('<delim>', expand=True).values,
             columns=['team1', 'team2'])
Джозеф Дэвисон
источник
6
что, если в каждом списке нечетное количество элементов?
ikel
Если вы хотите разделить столбец строк с разделителями, а не списков, вы можете сделать то же самое: df["teams"].str.split('<delim>', expand=True) уже возвращает DataFrame, поэтому, вероятно, было бы проще просто переименовать столбцы.
AMC
26

Это решение сохраняет индекс df2DataFrame, в отличие от любого решения, в котором используются tolist():

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

Вот результат:

  team1 team2
0    SF   NYG
1    SF   NYG
2    SF   NYG
3    SF   NYG
4    SF   NYG
5    SF   NYG
6    SF   NYG
Кевин Маркхэм
источник
2
Также один из самых медленных, которые applyвы можете сделать в пандах. Вам следует избегать этого метода и использовать принятый ответ. По 1400 xтаймингу верхнего ответа этот метод примерно медленнее @rajan
Erfan
2
@Erfan Да, но иногда пользователя не волнует, занимает ли операция 1 или 1 мс, и вместо этого они больше всего заботятся о написании простейшего и наиболее читаемого кода! Я признаю, что удобочитаемость / простота субъективны, но я просто считаю, что скорость не всегда является приоритетом для всех пользователей.
Кевин Маркхэм
1
Кроме того, я обнаружил, что этот applyметод более надежно работает при расширении больших массивов (более 1000 элементов) на больших наборах данных. tolist()Метод убил мой процесс , когда набор данных превысил 500K строк.
moritz
2
Это отличное решение, потому что оно хорошо работает со списками разного размера.
dasilvadaniel
@KevinMarkham они больше всего заботятся о написании самого простого и читаемого кода. Неужели pd.DataFrame(df["teams"].to_list(), columns=["team_1", "team_2"])все намного сложнее?
AMC
15

Кажется, что существует более простой синтаксически и, следовательно, более простой для запоминания способ, в отличие от предлагаемых решений. Я предполагаю, что столбец называется «мета» в df фрейма данных:

df2 = pd.DataFrame(df['meta'].str.split().values.tolist())
mikkokotila
источник
1
У меня возникла ошибка, но я решил ее, удалив расширение str.split(). Это было намного проще и дает преимущество, если вы не знаете количество элементов в своем списке.
otteheng
Кажется, что существует более простой синтаксически и, следовательно, более простой для запоминания способ, в отличие от предлагаемых решений. В самом деле? Потому что это практически идентично главному ответу, опубликованному годами ранее. Единственная разница в том, что не связано с этим конкретным вопросом.
AMC
У меня работает !!
ЭдуардоУстарез,
3

Основываясь на предыдущих ответах, вот еще одно решение, которое возвращает тот же результат, что и df2.teams.apply (pd.Series), с гораздо более быстрым временем выполнения:

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

Тайминги:

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
источник
3

Вышеупомянутые решения не сработали для меня, так как у меня есть nanнаблюдения в моем dataframe. В моем случае df2[['team1','team2']] = pd.DataFrame(df2.teams.values.tolist(), index= df2.index)дает:

object of type 'float' has no len()

Я решаю это, используя понимание списка. Вот воспроизводимый пример:

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

вывод:

        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

решение с пониманием списка:

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

выходы:

    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

понимание списка

простая реализация с пониманием списка (мой любимый)

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

время на выходе:

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

вывод:

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
Талис
источник
Этот вид обрабатывает списки разной длины - что является улучшением по сравнению со многими другими ответами, но приводит к тому, что элементы не находятся в собственных столбцах.
Исаак
0

Вот еще одно решение с использованием df.transformи 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
Саяндип Датта
источник