在Python中为生成器计算均值

18
我正在进行一些统计工作,我有一个(大量的)随机数集合需要计算平均值,我想使用生成器进行操作,因为我只需要计算平均值,所以不需要存储这些数字。
问题是numpy.mean如果传递一个生成器会出错。我可以编写一个简单的函数来实现我的目标,但我想知道是否有适当的内置方法来完成这个任务?
如果能够这样说就太好了:"sum(values)/len(values)",但是len不能用于生成器,而且sum已经消耗掉了values。
以下是一个示例:
import numpy 

def my_mean(values):
    n = 0
    Sum = 0.0
    try:
        while True:
            Sum += next(values)
            n += 1
    except StopIteration: pass
    return float(Sum)/n

X = [k for k in range(1,7)]
Y = (k for k in range(1,7))

print numpy.mean(X)
print my_mean(Y)

这两个函数的输出结果相同且正确,但是my_mean函数不能用于列表,而numpy.mean函数不能用于生成器。

我真的很喜欢使用生成器来编程,但是像这样的细节似乎破坏了一切。


2
你应该知道你的生成器会产生多少个随机数,不是吗? - Sven Marnach
假设生成器正在从文件中读取?@Sven Marnach - Jimmy
2
如果你真的不想存储数据(也不想实现自己的较慢的sum函数),你可以创建一个计数生成器,并像这样调用它:co = countingGen(); mean = sum(co(data))/co.getCount() - Thomas Ahle
10个回答

27

一般来说,如果你需要对浮点数进行流式均值计算,使用比简单地将生成器求和并除以长度更稳定的算法可能会更好。

其中最简单的(我所知道的)通常是归功于Knuth,还可以计算方差。链接中包含了Python实现,但这里为了完整性只复制了平均部分。

def mean(data):
    n = 0
    mean = 0.0
 
    for x in data:
        n += 1
        mean += (x - mean)/n

    if n < 1:
        return float('nan')
    else:
        return mean

我知道这个问题非常老旧,但它仍然是谷歌搜索结果的第一条,因此发布这篇文章似乎是恰当的。我仍然很难过Python标准库中没有包含这个简单的代码片段。


8

只需要对您的代码进行一个简单的更改,就可以同时使用两者。生成器旨在可以在for循环中与列表互换使用。

def my_mean(values):
    n = 0
    Sum = 0.0
    for v in values:
        Sum += v
        n += 1
    return Sum / n

4
像“Sum”这样的大写字母通常被保留给类。 - xApple
2
@xApple,我尝试让这段代码与问题中的代码类似;你会发现变量也被命名为Sum。个人而言,我会遵循PEP 8中的约定。 - Mark Ransom
4
sum是一个内置函数,因此您应该使用sum_total - Aaron McMillin

8
def my_mean(values):
    total = 0
    for n, v in enumerate(values, 1):
        total += v
    return total / n

print my_mean(X)
print my_mean(Y)

Python 3.4中有一个statistics.mean()函数,但它在处理输入时会调用list()函数:

def mean(data):
    if iter(data) is data:
        data = list(data)
    n = len(data)
    if n < 1:
        raise StatisticsError('mean requires at least one data point')
    return _sum(data)/n

其中_sum()函数返回一个准确的和(类似于math.fsum()函数,除了支持float类型外,还支持FractionDecimal类型)。


3

传统的做法:

def my_mean(values):
   sum, n = 0, 0
   for x in values:
      sum += x
      n += 1
   return float(sum)/n

1

一种方法是

numpy.fromiter(Y, int).mean()

但是这实际上只是暂时存储了这些数字。


1
你的方法是不错的,但是你应该使用 for x in y 的习惯用法,而不是重复调用 next 直到出现 StopIteration。这适用于列表和生成器:
def my_mean(values):
    n = 0
    Sum = 0.0

    for value in values:
        Sum += value
        n += 1
    return float(Sum)/n

1
通常情况下,类名才使用大写字母,例如 Sum - xApple

1
你可以在不知道数组大小的情况下使用reduce:
from itertools import izip, count
reduce(lambda c,i: (c*(i[1]-1) + float(i[0]))/i[1], izip(values,count(1)),0)

0
def my_mean(values):
    n = 0
    sum = 0
    for v in values:
        sum += v
        n += 1
    return sum/n

上面的代码与你的代码非常相似,只是使用 for 来迭代 values,无论你得到列表还是迭代器都可以。然而,Python 的 sum 方法非常优化,所以除非列表真的非常长,否则你可能更喜欢暂时存储数据。

(另外请注意,由于你正在使用 Python3,你不需要 float(sum)/n)


1
通过执行 sum = 0,你掩盖了内置函数。 - xApple

0
如果您事先知道生成器的长度并且想要避免在内存中存储完整列表,您可以使用以下方法:
reduce(np.add, generator)/length

-1

尝试:

import itertools

def mean(i):
    (i1, i2) = itertools.tee(i, 2)
    return sum(i1) / sum(1 for _ in i2)

print mean([1,2,3,4,5])

tee 会为任何可迭代对象 i(例如生成器、列表等)复制一个迭代器,使您可以使用一个副本进行求和,另一个副本进行计数。

(请注意,“tee”仍将使用中间存储)。


2
这将临时存储整个列表。从内存角度来看,它相当于先转换为列表,然后使用sum(a)/len(a),但使用列表会更快。 - Sven Marnach
好的观点,没错 - 我只是在看tee()函数的实现。当这种情况发生时,我很讨厌。 :-) - payne
你可能会认为tee可以通过仅存储克隆迭代器之间的“差异”来实现,即一个已经消耗但另一个尚未消耗的元素。 - Ryan C. Thompson

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