Панди перетворюють кадр даних у масив кортежів


131

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

Мій DataFrame виглядає приблизно так:

In [182]: data_set
Out[182]: 
  index data_date   data_1  data_2
0  14303 2012-02-17  24.75   25.03 
1  12009 2012-02-16  25.00   25.07 
2  11830 2012-02-15  24.99   25.15 
3  6274  2012-02-14  24.68   25.05 
4  2302  2012-02-13  24.62   24.77 
5  14085 2012-02-10  24.38   24.61 

Я хочу перетворити його в масив кортежів:

[(datetime.date(2012,2,17),24.75,25.03),
(datetime.date(2012,2,16),25.00,25.07),
...etc. ]

Будь-яка пропозиція щодо того, як я можу це ефективно зробити?


21
Для тих, хто прийшов до цієї відповіді у 2017 році ++, нижче є нове ідіоматичне рішення . Ви можете просто скористатисяlist(df.itertuples(index=False, name=None))
Тед Петру

3
Дві речі, які я шукаю, коли приходжу до цього питання: Список кортежів - df.to_records(index=False)і список диктовок:df.to_dict('records')
Мартін Тома

@MartinThoma і to_records, і to_dict ('записи') накручують мої типи даних. Відома помилка, але робить це рішення нікчемним ...
Jochen

Відповіді:


206

Як щодо:

subset = data_set[['data_date', 'data_1', 'data_2']]
tuples = [tuple(x) for x in subset.to_numpy()]

для панд <0,24 використання

tuples = [tuple(x) for x in subset.values]

2
Нижче див. Відповідь @ ksindi щодо використання .itertuples, що буде ефективніше, ніж отримання значень у масиві та t перенесення їх у кортеж.
vy32

1
трохи чистішим є: кортежі = карта (кортеж, підмножина)
RufusVS

Це може передавати значення іншому типу, правда?
AMC

160
list(data_set.itertuples(index=False))

Станом на 17.1 вищенаведений поверне список названих пар .

Якщо ви хочете список звичайних кортежів, передайте name=Noneяк аргумент:

list(data_set.itertuples(index=False, name=None))

39
Це має бути прийнятою відповіддю IMHO (тепер, коли існує спеціальна функція). BTW, якщо ви хочете мати нормальний tuples у своєму zipітераторі (замість namedtuples), тоді телефонуйте:data_set.itertuples(index=False, name=None)
Axel


3
@coldspeed Урок, який я отримав із пов'язаного запитання, полягає в тому, що ітератури є повільними, оскільки перетворення в кортежі, як правило, повільніше, ніж операції з векторизованим цитоном. Зважаючи на те, що питання задається перетворенням на кортежі, чи є якась причина, що ми думаємо, що прийнята відповідь швидша? Швидкий тест, який я зробив, вказує, що версія itertuples швидша.
TC Proctor

2
Я опублікував свої результати тесту на швидкість у цій відповіді
TC Proctor

1
@johnDanger це схоже на поняття eval () та globals () у python. Усі знають, що вони існують. Усі також знають, що ти зазвичай не повинен використовувати ці функції, оскільки це вважається поганою формою. Принцип тут схожий, дуже мало випадків використання сімейства iter * в пандах, це, мабуть, один із них. Я все одно використовую інший метод (наприклад, список комп’ютерів чи карта), але це я.
cs95


30

Мотивація
Багато наборів даних є досить великими, що нам потрібно дбати про швидкість / ефективність. Тому я пропоную це рішення в такому дусі. Це буває і стислим.

Для порівняння давайте опустимо indexстовпчик

df = data_set.drop('index', 1)

Рішення
Я запропоную використовувати zipтаmap

list(zip(*map(df.get, df)))

[('2012-02-17', 24.75, 25.03),
 ('2012-02-16', 25.0, 25.07),
 ('2012-02-15', 24.99, 25.15),
 ('2012-02-14', 24.68, 25.05),
 ('2012-02-13', 24.62, 24.77),
 ('2012-02-10', 24.38, 24.61)]

Це може бути також гнучким, якщо ми хотіли мати справу з певним набором стовпців. Ми припустимо, що стовпці, які ми вже відображали, - це підмножина, яку ми хочемо.

list(zip(*map(df.get, ['data_date', 'data_1', 'data_2'])))

[('2012-02-17', 24.75, 25.03),
 ('2012-02-16', 25.0, 25.07),
 ('2012-02-15', 24.99, 25.15),
 ('2012-02-14', 24.68, 25.05),
 ('2012-02-13', 24.62, 24.77),
 ('2012-02-10', 24.38, 24.61)]

Що таке швидше?

Поворот recordsшвидко виходить з асимптотично сходяться zipmapіiter_tuples

Я буду використовувати бібліотеку, simple_benchmarksяку я отримав з цієї посади

from simple_benchmark import BenchmarkBuilder
b = BenchmarkBuilder()

import pandas as pd
import numpy as np

def tuple_comp(df): return [tuple(x) for x in df.to_numpy()]
def iter_namedtuples(df): return list(df.itertuples(index=False))
def iter_tuples(df): return list(df.itertuples(index=False, name=None))
def records(df): return df.to_records(index=False).tolist()
def zipmap(df): return list(zip(*map(df.get, df)))

funcs = [tuple_comp, iter_namedtuples, iter_tuples, records, zipmap]
for func in funcs:
    b.add_function()(func)

def creator(n):
    return pd.DataFrame({"A": random.randint(n, size=n), "B": random.randint(n, size=n)})

@b.add_arguments('Rows in DataFrame')
def argument_provider():
    for n in (10 ** (np.arange(4, 11) / 2)).astype(int):
        yield n, creator(n)

r = b.run()

Перевірте результати

r.to_pandas_dataframe().pipe(lambda d: d.div(d.min(1), 0))

        tuple_comp  iter_namedtuples  iter_tuples   records    zipmap
100       2.905662          6.626308     3.450741  1.469471  1.000000
316       4.612692          4.814433     2.375874  1.096352  1.000000
1000      6.513121          4.106426     1.958293  1.000000  1.316303
3162      8.446138          4.082161     1.808339  1.000000  1.533605
10000     8.424483          3.621461     1.651831  1.000000  1.558592
31622     7.813803          3.386592     1.586483  1.000000  1.515478
100000    7.050572          3.162426     1.499977  1.000000  1.480131

r.plot()

введіть тут опис зображення


12

Ось векторизованних підхід (за умови , dataframe, data_setщоб визначити , як dfзамість цього) , що повертає listз , tuplesяк показано нижче:

>>> df.set_index(['data_date'])[['data_1', 'data_2']].to_records().tolist()

виробляє:

[(datetime.datetime(2012, 2, 17, 0, 0), 24.75, 25.03),
 (datetime.datetime(2012, 2, 16, 0, 0), 25.0, 25.07),
 (datetime.datetime(2012, 2, 15, 0, 0), 24.99, 25.15),
 (datetime.datetime(2012, 2, 14, 0, 0), 24.68, 25.05),
 (datetime.datetime(2012, 2, 13, 0, 0), 24.62, 24.77),
 (datetime.datetime(2012, 2, 10, 0, 0), 24.38, 24.61)]

Ідея встановити стовпець datetime як вісь індексу полягає у сприянні перетворенню Timestampзначення у відповідний datetime.datetimeформат, еквівалентному, використовуючи convert_datetime64аргумент, у DF.to_recordsякому це робиться дляDateTimeIndex фрейму даних.

Це повертає те, recarrayщо може бути зроблено для повернення listвикористання.tolist


Більш узагальненим рішенням залежно від випадку використання буде:

df.to_records().tolist()                              # Supply index=False to exclude index

10

Найефективніший і найпростіший спосіб:

list(data_set.to_records())

Ви можете відфільтрувати потрібні стовпці до цього дзвінка.


1
Я думаю, що "index = False" слід наводити як аргумент to_records (). Таким чином, список (data_set.to_records (index = False))
користувач3415167

8

Ця відповідь не додає відповідей, які вже не обговорювалися, але ось деякі результати швидкості. Я думаю, що це повинно вирішити питання, які виникли в коментарях. Усі вони виглядають як O (n) , виходячи з цих трьох значень.

TL; DR : tuples = list(df.itertuples(index=False, name=None))і tuples = list(zip(*[df[c].values.tolist() for c in df]))прив'язуються найшвидше.

Тут я зробив швидкий тест на швидкість на три пропозиції:

  1. Відповідь на поштовий індекс від @pirsquared: tuples = list(zip(*[df[c].values.tolist() for c in df]))
  2. Прийнята відповідь від @ wes-mckinney: tuples = [tuple(x) for x in df.values]
  3. Посилання на відповідь від @ksindi з name=Noneпропозицією від @Axel:tuples = list(df.itertuples(index=False, name=None))
from numpy import random
import pandas as pd


def create_random_df(n):
    return pd.DataFrame({"A": random.randint(n, size=n), "B": random.randint(n, size=n)})

Маленький розмір:

df = create_random_df(10000)
%timeit tuples = list(zip(*[df[c].values.tolist() for c in df]))
%timeit tuples = [tuple(x) for x in df.values]
%timeit tuples = list(df.itertuples(index=False, name=None))

Дає:

1.66 ms ± 200 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
15.5 ms ± 1.52 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
1.74 ms ± 75.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Більший:

df = create_random_df(1000000)
%timeit tuples = list(zip(*[df[c].values.tolist() for c in df]))
%timeit tuples = [tuple(x) for x in df.values]
%timeit tuples = list(df.itertuples(index=False, name=None))

Дає:

202 ms ± 5.91 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
1.52 s ± 98.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
209 ms ± 11.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Стільки терпіння, скільки у мене:

df = create_random_df(10000000)
%timeit tuples = list(zip(*[df[c].values.tolist() for c in df]))
%timeit tuples = [tuple(x) for x in df.values]
%timeit tuples = list(df.itertuples(index=False, name=None))

Дає:

1.78 s ± 118 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
15.4 s ± 222 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
1.68 s ± 96.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Версія zip та версія itertuples знаходяться в довірчих інтервалах. Я підозрюю, що вони роблять те саме під кришкою.

Ці випробування на швидкість, мабуть, не мають значення. Натискання меж пам'яті мого комп’ютера не займає величезної кількості часу, і вам дійсно не слід робити це на великому наборі даних. Робота з цими кортежами після цього закінчиться справді неефективною. Навряд чи це буде головне вузьке місце у вашому коді, тому просто дотримуйтесь версії, яку ви вважаєте найбільш зрозумілою.


Я оновив свою несвіжу посаду. Я використовував [*zip(*map(df.get, df))]колись зараз. У всякому разі, думав, що вам це буде цікаво.
piRSquared

@piRSquared Oooh. Мені подобається гарний сюжет. Я думаю, це виглядає так, як це О (n) .
TC Proctor

2
#try this one:

tuples = list(zip(data_set["data_date"], data_set["data_1"],data_set["data_2"]))
print (tuples)

2

Зміна списку кадрів даних на список кортежів.

df = pd.DataFrame({'col1': [1, 2, 3], 'col2': [4, 5, 6]})
print(df)
OUTPUT
   col1  col2
0     1     4
1     2     5
2     3     6

records = df.to_records(index=False)
result = list(records)
print(result)
OUTPUT
[(1, 4), (2, 5), (3, 6)]

1
Будь ласка, не публікуйте код лише як відповідь, але також надайте пояснення, що робить ваш код і як він вирішує проблему. Відповіді з поясненням, як правило, вищої якості і, швидше за все, залучають репутацію.
Марк Ротвевель

1

Більш пітонічний спосіб:

df = data_set[['data_date', 'data_1', 'data_2']]
map(tuple,df.values)

Більш пітонічний спосіб: насправді навпаки. map()є сумно непіфонічним.
AMC
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.