Панда разделить столбец списков на несколько столбцов
У меня есть pandas dataFrame с одним столбцом, который выглядит следующим образом:
`
In [207]:df2.teams
Out[207]:
0 [SF, NYG]
1 [SF, NYG]
2 [SF, NYG]
3 [SF, NYG]
4 [SF, NYG]
5 [SF, NYG]
6 [SF, NYG]
7 [SF, NYG]
`
Мне нужно разделить этот столбец списков на 2 столбца с именем team1 и team2, используя панд
13 ответов
Ты можешь использовать DataFrame
конструктор с lists
создан путем преобразования в numpy array
от values
с tolist
:
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.values.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'].values.tolist(), 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 [89]: %timeit df2['teams'].apply(pd.Series)
1 loop, best of 3: 1.15 s per loop
In [90]: %timeit pd.DataFrame(df2['teams'].values.tolist(), columns=['team1','team2'])
1000 loops, best of 3: 820 µs per loop
Гораздо более простое решение:
pd.DataFrame(df2.teams.tolist(), 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'])
Это решение сохраняет индекс df2
DataFrame, в отличие от любого решения, использующего 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
Кажется, существует синтаксически более простой способ, и, следовательно, его легче запомнить, в отличие от предложенных решений. Я предполагаю, что столбец называется "мета" в кадре данных df:
df2 = pd.DataFrame(df['meta'].str.split().values.tolist())
Я хотел бы порекомендовать более эффективный и Pythonic способ.
Сначала определите DataFrame как исходный пост:
df = pd.DataFrame({"teams": [["SF", "NYG"] for _ in range(7)]})
Мое решение:
%%timeit
df['team1'], df['team2'] = zip(*list(df['teams'].values))
>> 761 µs ± 8.35 µs per loop
Для сравнения, решение с наибольшим количеством голосов:
%%timeit
df[['team1','team2']] = pd.DataFrame(df.teams.tolist(), index=df.index)
df = pd.DataFrame(df['teams'].to_list(), columns=['team1','team2'])
>> 1.31 ms ± 11.2 µs per loop
Мое решение экономит 40% времени и намного короче. Единственное, что вам нужно запомнить, это как распаковать и преобразовать двумерный список с помощью
zip(*list)
.
понимание списка
простая реализация с пониманием списка (мой любимый)
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
Вышеупомянутые решения не сработали для меня, так как у меня 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
Вот еще одно решение с использованием 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
Основываясь на предыдущих ответах, вот еще одно решение, которое возвращает тот же результат, что и 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)
Если кто-то придет сюда, чтобы найти готовую функцию, я написал ее.
- находит все столбцы со списками и разворачивает их, если
columns
не указаны; - добавленные столбцы называются
column_name_0
,column_name_1
, и т. д.; - порядок столбцов сохраняется в финальном кадре данных;
- если
strict=True
, он проверяет, имеют ли списки в данном столбце одинаковый размер.
Улучшения и комментарии приветствуются.
def unfold_columns(df, columns=[], strict=False):
assert isinstance(columns, list), "Columns should be a list of column names"
if len(columns) == 0:
columns = [
column for column in df.columns
if df.applymap(lambda x: isinstance(x, list)).all()[column]
]
else:
assert(all([(column in df.columns) for column in columns])), \
"Not all given columns are found in df"
columns_order = df.columns
for column_name in columns:
if df[column_name].apply(lambda x: isinstance(x, list)).all():
if strict:
assert len(set(df[column_name].apply(lambda x: len(x)))) == 1, \
f"Lists in df['{column_name}'] are not of equal length"
unfolded = pd.DataFrame(df[column_name].tolist())
unfolded.columns = [f'{column_name}_{x}' for x in unfolded.columns]
columns_order = [
*columns_order[:list(columns_order).index(column_name)],
*unfolded.columns,
*columns_order[list(columns_order).index(column_name)+1:]
]
df = df.join(unfolded).drop([column_name], axis=1)
return df[columns_order]
Чтобы добавить два новых столбца в существующий DataFrame:
df[['team1', 'team2']] = df["teams"].to_list()
Обобщение всех ответов. Если вам нужно просто создать новый DataFrame с двумя столбцами
pd.DataFrame(df['teams'].tolist(), columns=['team1', 'team2'], index=df.index)
Если вы хотите назначить один и тот же df, у вас есть несколько вариантов.
Кратчайший
df[['team1', 'team2']] = df['teams'].tolist()
Самый медленный (не рекомендую, он может быть в 10 раз медленнее и более без каких-либо преимуществ)
df[['team1', 'team2']] = df['teams'].apply(pd.Series)
И самый быстрый почему-то (почти в 2 раза быстрее первого).
df['team1'], df['team2'] = zip(*df['teams'].tolist())
Так что я бы рекомендовал 1. Если очень нужна скорость, можно попробовать 3, но это выглядит странно и в будущих версиях преимущество может исчезнуть.
вы можете попробовать использовать два раза применения, чтобы создать новый столбец «team1» и «team2» в вашем df
df = pd.DataFrame({"teams": [["SF", "NYG"] for _ in range(7)]})
df["team1"]=df['teams'].apply(lambda x: x[0] )
df["team2"]=df['teams'].apply(lambda x: x[1] )
df