як розділити стовпець кортежів у фреймі даних pandas?


88

У мене є кадр даних pandas (це лише невеликий фрагмент)

>>> 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.

Як я можу це зробити?

Відповіді:


159

Ви можете зробити це, виконавши 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
чи є спосіб його автоматизувати через велику кількість стовпців?
Донбео,

Я думаю, не безпосередньо. Але ви можете легко написати для нього функцію, використовуючи наведений вище код (+ видалення вихідного)
joris

Якщо у вас велика кількість стовпців, можливо, ви захочете « привести в порядок» свої дані: vita.had.co.nz/papers/tidy-data.html Ви можете зробити це за допомогою функції плавлення.
Аксель

.apply (pd.Series) працює нормально, але для великих наборів даних споживає багато пам'яті і може спричинити помилку в пам'яті
Юрій Гаманець

26

На набагато більших наборах даних я виявив, що .apply()на кілька порядків повільніше, ніжpd.DataFrame(df['b'].values.tolist(), index=df.index)

Цю проблему продуктивності було закрито в GitHub, хоча я не згоден з цим рішенням:

https://github.com/pandas-dev/pandas/issues/11615

EDIT: на основі цієї відповіді: https://stackoverflow.com/a/44196843/2230844


5
pd.DataFrame(df['b'].tolist())без, .valuesздається, теж чудово працює. (І дякую, ваше рішення набагато швидше, ніж .apply())
Swier

Мене турбувало захоплення індексу, отже явне використання .values.
denfromufa

1
рішення @denfromufa працює надшвидко df [['b1', 'b2']] = pd.DataFrame (df ['b']. values.tolist (), index = df.index) і не викликає помилок пам'яті (як порівняно з .apply (pd.Series))
Юрій Гаманець

17

Доступ, 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ітерабелі, а 'h'це другим елементом у 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)

Тест наївного часу

Короткий кадр даних

Використовуйте визначену вище

%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)

2
Подумайте про додавання TL; DR: df['a'], df['b'] = df.col.str:)
mirekphd

11

Я думаю, що простіший спосіб:

>>> 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

1
Ці рішення насправді набагато простіші
ApplePie

@jinhuawang виявляється, що це хак поверх strподання pd.Seriesоб'єкта. Можете пояснити, як це взагалі працює ?!
denfromufa

Я думаю, це просто те, як працює об’єкт str? ви можете отримати доступ до об’єкту масиву за допомогою str
Jinhua Wang

Що робити, якщо в деяких рядках є кортежі з різною кількістю значень?
mammykins

Я думаю, це має бути прийнятим. Це більше "панди-онік" ... якщо це річ.
Натача

8

Я знаю, що це давно, але застереження щодо другого рішення:

pd.DataFrame(df['b'].values.tolist())

полягає в тому, що він явно відкине індекс і додасть послідовний індекс за замовчуванням, тоді як прийнята відповідь

apply(pd.Series)

не буде, оскільки результат застосування збереже індекс рядка. Поки порядок спочатку зберігається з вихідного масиву, панди намагатимуться збігати показники з двох кадрів даних.

Це може бути дуже важливим, якщо ви намагаєтесь встановити рядки в числовий індексований масив, і панди автоматично спробують узгодити індекс нового масиву зі старим і спричинять певні спотворення в упорядкуванні.

Кращим гібридним рішенням було б встановити індекс вихідного кадру даних на новий, тобто

pd.DataFrame(df['b'].values.tolist(), index=df.index)

Що збереже швидкість використання другого методу, одночасно забезпечуючи збереження порядку та індексації результату.


Я відредагував свою відповідь на основі вашого спостереження за індексуванням, дякую!
denfromufa
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.