提问人:TylerD 提问时间:10/24/2023 最后编辑:Timur ShtatlandTylerD 更新时间:10/26/2023 访问量:120
从一长串字典中高效提取元素
Extracting elements from a long list of dictionaries efficiently
问:
我有一个(很长的)词典列表,但为了这个例子,我将它们表示为
d = [{'a':1}, {'a':2}, {'a':3}]
我需要从这些字典中提取相同的元素,即
[i['a'] for i in d]
在 Python 中执行此操作的最有效方法是什么?列表推导式和 for 循环效果很好,但效率不高。该过程可以以某种方式矢量化吗?
其他细节:字典有多个键,但与我需要提取的键相同。所有词典都有相同的键。
答:
2赞
Timur Shtatland
10/24/2023
#1
用。您必须支付导入和创建数据框的前期成本。但后续操作是矢量化和高效的:pandas
import pandas as pd
d = [{'a':1, 'b':11}, {'a':2, 'b':12}, {'a':3, 'b':13}]
df = pd.DataFrame(d)
print(df['a'])
print(list(df['a']))
指纹:
0 1
1 2
2 3
Name: a, dtype: int64
[1, 2, 3]
标杆:
结果是,对于中等大小的数据集,数据框比字典列表略快,这还不包括创建数据框的成本。pandas
基准测试代码基于 JL Peyret 的答案。
请注意,与该答案不同的是,我将数据帧初始化(不仅仅是)放在基准测试循环之外。我只是对数据结构元素的访问进行基准测试。数据结构也具有更多行(100 万)。我假设这种情况是中型数据集的另一种现实情况。import pandas
import random
import pandas as pd
import timeit
def pandas_dict(datain):
return list(df["a"])
def list_comp(datain):
return [v["a"] for v in datain]
nrows = 1000000
rand = list(range(nrows))
random.shuffle(rand)
data = [{"a":rand[i], "b":rand[i]} for i in range(nrows)]
df = pd.DataFrame(data)
print(df)
results = []
for totest in [list_comp, pandas_dict]:
print(totest.__name__)
print(timeit.timeit(stmt='totest(data)', number=100, globals=globals()))
res = totest(data)
results.append(res)
print(f"{res[0:10]=}, {len(res)=}")
结果:
a b
0 847669 847669
1 777701 777701
2 446229 446229
3 984577 984577
4 813383 813383
... ... ...
999995 636811 636811
999996 413271 413271
999997 346275 346275
999998 414864 414864
999999 381832 381832
[1000000 rows x 2 columns]
list_comp
4.460098167066462
res[0:10]=[847669, 777701, 446229, 984577, 813383, 705699, 7830, 466819, 485673, 400344], len(res)=1000000
pandas_dict
4.201689457986504
res[0:10]=[847669, 777701, 446229, 984577, 813383, 705699, 7830, 466819, 485673, 400344], len(res)=1000000
另请参阅:
-1赞
Waket Zheng
10/24/2023
#2
使用生成器怎么样:
from typing import Any, Generator
def iter_one_key(ds: list[dict[str, Any]], key: str) -> Generator:
for i in range(len(ds)):
yield ds[i][key]
def main():
d = [{"a": 1}, {"a": 2}, {"a": 3}]
for v in iter_one_key(d, "a"):
print(v)
if __name__ == "__main__":
main()
1赞
dawg
10/25/2023
#3
我想如果我不需要熊猫,我会直接这样做:
LoD = [{'a':1}, {'b':2}, {'c':18}, {'a':3}, {'b':3}, {'c':3}, {'b':2}]
di={}
for d in LoD:
for k,v in d.items():
di.setdefault(k, []).append(v)
>>> di
{'a': [1, 3], 'b': [2, 3, 2], 'c': [18, 3]}
2赞
JL Peyret
10/25/2023
#4
除非我错过了什么,否则列表理解速度最快,然后是极地,然后是熊猫。
import timeit
import polars as pl
import pandas as pd
def polars_dict(datain):
df = pl.from_dicts(datain)
return df.to_series(0).to_list()
def pandas_dict(datain):
df = pd.DataFrame(datain)
return list(df["a"])
def list_comp(datain):
return [v["a"] for v in datain]
data = [{"a":1,"b":2}] * 10000
results = []
for totest in [list_comp, polars_dict, pandas_dict]:
print(totest.__name__)
print(timeit.timeit(stmt='totest(data)',number=10,globals=globals()))
res = totest(data)
results.append(res)
print(f"{res[0:50]=}, {len(res)=}")
print(results[0] == results[1] == results[2])
输出:
list_comp
0.003527630993630737
res[0:50]=[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], len(res)=10000
polars_dict
0.02488957904279232
res[0:50]=[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], len(res)=10000
pandas_dict
0.06578049197560176
res[0:50]=[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], len(res)=10000
True
一句话:就其本身而言,没有其他事情可做,无论是大熊猫还是极地,都不会改善任何事情。您可以改变数据长度以使其更大,但这并没有改变太多。
p.s. 这是在 3.11 上。3.12 优化了之前在幕后生成的函数,以迭代列表复合中的项目,现在将其内联展开。这应该可以消除函数调用开销。
评论
collections.defaultdict(list)