2011-02-10 17 views
13

Estoy haciendo un trabajo de estadísticas, tengo una (gran) colección de números aleatorios para calcular la media de, me gustaría trabajar con generadores, porque solo necesito calcular el significa, así que no necesito almacenar los números.medio de cálculo en Python para un generador

El problema es que numpy.mean se rompe si le pasa un generador. Puedo escribir una función simple para hacer lo que quiero, pero me pregunto si hay una forma adecuada e integrada para hacer esto.

Sería bueno si pudiera decir "sum (values)/len (values)", pero len no funciona para genetators, y suma los valores ya consumidos.

He aquí un ejemplo:

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) 

éstos ambos dan la misma, correcta, la respuesta, comprar my_mean no funciona para las listas, y numpy.mean no funciona para los generadores.

Me gusta mucho la idea de trabajar con generadores, pero detalles como este parecen echar a perder las cosas.

+2

sabrías la cantidad de números al azar produciría su generador, ¿verdad? –

+0

@Sven Marnach: supongamos que el generador está leyendo de un archivo? – Jimmy

+1

Si realmente no desea almacenar los datos (y no implementar su propia función 'sum' más lenta), podría crear un generador de conteo y llamarlo así:' co = countingGen(); mean = sum (co (data))/co.getCount() ' –

Respuesta

5

Solo un cambio simple en su código le permitirá usar ambos. Los generadores estaban destinados a ser utilizados indistintamente para las listas en un for-loop.

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

Las mayúsculas, como en Suma, generalmente se reservan para las clases. – xApple

+0

@xApple, traté de hacer esto similar al código en la pregunta; verás que la variable se llama 'Suma' allí también. Personalmente, habría seguido la convención en PEP 8. –

+2

y 'sum' es una función incorporada, por lo que debes usar' sum_' o 'total' –

-1

Probar:

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 duplicará su iterador para cualquier iterable i (por ejemplo, un generador, una lista, etc.), lo que permite utilizar un duplicado para sumar y el otro para el conteo.

(Tenga en cuenta que 'tee' seguirá utilizando almacenamiento intermedio).

+2

Esto almacena temporalmente toda la lista. En cuanto a la memoria, es equivalente a la conversión a una lista primero y al uso de 'suma (a)/len (a)', pero usar una lista sería más rápido. –

+0

Buen punto, es cierto, solo estaba viendo cómo se implementa tee(). Odio cuando eso ocurre. :-) – payne

+0

Se podría pensar que 'tee' podría implementarse almacenando solo el" diff "entre los iteradores clonados, es decir, los elementos que uno ha consumido pero el otro todavía no. –

1

Una forma sería

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

pero esto en realidad almacena temporalmente los números.

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

Lo anterior es muy similar a su código, excepto mediante el uso de for iterar values que son buenos, no importa si se obtiene una lista o un iterador. El método de python sum está, sin embargo, muy optimizado, así que a menos que la lista sea muy, muy larga, es posible que esté más feliz almacenando temporalmente los datos.

(Observe también que dado que está utilizando python3, no es necesario float(sum)/n)

+1

Al hacer 'suma = 0' estás enmascarando funciones incorporadas. – xApple

1

Su enfoque es buena, pero en su lugar debe usar el lenguaje for x in y en lugar de llamar repetidamente next hasta obtener una StopIteration . Esto funciona tanto para listas como para generadores:

def my_mean(values): 
    n = 0 
    Sum = 0.0 

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

Las mayúsculas como en 'Suma' generalmente se reservan para las clases. – xApple

3

La manera tradicional de hacerlo:

def my_mean(values): 
    sum, n = 0, 0 
    for x in values: 
     sum += x 
     n += 1 
    return float(sum)/n 
4
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) 

Hay statistics.mean() in Python 3.4 pero it calls list() on the input:

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 

donde _sum() devuelve una suma exacta (math.fsum() -como función que además de float también es compatible s Fraction, Decimal).

+0

más uno para el' enumerate' - esa es la manera más pitónica, IMO. – Tgsmith61591

0

Si conoce la longitud del generador de antemano y se quiere evitar el almacenamiento de la lista completa en la memoria, puede utilizar:

reduce(np.add, generator)/length 
14

En general, si está haciendo un streaming de cálculo del promedio de flotar números de puntos, probablemente sea mejor utilizar un algoritmo numéricamente más estable que simplemente sumar el generador y dividir por la longitud.

El más simple de estos (que yo sé) es generalmente credited to Knuth, y también calcula la varianza. El enlace contiene una implementación de Python, pero solo la parte media se copia aquí para completarla.

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 

Sé que esta pregunta es muy antigua, pero sigue siendo el primer golpe en Google, por lo que parecía apropiado para publicar. Todavía estoy triste de que la biblioteca estándar de Python no contenga esta simple pieza de código.

0

Se puede utilizar reducir sin conocer el tamaño de la matriz:

from itertools import izip, count 
reduce(lambda c,i: (c*(i[1]-1) + float(i[0]))/i[1], izip(values,count(1)),0) 
Cuestiones relacionadas