Pandas 将数据框转换为元组数组

254
我使用pandas操作了一些数据,现在我想批量保存回数据库。这需要将数据框转换为元组数组,其中每个元组对应于数据框的一行。
我的数据框看起来像:
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. ]
有什么建议可以让我高效地完成这个任务吗?

45
对于那些在2017年及以后阅读此答案的人,下面有一个新的惯用解决方案。你可以直接使用list(df.itertuples(index=False, name=None)) - Ted Petrou
6
来到这个问题时,我寻找的两件事情是:元组列表 - df.to_records(index=False) 和字典列表 - df.to_dict('records') - Martin Thoma
1
@MartinThoma,to_records和to_dict('records')都搞砸了我的数据类型。已知的错误,但这使得这些解决方案毫无价值... - Jochen
11个回答

342
list(data_set.itertuples(index=False))
截至17.1版本,上述内容将返回一个带名称的元组列表
如果您想要一个普通元组列表,请将参数传递为name=None
list(data_set.itertuples(index=False, name=None))

54
在我看来,这应该是被接受的答案(现在有了专门的功能)。顺便说一下,如果您想在 zip 迭代器中使用普通的元组(而不是命名元组),那么请调用:data_set.itertuples(index=False, name=None) - Axel
3
实际上,它不应该。如果可能的话,避免使用itertuples,因为它很慢。在这些情况下,通常使用for循环(如接受答案中所示)会更快。 - cs95
4
从链接的问题中得到的教训是itertuples速度较慢,因为转换为元组通常比向量化/ Cython操作慢。 鉴于该问题要求转换为元组,我们是否有任何理由认为已接受的答案更快? 我进行的快速测试表明itertuples版本更快。 - T.C. Proctor
2
我在这个答案中发布了我的速度测试结果。 - T.C. Proctor
1
@cs95 itertuples is slow. .iterrows() 的性能与.itertuples() 相比非常差。在我运行的一些简单基准测试中,前者比后者慢了约1000倍,它们几乎无法进行比较。 - AMC
显示剩余3条评论

261

那怎么样:

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

对于 pandas 版本小于 0.24,请使用:

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

5
请查看@ksindi下面的答案,使用.itertuples将比获取值作为数组并将其转换为元组更有效。 - vy32
3
稍微改进一下就是:tuples=map(tuple,subset.values),意思是将subset中的值转换为元组并存储在tuples变量中。 - RufusVS
这可以将值转换为不同的类型,对吧? - AMC

52

动机
许多数据集足够大,以至于我们需要考虑速度/效率。因此,我提供了这个解决方案来迎合这一需求。它碰巧也很简洁。

为了比较起见,让我们删除index列。

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

解决方案
我建议使用zipmap

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

什么是 Quicker?

事实证明,在渐近收敛的情况下,records 是最快的,其次是 zipmapiter_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()

在此输入图片描述


50

一种通用的方式:

[tuple(x) for x in data_set.to_records(index=False)]

7
data_set.to_records(index=False).tolist() 不是更好吗? - Amir Shabani
建议使用@AmirShabani强制转换为List,以避免丢失数据类型和格式。例如,当列的类型为pandas DateTime时,日期格式将不会保持不变。 - Mahery Ranaivoson

17

最高效且简单的方法:

list(data_set.to_records())

在此调用之前,您可以筛选出所需的列。


3
我认为'to_records()'应该加上参数'index=False'。这样,使用'list(data_set.to_records(index=False))'可以得到一个不带索引的记录列表。 - user3415167
这不是OP所问的:to_records的结果不是一个元组,而是一个numpy记录数组。当你需要一个实际的tuple - 例如,因为你需要对其进行哈希 - 这个解决方案不适用。 - undefined

16

这里是一种矢量化的方法(假设数据框data_set被定义为df),它返回一个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列设置为索引轴的想法是为了利用DF.to_records中的convert_datetime64参数,通过使用DateTimeIndex数据框将Timestamp值转换为其对应的datetime.datetime格式等效形式。

这将返回一个recarray,可以使用.tolist使其返回一个list


根据用例的不同,更通用的解决方案可能是:

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

14

将数据框列表转换为元组列表。

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

2
请不要仅仅发布代码作为答案,还要提供解释您的代码是如何解决问题的。带有解释的答案通常质量更高,更有可能吸引赞同。 - Mark Rotteveel

11

这个答案没有提供任何新的回答,但是以下是一些速度测试结果。我认为这应该解决了评论中出现的问题。根据这三个值,所有这些看起来都是 O(n)

简短版: tuples = list(df.itertuples(index=False, name=None))tuples = list(zip(*[df[c].values.tolist() for c in df])) 速度最快。

我对这里提出的三个建议进行了快速速度测试:

  1. @pirsquared 的 zip 回答:tuples = list(zip(*[df[c].values.tolist() for c in df]))
  2. @wes-mckinney 接受的答案:tuples = [tuple(x) for x in df.values]
  3. @ksindi's 的 itertuples 回答,加上 @Axel 的 name=None 建议: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)

压缩版本和itertuples版本的结果在置信区间内。我猜它们在底层做了同样的事情。

虽然这些速度测试可能不太相关。推动计算机内存的极限并不需要太长时间,而且你真的不应该在大数据集上这样做。在这之后处理这些元组将非常低效。这不太可能成为你代码的主要瓶颈,所以只需选择你认为最易读的版本即可。


我更新了过期的帖子。我一直在使用[*zip(*map(df.get, df))]。无论如何,我认为你会觉得很有趣。 - piRSquared
@piRSquared 哦哦。我喜欢这个漂亮的图表。我猜看起来大约是*O(n)*。 - T.C. Proctor

4
#try this one:

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

3

更符合 Python 风格的方式:

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

更Pythonic的方式:实际上正好相反。map()是臭名昭著的不Pythonic的。 - AMC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接