如何高效地在Python中获取两个列表的列表中元素的平均值

51

我有两个如下列表。

mylist1 = [["lemon", 0.1], ["egg", 0.1], ["muffin", 0.3], ["chocolate", 0.5]]
mylist2 = [["chocolate", 0.5], ["milk", 0.2], ["carrot", 0.8], ["egg", 0.8]]

我想获取两个列表中共同元素的平均值,操作步骤如下。

myoutput = [["chocolate", 0.5], ["egg", 0.45]]

我目前的代码如下

for item1 in mylist1:
    for item2 in mylist2:
        if item1[0] == item2[0]:
             print(np.mean([item1[1], item2[1]]))

然而,由于有两个for循环(O(n^2)的复杂度),这对于非常长的列表来说非常低效。我想知道在Python中是否有更标准/高效的方法。


5
将这些项转换为字典将成为可读性好且符合 Python 风格的解决方案。 - moo
也许你可以获取每个列表的平均值,然后执行类似于 mean1/len(mylist1) + mean2/len(mylist2) 的操作,这将为您获取合并列表的平均值。 - dodekja
2
每个键对于每个列表都是唯一的这一点是确定的吗?我认为许多答案都做出了这种假设。这可能是一个有效的假设,但是我想确认一下,以防这只是虚拟数据,而“真实”数据可能会有重复项(我不完全清楚这些键值对代表什么)。 - Roberto
dict() 后的代码为:(mydict1[i] + mydict2[i])/2,其中 i 为 mydict1 和 mydict2 的交集。 - benxyzzy
谢谢您的建议 :) - EmJ
9个回答

37

您可以通过将列表1转换为字典,然后在第二个列表的每个项目中使用该字典(O(1)时间复杂度)来使整个操作时间复杂度为O(n),具体方法如下:

mylist1 = [["lemon", 0.1], ["egg", 0.1], ["muffin", 0.3], ["chocolate", 0.5]]
mylist2 = [["chocolate", 0.5], ["milk", 0.2], ["carrot", 0.8], ["egg", 0.8]]

l1_as_dict = dict(mylist1)

myoutput = []
for item,price2 in mylist2:
    if item in l1_as_dict:
        price1 = l1_as_dict[item]
        myoutput.append([item, (price1+price2)/2])

print(myoutput)

输出结果:

[['chocolate', 0.5], ['egg', 0.45]]

17

一个O(n)解决方案,它将平均所有项目。
构建一个字典来存储值的列表,然后在之后对该字典进行求平均:

In []:
d = {}
for lst in (mylist1, mylist2):
    for i, v in lst:
        d.setdefault(i, []).append(v)   # alternative use collections.defaultdict

[(k, sum(v)/len(v)) for k, v in d.items()]

Out[]:
[('lemon', 0.1), ('egg', 0.45), ('muffin', 0.3), ('chocolate', 0.5), ('milk', 0.2), ('carrot', 0.8)]

如果你只需要常见的,那么你可以添加一个防护条件:

In []:
[(k, sum(v)/len(v)) for k, v in d.items() if len(v) > 1]

Out[]:
[('egg', 0.45), ('chocolate', 0.5)]

这适用于任意数量的列表,并且不对共同元素的数量做任何假设。


9
这里有一个解决方案,它使用 collections.defaultdict 来对项目进行分组,并使用 statistics.mean 计算平均值:
from collections import defaultdict
from statistics import mean

mylist1 = [["lemon", 0.1], ["egg", 0.1], ["muffin", 0.3], ["chocolate", 0.5]]
mylist2 = [["chocolate", 0.5], ["milk", 0.2], ["carrot", 0.8], ["egg", 0.8]]

d = defaultdict(list)
for lst in (mylist1, mylist2):
    for k, v in lst:
        d[k].append(v)

result = [[k, mean(v)] for k, v in d.items()]

print(result)
# [['lemon', 0.1], ['egg', 0.45], ['muffin', 0.3], ['chocolate', 0.5], ['milk', 0.2], ['carrot', 0.8]]

如果我们只想要常见的键,只需检查值是否大于1即可:
result = [[k, mean(v)] for k, v in d.items() if len(v) > 1]

print(result)
# [['egg', 0.45], ['chocolate', 0.5]]

我们也可以通过集合交集来构建结果:
mylist1 = [["lemon", 0.1], ["egg", 0.1], ["muffin", 0.3], ["chocolate", 0.5]]
mylist2 = [["chocolate", 0.5], ["milk", 0.2], ["carrot", 0.8], ["egg", 0.8]]

d1, d2 = dict(mylist1), dict(mylist2)

result = [[k, (d1[k] + d2[k]) / 2] for k in d1.keys() & d2.keys()]

print(result)
# [['egg', 0.45], ['chocolate', 0.5]]

8
你可以使用 Pandas 库来避免自己编写任何类型的循环。
你的代码将非常简洁和清晰。
安装 Pandas: pip install pandas
然后尝试这个:
In [132]: import pandas as pd

In [109]: df1 = pd.DataFrame(mylist1)

In [110]: df2 = pd.DataFrame(mylist2)

In [117]: res = pd.merge(df1, df2, on=0)

In [121]: res['mean'] = res.mean(axis=1)

In [125]: res.drop(['1_x', '1_y'], 1, inplace=True)

In [131]: res.values.tolist()
Out[131]: [['egg', 0.45], ['chocolate', 0.5]]

编辑

Pandas之所以非常快,是因为它在幕后使用numpy。Numpy实现了高效的数组操作。

请查看文章:Pandas为什么这么快? 以获取有关通过纯Python vs Pandas计算mean的更多详细信息。


7
为了更轻松地操作你的值,我建议使用一个dict,找到共同的键,并计算平均值:
mylist1 = [["lemon", 0.1], ["egg", 0.1], ["muffin", 0.3], ["chocolate", 0.5]]
mylist2 = [["chocolate", 0.5], ["milk", 0.2], ["carrot", 0.8], ["egg", 0.8]]

recipe_1 = dict(mylist1)  # {'lemon': 0.1, 'egg': 0.1, 'muffin': 0.3, 'chocolate': 0.5}
recipe_2 = dict(mylist2)  # {'chocolate': 0.5, 'milk': 0.2, 'carrot': 0.8, 'egg': 0.8}

common_keys = recipe_1.keys() & recipe_2.keys()  # {'chocolate', 'egg'}

myoutput = [[item, np.mean((recipe_1[item], recipe_2[item]))] for item in common_keys]
myoutput = [[item, (recipe_1[item] + recipe_2[item]) / 2] for item in common_keys]

6

将列表转换为字典

d_list1 = dict(mylist1)
d_list2 = dict(mylist2)

[[k, (v+d_list2[k])/2] for k, v in d_list1.items() if k in d_list2]
#[['egg', 0.45], ['chocolate', 0.5]]

6
使用setintersection方法从两个列表中获取共同的键,然后使用列表推导式计算平均值:
mylist1 = [["lemon", 0.1], ["egg", 0.1], ["muffin", 0.3], ["chocolate", 0.5]]
mylist2 = [["chocolate", 0.5], ["milk", 0.2], ["carrot", 0.8], ["egg", 0.8]]

dict1 = dict(mylist1)
dict2 = dict(mylist2)
res = [[key, (dict1.get(key)+dict2.get(key))/2] for key in set(dict1.keys()).intersection(set(dict2.keys()))]
print(res)

输出:

>> [['chocolate', 0.5], ['egg', 0.45]]

5
你可以在计算集合交集所需的时间内完成它,这个时间显然是O(min(N1,N2)),其中N1和N2是列表长度。
intersect = set([a[0] for a in mylist1]).intersection([a[0] for a in mylist2])
d1=dict(mylist1)
d2=dict(mylist2)
{i:(d1[i]+d2[i])/2 for i in intersect}

1
我会小心地描述复杂性。你有两个列表推导和三个字典构造。集合交集只是你的操作之一。话虽如此,它仍然只是O(n) - AChampion
“Comuting”不是英语单词。您是指“计算”还是“通勤”(“2.(不及物动词,数学)关于操作,是可交换的,即具有改变操作数顺序不改变结果的属性。”)? - Peter Mortensen
我认为交换集合的交集不会有任何影响,因为交集操作已经考虑了较短长度列表,并且不会执行多余的操作,所以无论是a.intersection.b还是b.intersection.a都没有区别。然而,这个答案确实将“时间服务”从O(n1*n2)减少到了O(min(n1,n2)),所以在上班路上要考虑这一点。 - jeremy_rutman

2
这里有一个简单的、非常Pythonic的解决方案: result = [[x[0], (x[1] + y[1])/2] for x in mylist1 for y in mylist2 if x[0] == y[0]] 它可能不是最快的解决方案,但它更快是因为使用了Python列表推导来迭代列表,并且由于此解决方案和OP的解决方案都无法处理列表键值的多个实例,它用一个简单的平均值替换np.mean。

这仍然是O(n^2)。 - Solomon Ucko

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