从一长串字典中高效提取元素

Extracting elements from a long list of dictionaries efficiently

提问人:TylerD 提问时间:10/24/2023 最后编辑:Timur ShtatlandTylerD 更新时间:10/26/2023 访问量:120

问:

我有一个(很长的)词典列表,但为了这个例子,我将它们表示为

d = [{'a':1}, {'a':2}, {'a':3}]

我需要从这些字典中提取相同的元素,即

[i['a'] for i in d]

在 Python 中执行此操作的最有效方法是什么?列表推导式和 for 循环效果很好,但效率不高。该过程可以以某种方式矢量化吗?


其他细节:字典有多个键,但与我需要提取的键相同。所有词典都有相同的键。

性能 字典 矢 量化

评论

4赞 Jean-François Fabre 10/24/2023
例如,使用列表字典一劳永逸地重新组织您的数据collections.defaultdict(list)

答:

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

另请参阅:

Pandas DataFrame 性能与字典的比较

-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 优化了之前在幕后生成的函数,以迭代列表复合中的项目,现在将其内联展开。这应该可以消除函数调用开销。