Панды конвертируют фрейм данных в массив кортежей


135

Я обработал некоторые данные с помощью панд, и теперь я хочу выполнить пакетное сохранение обратно в базу данных. Это требует, чтобы я преобразовал фрейм данных в массив кортежей, причем каждый кортеж соответствует «строке» фрейма данных.

Мой 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. ]

Любые предложения о том, как я могу это сделать эффективно?


22
Для тех, кто приходит к этому ответу в 2017+, ниже есть новое идиоматическое решение . Вы можете просто использоватьlist(df.itertuples(index=False, name=None))
Тед Петру

3
Когда я подхожу к этому вопросу, я ищу две вещи: список кортежей df.to_records(index=False)и список диктовок:df.to_dict('records')
Мартин Тома

@MartinThoma и to_records, и to_dict ('записи') меняют мои типы данных. Известная ошибка, но делает эти решения бесполезными ...
Йохен,

Ответы:


207

Как насчет:

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, который будет более эффективным, чем получение значений в виде массива и их преобразование в кортеж.
vy32

1
немного чище: tuples = map (tuple, subset.values)
RufusVS

Однако это может привести значения к другому типу, верно?
AMC,

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

Начиная с 17.1, приведенное выше будет возвращать список именованных кортежей .

Если вам нужен список обычных кортежей, передайте name=Noneв качестве аргумента:

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

39
Это должен быть принятый ответ ИМХО (теперь, когда существует специальная функция). Кстати, если вам нужен нормальный tuples в своем zipитераторе (вместо namedtuples), тогда звоните:data_set.itertuples(index=False, name=None)
Axel


3
@coldspeed Урок, который я получил из связанного вопроса, заключается в том, что itertuples работает медленно, потому что преобразование в кортежи обычно медленнее, чем операции векторизации / cython. Учитывая, что вопрос предлагает преобразовать в кортежи, есть ли причина, по которой мы могли бы подумать, что принятый ответ быстрее? Проведенный мною быстрый тест показывает, что версия 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фрейма данных.

Это возвращает a, recarrayкоторый затем можно заставить вернуть listusing.tolist


Более обобщенное решение в зависимости от варианта использования:

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

10

Самый эффективный и простой способ:

list(data_set.to_records())

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


2
Я думаю, что index = False следует указывать в качестве аргумента to_records (). Таким образом, list (data_set.to_records (index = False))
user3415167

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. Ответ на itertuples от @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 Ооо. Мне нравится красивый сюжет. Я предполагаю, что это похоже на O (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.