Как разбить столбец кортежей в pandas dataframe?
У меня есть пандас dataframe (это только маленький кусочек)
>>> d1
y norm test y norm train len(y_train) len(y_test) \
0 64.904368 116.151232 1645 549
1 70.852681 112.639876 1645 549
SVR RBF \
0 (35.652207342877873, 22.95533537448393)
1 (39.563683797747622, 27.382483096332511)
LCV \
0 (19.365430594452338, 13.880062435173587)
1 (19.099614489458364, 14.018867136617146)
RIDGE CV \
0 (4.2907610988480362, 12.416745648065584)
1 (4.18864306788194, 12.980833914392477)
RF \
0 (9.9484841581029428, 16.46902345373697)
1 (10.139848213735391, 16.282141345406522)
GB \
0 (0.012816232716538605, 15.950164822266007)
1 (0.012814519804493328, 15.305745202851712)
ET DATA
0 (0.00034337162272515505, 16.284800366214057) j2m
1 (0.00024811554516431878, 15.556506191784194) j2m
>>>
Я хочу разделить все столбцы, которые содержат кортежи. Например, я хочу заменить столбец LCV
столбцами LCV-a
и LCV-b
.
Как я могу это сделать?
Ответы
Ответ 1
Вы можете сделать это, выполнив pd.DataFrame(col.tolist())
для этого столбца:
In [2]: df = pd.DataFrame({'a':[1,2], 'b':[(1,2), (3,4)]})
In [3]: df
Out[3]:
a b
0 1 (1, 2)
1 2 (3, 4)
In [4]: df['b'].tolist()
Out[4]: [(1, 2), (3, 4)]
In [5]: pd.DataFrame(df['b'].tolist(), index=df.index)
Out[5]:
0 1
0 1 2
1 3 4
In [6]: df[['b1', 'b2']] = pd.DataFrame(df['b'].tolist(), index=df.index)
In [7]: df
Out[7]:
a b b1 b2
0 1 (1, 2) 1 2
1 2 (3, 4) 3 4
Примечание: в более ранней версии этот ответ рекомендовал использовать df['b'].apply(pd.Series)
вместо pd.DataFrame(df['b'].tolist(), index=df.index)
. Это также работает (потому что это делает из каждого кортежа Серию, которая затем рассматривается как строка информационного кадра), но медленнее/использует больше памяти, чем версия tolist
, как отмечено другими ответами здесь (благодаря @denfromufa).
Я обновил этот ответ, чтобы у наиболее заметного ответа было лучшее решение.
Ответ 2
В гораздо больших наборах данных я обнаружил, что .apply()
на несколько порядков медленнее, чем pd.DataFrame(df['b'].values.tolist(), index=df.index)
Эта проблема производительности была закрыта в GitHub, хотя я не согласен с этим решением:
https://github.com/pandas-dev/pandas/issues/11615
РЕДАКТИРОВАТЬ: на основе этого ответа: fooobar.com/questions/1687577/...
Ответ 3
Я знаю, что это давно, но предостережение о втором решении:
pd.DataFrame(df['b'].values.tolist())
является то, что он явно отбрасывает индекс и добавляет последовательный индекс по умолчанию, тогда как принятый ответ
apply(pd.Series)
не будет, так как результат применения сохранит индекс строки. Хотя порядок первоначально сохраняется из исходного массива, pandas будет пытаться сопоставить индикаторы с двух кадров данных.
Это может быть очень важно, если вы пытаетесь установить строки в числовом индексированном массиве, и pandas автоматически попытается сопоставить индекс нового массива со старым и вызвать некоторые искажения в порядке.
Лучшим гибридным решением было бы установить индекс исходного кадра данных на новый, т.е.
pd.DataFrame(df['b'].values.tolist(), index=df.index)
Который сохранит скорость использования второго метода при сохранении порядка и индексации на результат.
Ответ 4
Я думаю, что более простой способ:
>>> import pandas as pd
>>> df = pd.DataFrame({'a':[1,2], 'b':[(1,2), (3,4)]})
>>> df
a b
0 1 (1, 2)
1 2 (3, 4)
>>> df['b_a']=df['b'].str[0]
>>> df['b_b']=df['b'].str[1]
>>> df
a b b_a b_b
0 1 (1, 2) 1 2
1 2 (3, 4) 3 4
Ответ 5
str
сбруя, который доступен для pandas.Series
объектов dtype == object
фактически итератор.
Предположим, что pandas.DataFrame
df
:
df = pd.DataFrame(dict(col=[*zip('abcdefghij', range(10, 101, 10))]))
df
col
0 (a, 10)
1 (b, 20)
2 (c, 30)
3 (d, 40)
4 (e, 50)
5 (f, 60)
6 (g, 70)
7 (h, 80)
8 (i, 90)
9 (j, 100)
Мы можем проверить, является ли это итеративным
from collections import Iterable
isinstance(df.col.str, Iterable)
True
Затем мы можем назначить из него, как мы делаем другие итерации:
var0, var1 = 'xy'
print(var0, var1)
x y
Самое простое решение
Таким образом, в одной строке мы можем назначить оба столбца
df['a'], df['b'] = df.col.str
df
col a b
0 (a, 10) a 10
1 (b, 20) b 20
2 (c, 30) c 30
3 (d, 40) d 40
4 (e, 50) e 50
5 (f, 60) f 60
6 (g, 70) g 70
7 (h, 80) h 80
8 (i, 90) i 90
9 (j, 100) j 100
Более быстрое решение
Только немного сложнее, мы можем использовать zip
для создания подобного итерируемого
df['c'], df['d'] = zip(*df.col)
df
col a b c d
0 (a, 10) a 10 a 10
1 (b, 20) b 20 b 20
2 (c, 30) c 30 c 30
3 (d, 40) d 40 d 40
4 (e, 50) e 50 e 50
5 (f, 60) f 60 f 60
6 (g, 70) g 70 g 70
7 (h, 80) h 80 h 80
8 (i, 90) i 90 i 90
9 (j, 100) j 100 j 100
В соответствии
Смысл, не видоизменять существующий df
Это работает, потому что assign
принимает аргументы ключевого слова, где ключевыми словами являются новые (или существующие) имена столбцов, а значения будут значениями нового столбца. Вы можете использовать словарь и распаковать его с помощью **
и использовать его в качестве аргументов ключевого слова. Так что это умный способ назначить новый столбец с именем 'g'
который является первым элементом в df.col.str
df.col.str, и 'h'
который является вторым элементом в df.col.str
df.col.str.
df.assign(**dict(zip('gh', df.col.str)))
col g h
0 (a, 10) a 10
1 (b, 20) b 20
2 (c, 30) c 30
3 (d, 40) d 40
4 (e, 50) e 50
5 (f, 60) f 60
6 (g, 70) g 70
7 (h, 80) h 80
8 (i, 90) i 90
9 (j, 100) j 100
Моя версия list
подход
С современным пониманием списка и распаковкой переменных.
Примечание: также встроенный, используя join
df.join(pd.DataFrame([*df.col], df.index, [*'ef']))
col g h
0 (a, 10) a 10
1 (b, 20) b 20
2 (c, 30) c 30
3 (d, 40) d 40
4 (e, 50) e 50
5 (f, 60) f 60
6 (g, 70) g 70
7 (h, 80) h 80
8 (i, 90) i 90
9 (j, 100) j 100
Мутантная версия будет
df[['e', 'f']] = pd.DataFrame([*df.col], df.index)
Наивный тест на время
Короткий DataFrame Используйте один из указанных выше
%timeit df.assign(**dict(zip('gh', df.col.str)))
%timeit df.assign(**dict(zip('gh', zip(*df.col))))
%timeit df.join(pd.DataFrame([*df.col], df.index, [*'gh']))
1.16 ms ± 21.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
635 µs ± 18.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
795 µs ± 42.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Длинный фрейм данных В 10 ^ 3 раза больше
df = pd.concat([df] * 1000, ignore_index=True)
%timeit df.assign(**dict(zip('gh', df.col.str)))
%timeit df.assign(**dict(zip('gh', zip(*df.col))))
%timeit df.join(pd.DataFrame([*df.col], df.index, [*'gh']))
11.4 ms ± 1.53 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.1 ms ± 41.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.33 ms ± 35.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)