2009-11-24 11 views
13

Esiste un modo pietoso per creare un elenco che contiene una media corrente di qualche funzione?Media corrente in Python

Dopo aver letto un piccolo pezzo divertente di Martians, black boxes, and the Cauchy distribution, ho pensato che sarebbe stato divertente per calcolare una media in esecuzione della distribuzione di Cauchy me stesso:

import math 
import random 

def cauchy(location, scale): 
    p = 0.0 
    while p == 0.0: 
     p = random.random() 
    return location + scale*math.tan(math.pi*(p - 0.5)) 

# is this next block of code a good way to populate running_avg? 
sum = 0 
count = 0 
max = 10 
running_avg = [] 
while count < max: 
    num = cauchy(3,1) 
    sum += num 
    count += 1 
    running_avg.append(sum/count) 

print running_avg  # or do something else with it, besides printing 

Penso che questo approccio funziona, ma sono curioso di sapere se potrebbe esserci un approccio più elegante alla compilazione dell'elenco running_avg rispetto all'utilizzo di loop e contatori (ad esempio list comprehensions).

ci sono alcune domande correlate, ma affrontare i problemi più complicati (piccola dimensione della finestra, ponderazione esponenziale) o non sono specifiche di Python:

+1

Sono curioso perché questo è etichettato in movimento-media, dal momento che siete realmente interessati in un (crescente finestra) rodaggio media, non un (-finestra fissa) media mobile ? Se vuoi fare una media mobile c'è una bella ricetta in http://docs.python.org/library/collections.html#deque-recipes :) –

+0

@Jeffrey: hai ragione - è stato soprattutto per aiutare le persone chi non conosce la frase giusta. Ma con il link utile che hai pubblicato, forse avrebbe senso lasciare il tag media mobile lì? :) –

+0

@JeffreyHarris, non si muovono in media e la media è la stessa cosa? –

risposta

15

È possibile scrivere un generatore:

def running_average(): 
    sum = 0 
    count = 0 
    while True: 
    sum += cauchy(3,1) 
    count += 1 
    yield sum/count 

Oppure, dato un generatore per i numeri di Cauchy e di una funzione di utilità per un generatore di somma parziale, si può avere un generatore di espressione ordinata:

# Cauchy numbers generator 
def cauchy_numbers(): 
    while True: 
    yield cauchy(3,1) 

# running sum utility function 
def running_sum(iterable): 
    sum = 0 
    for x in iterable: 
    sum += x 
    yield sum 

# Running averages generator expression (** the neat part **) 
running_avgs = (sum/(i+1) for (i,sum) in enumerate(running_sum(cauchy_numbers()))) 

# goes on forever 
for avg in running_avgs: 
    print avg 

# alternatively, take just the first 10 
import itertools 
for avg in itertools.islice(running_avgs, 10): 
    print avg 
+0

Fantastico. Per essere chiari, il tuo primo esempio dovrebbe essere usato come: running_avg = [running_average(). Next() per i in range (10)]? –

+0

Sì, potresti usarlo in questo modo, o potresti usare 'itertools.islice' come nel secondo esempio:' per avg in itertools.islice (running_average(), 10): ' – orip

+0

Uso pulito dei generatori in questa soluzione, tuttavia, a causa di quanti ne stai andando, sembra essere ~ 2 volte più lento di una soluzione LC più semplice, anche se potrebbe essere solo un compromesso per il tuo essere in grado di gestire generatori dove la soluzione LC richiede un elenco. –

4

Ho due soluzioni possibili qui per voi. Entrambe sono solo funzioni generiche che funzionano su qualsiasi elenco di numeri. (Potrebbe essere fatto per funzionare con qualsiasi iterabile)

Generator basa: basata

nums = [cauchy(3,1) for x in xrange(10)] 

def running_avg(numbers): 
    for count in xrange(1, len(nums)+1): 
     yield sum(numbers[:count])/count 

print list(running_avg(nums)) 

di lista (in realtà lo stesso codice come il precedente):

nums = [cauchy(3,1) for x in xrange(10)] 

print [sum(nums[:count])/count for count in xrange(1, len(nums)+1)] 

Generator -compatabile basato su generatore:

Modifica: questo ho appena testato per vedere se potevo rendere la mia soluzione compatibile con i generatori facilmente e quale sarebbe la sua prestazione. Questo è quello che mi è venuto in mente.

def running_avg(numbers): 
    sum = 0 
    for count, number in enumerate(numbers): 
     sum += number 
     yield sum/(count+1) 

Vedere le statistiche sul rendimento qui sotto, ne vale la pena.

caratteristiche prestazionali:

Modifica: ho anche deciso di testare uso interessante di Orip di più generatori di vedere l'impatto sulle prestazioni.

Utilizzando timeit e la seguente (1.000.000 iterazioni 3 volte):

print "Generator based:", ', '.join(str(x) for x in Timer('list(running_avg(nums))', 'from __main__ import nums, running_avg').repeat()) 
print "LC based:", ', '.join(str(x) for x in Timer('[sum(nums[:count])/count for count in xrange(1, len(nums)+1)]', 'from __main__ import nums').repeat()) 
print "Orip's:", ', '.join(str(x) for x in Timer('list(itertools.islice(running_avgs, 10))', 'from __main__ import itertools, running_avgs').repeat()) 

print "Generator-compatabile Generator based:", ', '.join(str(x) for x in Timer('list(running_avg(nums))', 'from __main__ import nums, running_avg').repeat()) 

ottengo i seguenti risultati:

Generator based: 17.653908968, 17.8027219772, 18.0342400074 
LC based: 14.3925321102, 14.4613749981, 14.4277560711 
Orip's: 30.8035550117, 30.3142540455, 30.5146529675 

Generator-compatabile Generator based: 3.55352187157, 3.54164409637, 3.59098005295 

vedere i commenti per il codice:

Orip's genEx based: 4.31488609314, 4.29926609993, 4.30518198013 

I risultati sono in pochi secondi e mostra il LC nuovo generatore compatibile ge il metodo nerator per essere sempre più veloce, i risultati possono variare. Mi aspetto che l'enorme differenza tra il mio generatore originale e quello nuovo sia il fatto che la somma non viene calcolata al volo.

+0

Interessante. Qualche idea (per quanto riguarda le prestazioni) di come questi approcci si confrontino con il primo esempio generatore di orip? –

+0

Hai rigenerato i numeri Cauchy ogni volta, come la soluzione sopra? In caso contrario, stai calcolando la generazione dei numeri e la media corrente. – orip

+0

I tempi che ottengo: la soluzione LC (con la generazione dei numeri ogni volta): [16.687758600203807, 16.715932782820914, 16.738767166880578], la mia prima soluzione generatore: [14.070051607753044, 14.052854863427882, 14.081863001340764], espressione del mio generatore: [15.121694400936235, 15.14989374874375, 15.192127309105331] – orip

6

È possibile utilizzare le coroutine. Sono simili ai generatori, ma ti permettono di inviare valori. Coroutine è stato aggiunto in Python 2.5, quindi non funzionerà nelle versioni precedenti.

def running_average(): 
    sum = 0.0 
    count = 0 
    value = yield(float('nan')) 
    while True: 
     sum += value 
     count += 1 
     value = yield(sum/count) 

ravg = running_average() 
next(ravg) # advance the corutine to the first yield 

for i in xrange(10): 
    avg = ravg.send(cauchy(3,1)) 
    print 'Running average: %.6f' % (avg,) 

come una lista di comprensione:

ravg = running_average() 
next(ravg) 
ravg_list = [ravg.send(cauchy(3,1)) for i in xrange(10)] 

Modifiche:

  • Usando la funzione next() invece del metodo it.next(). Funziona così anche con Python 3. Anche la funzione è stata backportata su Python 2.6+.
    In Python 2.5, è possibile sostituire le chiamate con it.next() o definire manualmente una funzione next.
    (Grazie Adam Parkin)
+0

Wow, è abbastanza flessibile. Non avevo idea che si potesse usare yield per inviare roba _in una funzione. –

+0

+1, completamente fantastico – orip

+0

Nota in Python 3 la prossima sintassi è un po 'diversa, do 'next (ravg)' piuttosto che 'ravg.next()' –

Problemi correlati