Как разбить столбец кортежей в 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)