Python中列表中相邻两个元素的平均值

8
我有一串由偶数个浮点数组成的列表:

[2.34, 3.45, 4.56, 1.23, 2.34, 7.89, ...]

我的任务是计算第 1 和 2 个元素、第 3 和 4 个元素、第 5 和 6 个元素等的平均值。在 Python 中有什么简单的方法可以做到这一点?

一个好的扩展应该如何看待每次n>=2而不是严格的2个值的聚合? - FlorianH
5个回答

23
data = [2.34, 3.45, 4.56, 1.23, 2.34, 7.89]
print [(a + b) / 2 for a, b in zip(data[::2], data[1::2])]

解释:

data[::2] 是元素 2.34, 4.56, 2.34

data[1::2] 是元素 3.45, 1.23, 7.89

zip 将它们组合成二元组: (2.34, 3.45), (4.56, 1.23), (2.34, 7.89)


2
很好的使用了zip和列表推导式!使计算变得简洁高效。 - Joël
太好了,我会使用range、for循环等等。... - TheEagle
如果所需的最终结果是长度为n的数据发送的[0,1],[1,2],[2,3]...,[n-1,n]的平均值怎么办?在这种情况下,列表只前进一个元素而不是跳过一个元素,但我在这个响应中遇到了索引问题(尽管它很优雅)。 - brosenheim
我明白了。[(a + b) / 2 for a, b in zip(data[:-1], data[1::])] 让我困惑的是一个需要3个地址,而另一个只需要2个地址。元组的第一个表示除了最后一个元素以外的整个列表,而第二个元组则要求从第二个元素开始的列表中的每个成员。 - brosenheim

13

如果列表长度不太长,Paul Draper的回答非常简单。如果它真的很长,您可能需要考虑另外两个选项之一。


首先,使用迭代器,您可以避免复制大型临时列表:

avgs = [(a + b) / 2 for a, b in zip(*[iter(data)]*2)]

这实际上是相同的操作,但是惰性地执行,这意味着它只需要在内存中存储一个值(好吧,三个值——a、b和平均值),而不是所有值。

  • iter(data)创建一个对数据进行惰性迭代的迭代器。
  • [iter(data)]*2创建一个包含两个指向同一迭代器的引用的列表,因此当一个迭代器推进时,另一个也会被推进。
  • 然后我们使用了和Paul已经很好地解释过的相同的zip和列表解析。 (在Python 2.x中,与3.x不同,zip不是惰性的,因此您要使用itertools.izip而不是zip。)

如果您实际上不需要结果列表,而只需要可以迭代的东西,请将外部方括号更改为括号,它就会变成生成器表达式,这意味着它给您一个迭代器而不是列表,您根本没有存储任何内容。

请注意,itertools文档提供了一个名为grouper的技巧(您还可以在第三方模块more-itertools中找到它),因此您可以只需编写grouper(data,2)而不是zip(*[iter(data)]*2),如果经常这样做,这显然更容易阅读。 如果需要更多解释,请参见How grouper works


或者,您可以使用NumPy数组代替列表:

data_array = np.array(data)

然后你只需要这样做:

avg_array = (data_array[::2] + data_array[1::2]) / 2

这不仅更简单(无需显式循环),而且速度大约快10倍,使用的内存量也只有原来的四分之一。


如果你想将此推广至任意长度的组……

对于迭代器解决方案,这很简单:

[sum(group) / size for group in zip(*[iter(data)]*size)]

对于NumPy解决方案,要稍微复杂一些。您需要动态创建一个迭代器以遍历data[::size]data[1::size]、…、data[size-1::size],就像这样:

sum(data[x::size] for x in range(size)) / size

NumPy还有其他方法可以实现这个,但只要size不太大,这种方法就可以了,并且它的优点是完全相同的技巧也适用于Paul Draper的解决方案:

[sum(group) / size for group in zip(*(data[x::size] for x in range(size)))]

1
我会倾向于将其通用化,通过去除解包并使唯一变量为组大小来实现:avgs = [sum(vals, 0.0) / size for vals in zip(*[iter(data)]*size)] 或类似的方式。 - Jon Clements
@JonClements:我链接了“Grouper的工作原理”,它应该解释了如何完成这个难点,但是,也许值得解释一下容易的部分(使用sum而不是+)……除非我认为你的评论已经说了所有必要的内容。 - abarnert
@JonClements:仔细想想,我认为解释一下是值得的,这样可以展示“简单”的解决方案实际上比两个“困难”的解决方案更难扩展到任意/动态大小。所以,感谢你提出这个问题。 - abarnert
1
@abarnet和“NumPy中有其他方法可以做到这一点”的说法,可能是这样的:np.array([1, 2, 3, 4, 5, 6]).reshape(3, 2).mean(axis=1) - Jon Clements
1
@abarnet 多么奇怪啊。我觉得你会让这样的事情发生很奇怪,一直在看它,然后想 - 好吧,最好纠正一下。然后今天早上再看它 - 确实没有任何问题。好意似乎并没有克服疲倦的眼睛。我道歉。 - Jon Clements
显示剩余2条评论

5
s= [2.34, 3.45, 4.56, 1.23, 2.34, 7.89, ...]

res= [(s[i]+s[i+1])/2 for i in range(0, len(s)-1, 2)]

2
使用NumPy查找连续两个值的平均值/平均数在时间和空间复杂度方面更为高效:
data=np.array([1,2,3,4,5,6])
k=2 #In your case
data1=np.mean(data.reshape(-1, k), axis=1)

0

只需使用索引来完成任务。

举个简单的例子,

avg = []
list1 = [2.34, 3.45, 4.56, 1.23, 2.34, 7.89]

for i in range(len(list1)):
    if(i+1 < len(list1):
        avg.append( (list1[i] + list1[i+1]) / 2.0 )

avg2 = []
avg2 = [j for j in avg[::2]] 

你需要的是avg2。这可能很容易理解。


请避免使用索引来访问序列中的数据,并注意不要将“list”用作变量名:“for elem_odd,elem_even in zip(data [:: 2],data [1 :: 2]):print((elem_odd + elem_even)/ 2.0),”更加简洁。 - Joël
1
@Joël:这取决于你所说的“更轻量级”的含义。他的版本总存储成本为3个浮点数;而你的版本则是3个列表和2*N个浮点数。如果N是6,那无所谓。但如果N是1000000,那就不算是一种改进了。 - abarnert
@abarnert 我的意思是简单明了,但你说得对,如果需要优化性能,这可能是一种改进,因为你只存储一个列表。但在我的算法中,如果我没有弄错,“elem_odd”和“elem_even”仅存储一次,即使我创建三个列表(如Paul Draper的答案)。注意:在Python3中,“zip”是惰性的,因此这只会创建两个列表。 - Joël
@Joël:我知道在Python 3中zip是惰性的,但你的代码显然是Python 2,因为它使用print作为语句。同时,你是对的,你不会创建2N个新浮点数,但你仍然会创建3N个列表插槽(在CPython术语中,只有2个额外的PyFloatObject,但是有3*N个额外的指针)。无论如何,你可以通过使用itertools.islice而不是列表切片来解决这个问题,但我认为在那时你最好还是首先按迭代器分组(就像我的答案一样)。 - abarnert

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