2011-02-10 15 views
13

Sto facendo un lavoro di statistica, ho una (grande) collezione di numeri casuali per calcolare la media di, mi piacerebbe lavorare con i generatori, perché ho solo bisogno di calcolare il significa, quindi non ho bisogno di memorizzare i numeri.media di calcolo in python per un generatore

Il problema è che numpy.mean si interrompe se si passa un generatore. Posso scrivere una semplice funzione per fare ciò che voglio, ma mi chiedo se c'è un modo corretto e integrato per farlo?

Sarebbe bello se potessi dire "somma (valori)/len (valori)", ma len non funziona per i genetator e somma i valori già consumati.

Ecco un esempio:

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) 

questi sia dare la stessa, giusta, risposta, acquistare my_mean non funziona per le liste, e numpy.mean non funziona per i generatori.

Mi piace molto l'idea di lavorare con i generatori, ma dettagli come questo sembrano rovinare le cose.

+2

sapresti quanti numeri casuali il generatore produrrebbe, non è vero? –

+0

@Sven Marnach: supponiamo che il generatore stia leggendo da un file? – Jimmy

+1

Se si vuole davvero non archiviare i dati (e non implementare la propria funzione 'sum' più lenta) è possibile creare un generatore di conteggio e chiamarlo in questo modo:' co = countingGen(); mean = sum (co (data))/co.getCount() ' –

risposta

5

Solo una semplice modifica al codice consente di utilizzare entrambi. I generatori dovevano essere usati in modo intercambiabile per gli elenchi in un ciclo continuo.

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

Le lettere maiuscole come in Sum sono solitamente riservate per le classi. – xApple

+0

@xApple, ho provato a renderlo simile al codice nella domanda; vedrai che la variabile è chiamata anche 'Sum'. Personalmente avrei seguito la convenzione in PEP 8. –

+2

e 'sum' è un built-in, quindi dovresti usare' sum_' o 'total' –

-1

Prova:

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 sarà duplicare il vostro iteratore per qualsiasi iterabile i (ad esempio, un generatore, un elenco, ecc), che consente di utilizzare un duplicato per sommare e l'altro per il conteggio.

(nota che "tee" utilizzerà comunque la memoria intermedia).

+2

Memorizza temporaneamente l'intera lista. Dal punto di vista della memoria, equivale a convertire prima in una lista e usare 'sum (a)/len (a)', ma usare una lista sarebbe più veloce. –

+0

Buon punto, vero - Stavo solo guardando come viene implementato tee(). Lo odio quando ciò accade. :-) – payne

+0

Si potrebbe pensare che 'tee' possa essere implementato solo memorizzando il" diff "tra gli iteratori clonati, cioè gli elementi che uno ha consumato ma l'altro non ancora. –

1

Un modo potrebbe essere

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

ma questo in realtà memorizza temporaneamente i numeri.

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

Quanto sopra è molto simile al codice, ad eccezione utilizzando for per iterare values sei bravo, non importa se si ottiene una lista o un iteratore. Il metodo python sum è comunque molto ottimizzato, quindi a meno che la lista non sia davvero, davvero lunga, potresti essere più felice di memorizzare temporaneamente i dati.

(anche notare che, dal momento che si sta utilizzando python3, non è necessario float(sum)/n)

+1

Facendo 'sum = 0' state mascherando le funzioni built-in. – xApple

1

Il tuo approccio è una buona, ma si dovrebbe invece utilizzare il linguaggio for x in y invece di chiamare più volte next fino ad ottenere un StopIteration . Funziona sia per gli elenchi che per i generatori:

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

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

Le lettere maiuscole come in "Sum" sono solitamente riservate alle classi. – xApple

3

Il vecchio modo per farlo:

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) 

C'è statistics.mean() in Python 3.4 ma 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 

dove _sum() restituisce una somma precisa (math.fsum() -come funzione che oltre al float supporta anche s Fraction, Decimal).

+0

Più uno per' enumerare' - quello è il modo più pazzesco, IMO. – Tgsmith61591

0

Se si conosce la lunghezza del generatore in anticipo e si vuole evitare di memorizzare l'elenco completo in memoria, è possibile utilizzare:

reduce(np.add, generator)/length 
14

In generale, se si sta facendo uno streaming media calcolo del flottante numero di punti, probabilmente stai meglio usando un algoritmo più numericamente stabile che sommando semplicemente il generatore e dividendolo per la lunghezza.

Il più semplice di questi (che io conosca) è in genere credited to Knuth e calcola anche la varianza. Il collegamento contiene un'implementazione python, ma solo la porzione media viene copiata qui per completezza.

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 

So che questa domanda è super vecchio, ma è ancora il primo colpo su google, quindi è sembrato opportuno inserire. Sono ancora triste che la libreria standard python non contenga questo semplice pezzo di codice.

0

È possibile utilizzare ridurre senza conoscere la dimensione dell'array:

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