我有一個排序的字典串列,如下所示:
dat = [
{"id1": 1, "id2": 2, "value": 1},
{"id1": 1, "id2": 2, "value": 2},
{"id1": 2, "id2": 2, "value": 2},
{"id1": 2, "id2": 3, "value": 1},
{"id1": 3, "id2": 3, "value": 1},
{"id1": 3, "id2": 4, "value": 1},
{"id1": 3, "id2": 4, "value": 1},
{"id1": 3, "id2": 4, "value": 1},
{"id1": 3, "id2": 4, "value": 1},
]
這實際上是 (id1, id2, value) 元組,但有重復。我想通過對兩個 id 相等的值求和來對這些進行重復資料洗掉,留下唯一的 (id1, id2) 對,其中新值是重復值的總和。
也就是說,從上面看,所需的輸出是:
dat =[
{'id1': 1, 'id2': 2, 'value': 3},
{'id1': 2, 'id2': 2, 'value': 2},
{'id1': 2, 'id2': 3, 'value': 1},
{'id1': 3, 'id2': 3, 'value': 1},
{'id1': 3, 'id2': 4, 'value': 4}
]
假設串列有數百萬個重復項。itertools使用or funcy(與使用 pandas 相比)最有效的方法是什么?
uj5u.com熱心網友回復:
您可以從運算子開始collections.Counter并使用它 =,方便的部分Counter是在不存在的 =鍵上假設為零。
dat = [
{"id1": 1, "id2": 2, "value": 1},
{"id1": 1, "id2": 2, "value": 2},
{"id1": 2, "id2": 2, "value": 2},
{"id1": 2, "id2": 3, "value": 1},
{"id1": 3, "id2": 3, "value": 1},
{"id1": 3, "id2": 4, "value": 1},
{"id1": 3, "id2": 4, "value": 1},
{"id1": 3, "id2": 4, "value": 1},
{"id1": 3, "id2": 4, "value": 1},
]
from collections import Counter
cnt = Counter()
for item in dat:
cnt[item["id1"], item["id2"]] = item["value"]
[{'id1':id1, 'id2': id2, 'value':v}for (id1, id2), v in cnt.items()]
給予
[{'id1': 1, 'id2': 2, 'value': 3},
{'id1': 2, 'id2': 2, 'value': 2},
{'id1': 2, 'id2': 3, 'value': 1},
{'id1': 3, 'id2': 3, 'value': 1},
{'id1': 3, 'id2': 4, 'value': 4}]
uj5u.com熱心網友回復:
只是為了好玩,一個純粹的itertools解決方案(不使用collections或以其他方式使用任何必須構建和更新的中間容器,如果list它已經是關鍵順序,但如果你不能保證它已經排序到組,它需要預先排序唯一的 id 對在一起):
# At top of file
from itertools import groupby
# Also at top of file; not strictly necessary, but I find it's nicer to make cheap getters
# with self-documenting names
from operator import itemgetter
get_ids = itemgetter('id1', 'id2')
get_value = itemgetter('value')
# On each use:
dat.sort(key=get_ids) # Not needed if data guaranteed grouped by unique id1/id2 pairs as in example
dat = [{'id1': id1, 'id2': id2, 'value': sum(map(get_value, group))}
for (id1, id2), group in groupby(dat, key=get_ids)]
# If sorting needed, you can optionally one-line as the rather overly dense (I don't recommend it):
dat = [{'id1': id1, 'id2': id2, 'value': sum(map(get_value, group))}
for (id1, id2), group in groupby(sorted(dat, key=get_ids), key=get_ids)]
就個人而言,我通常會使用Counteror defaultdict(int),如其他答案所示,因為O(n)即使使用未排序的資料(groupbyis O(n),但如果您需要先排序,排序是O(n log n))它們也能獲得性能。基本上,這甚至具有理論上的優勢的唯一一次是當資料已經排序并且您重視使用單行(不包括匯入和制作 s 的一次性設定成本itemgetter)時;在實踐中,itertools.groupby有足夠的開銷,它通常仍然會輸給collections.Counter/中的一個或兩個collections.defaultdict(int),特別是在使用collections.Counter其優化模式來計算要計算的事物的可迭代時(此處不適用,但值得了解)。
uj5u.com熱心網友回復:
我們也可以使用collections.defaultdict:
from collections import defaultdict
tmp = defaultdict(int)
for d in dat:
tmp[d['id1'], d['id2']] = d['value']
out = [{'id1':id1, 'id2':id2, 'value':v} for (id1, id2), v in tmp.items()]
或(假設 id 已排序)itertools.groupby,:
from itertools import groupby
out = [{'id1': k1, 'id2': k2, 'value': sum(d['value'] for d in g)} for (k1,k2), g in groupby(dat, lambda x: (x['id1'], x['id2']))]
或groupby sum to_dict中pandas:
out = pd.DataFrame(dat).groupby(['id1','id2'], as_index=False)['value'].sum().to_dict('records')
輸出:
[{'id1': 1, 'id2': 2, 'value': 3},
{'id1': 2, 'id2': 2, 'value': 2},
{'id1': 2, 'id2': 3, 'value': 1},
{'id1': 3, 'id2': 3, 'value': 1},
{'id1': 3, 'id2': 4, 'value': 4}]
所提供資料的基本基準表明groupby使用itemgetter(如@ShadowRanger 所建議)是最快的:
- 默認字典:
6.57 μs ± 491 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) - 計數器(鮑勃):
9.56 μs ± 1.47 μs per loop (mean ± std. dev. of 7 runs, 100000 loops each) - groupby itemgetter ( ShadowRanger ):
6.01 μs ± 182 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) - groupby lambda:
9.02 μs ± 598 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) - 熊貓:
3.81 ms ± 68.2 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
現在如果我們重復dat100 萬次,即做
dat = dat*1_000_000
dat.sort(key=itemgetter('id1', 'id2'))
并再次執行相同的基準測驗,groupby結果itemgetter是失控的贏家:
3.91 s ± 320 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)5.38 s ± 251 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)1.77 s ± 128 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)3.53 s ± 199 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)15.2 s ± 831 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
ran on Python 3.9.7 (64bit).
This benchmark somewhat favors groupby since there are very few groups when we duplicate an existing small list of dicts. If create randomize the size of "group"s, groupby itemgetter still beats all but the difference is not as stark.
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/436678.html
