优雅的方法来减少一个字典列表?

5

我有一个字典列表,每个字典都包含完全相同的键。我想找到每个键的平均值,并且我想知道如何使用reduce(如果不可能使用嵌套的for循环以外)更优雅的方式来完成。

这是列表:

[
  {
    "accuracy": 0.78,
    "f_measure": 0.8169374016795885,
    "precision": 0.8192088044235794,
    "recall": 0.8172222222222223
  },
  {
    "accuracy": 0.77,
    "f_measure": 0.8159133315763016,
    "precision": 0.8174754717495807,
    "recall": 0.8161111111111111
  },
  {
    "accuracy": 0.82,
    "f_measure": 0.8226353934130455,
    "precision": 0.8238175920455686,
    "recall": 0.8227777777777778
  }, ...
]

I would like to get back I dictionary like this:

{
  "accuracy": 0.81,
  "f_measure": 0.83,
  "precision": 0.84,
  "recall": 0.83
}

这是我目前的翻译,但我不太满意:

folds = [ ... ]

keys = folds[0].keys()
results = dict.fromkeys(keys, 0)

for fold in folds:
    for k in keys:
        results[k] += fold[k] / len(folds)

print(results)

4
你能分享一下你目前为止的尝试吗? - TigerhawkT3
我想使用reduce来完成它。那么为什么选择reduce呢?你目前尝试了什么? - Mazdak
转换为数组值的字典。示例:{ “准确度”:[0.82,val2,...], “F度量”:[0.8226353934130455,val2,...], “精确度”:[0.8238175920455686,val2,...], “召回率”:[0.8227777777777778,val2,...], } - Swadhikar
你期望的结果不准确;“精度”平均值实际上是0.82,而不是0.84,“准确度”平均值为0.79。 - Martijn Pieters
@TigerhawkT3 看看我的修改。虽然我不明白你的问题。但是我说我想用更优雅的方式来完成它,而不是使用for循环。 - Christos Baziotis
@MartijnPieters 我并没有实际计算这些数字。这只是一个示例,以展示预期结果的格式... - Christos Baziotis
5个回答

9
作为替代方案,如果您要对数据进行这样的计算,那么您可能希望使用pandas(对于一次性操作来说有些过头了,但会大大简化此类任务...)
import pandas as pd

data = [
  {
    "accuracy": 0.78,
    "f_measure": 0.8169374016795885,
    "precision": 0.8192088044235794,
    "recall": 0.8172222222222223
  },
  {
    "accuracy": 0.77,
    "f_measure": 0.8159133315763016,
    "precision": 0.8174754717495807,
    "recall": 0.8161111111111111
  },
  {
    "accuracy": 0.82,
    "f_measure": 0.8226353934130455,
    "precision": 0.8238175920455686,
    "recall": 0.8227777777777778
  }, # ...
]

result = pd.DataFrame.from_records(data).mean().to_dict()

这将为您提供:

{'accuracy': 0.79000000000000004,
 'f_measure': 0.8184953755563118,
 'precision': 0.82016728940624295,
 'recall': 0.81870370370370382}

谢谢。这非常干净! - Christos Baziotis
1
@ChristosBaziotis 唯一的缺点是它是一个非标准库,而且相当大 - 但如果你要做这样的事情,你会发现它拥有所有数据分析/操作/整形工具,你可能需要为此和未来的工作。 - Jon Clements

6

这里有一个使用 reduce() 的解决方案:

from functools import reduce  # Python 3 compatibility

summed = reduce(
    lambda a, b: {k: a[k] + b[k] for k in a},
    list_of_dicts,
    dict.fromkeys(list_of_dicts[0], 0.0))
result = {k: v / len(list_of_dicts) for k, v in summed.items()}

这将从第一个字典的键产生具有0.0值的起始点,然后将所有值(按键)相加到最终字典中。 然后对总和进行除法以产生平均值。

示例:

>>> from functools import reduce
>>> list_of_dicts = [
...   {
...     "accuracy": 0.78,
...     "f_measure": 0.8169374016795885,
...     "precision": 0.8192088044235794,
...     "recall": 0.8172222222222223
...   },
...   {
...     "accuracy": 0.77,
...     "f_measure": 0.8159133315763016,
...     "precision": 0.8174754717495807,
...     "recall": 0.8161111111111111
...   },
...   {
...     "accuracy": 0.82,
...     "f_measure": 0.8226353934130455,
...     "precision": 0.8238175920455686,
...     "recall": 0.8227777777777778
...   }, # ...
... ]
>>> summed = reduce(
...     lambda a, b: {k: a[k] + b[k] for k in a},
...     list_of_dicts,
...     dict.fromkeys(list_of_dicts[0], 0.0))
>>> summed
{'recall': 2.4561111111111114, 'precision': 2.4605018682187287, 'f_measure': 2.4554861266689354, 'accuracy': 2.37}
>>> {k: v / len(list_of_dicts) for k, v in summed.items()}
{'recall': 0.8187037037037038, 'precision': 0.820167289406243, 'f_measure': 0.8184953755563118, 'accuracy': 0.79}
>>> from pprint import pprint
>>> pprint(_)
{'accuracy': 0.79,
 'f_measure': 0.8184953755563118,
 'precision': 0.820167289406243,
 'recall': 0.8187037037037038}

2
您可以使用Counter来优雅地进行求和:
from itertools import Counter

summed = sum((Counter(d) for d in folds), Counter())
averaged = {k: v/len(folds) for k, v in summed.items()}

如果你真的想这样做,它甚至可以被转换成一行代码。
averaged = {
    k: v/len(folds)
    for k, v in sum((Counter(d) for d in folds), Counter()).items()
}

无论如何,我认为这两种方法都比复杂的reduce()更易读;sum()本身就是一个恰当专业化的版本。
甚至还有一种更简单的一行代码,不需要任何导入:
averaged = {
    k: sum(d[k] for d in folds)/len(folds)
    for k in folds[0]
}

有趣的是,它明显比pandas更快(?!),而且统计数据更容易更改。

我尝试用Python 3.5中的statistics.mean()函数替换手动计算,但速度慢了10倍以上。


1

这是一个可怕的一行代码,使用列表推导式。你最好不要使用它。

final =  dict(zip(lst[0].keys(), [n/len(lst) for n in [sum(i) for i in zip(*[tuple(x1.values()) for x1 in lst])]]))

for key, value in final.items():
    print (key, value)

#Output
recall 0.818703703704
precision 0.820167289406
f_measure 0.818495375556
accuracy 0.79

-1

这里有另一种方法,稍微详细一些:

from functools import reduce

d = [
  {
    "accuracy": 0.78,
    "f_measure": 0.8169374016795885,
    "precision": 0.8192088044235794,
    "recall": 0.8172222222222223
  },
  {
    "accuracy": 0.77,
    "f_measure": 0.8159133315763016,
    "precision": 0.8174754717495807,
    "recall": 0.8161111111111111
  },
  {
    "accuracy": 0.82,
    "f_measure": 0.8226353934130455,
    "precision": 0.8238175920455686,
    "recall": 0.8227777777777778
  }
]

key_arrays = {}
for item in d:
  for k, v in item.items():
    key_arrays.setdefault(k, []).append(v)

ave = {k: reduce(lambda x, y: x+y, v) / len(v) for k, v in key_arrays.items()}

print(ave)
# {'accuracy': 0.79, 'recall': 0.8187037037037038,
#  'f_measure': 0.8184953755563118, 'precision': 0.820167289406243}

很酷,我没想到你可以通过在字典上调用list()来获取它的键。编辑以包括您的建议,谢谢! - Alec
@MartijnPieters 太好了,感谢您提供的所有建议!您知道在字典推导中是否有使用setdefault的方法吗? - Alec
1
这里没有字典推导,它产生的键值对数量与您迭代的数量相同;在这里您正在进行聚合操作,因此生成的键比您迭代的项少。 - Martijn Pieters
@MartijnPieters 很好知道,谢谢!(我猜发表答案有时比提问更能教给你更多!) - Alec

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