Question

Y at-il un moyen pythonique construire une liste qui contient une moyenne mobile d'une fonction?

Après avoir lu un petit morceau amusant sur le , boîtes noires, et la distribution de Cauchy , Je pensais que ce serait amusant de calculer une moyenne mobile de moi-même la distribution de Cauchy:

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

Je pense que cette approche fonctionne, mais je suis curieux de savoir s'il pourrait y avoir une approche plus élégante pour construire cette liste de running_avg que d'utiliser des boucles et des compteurs (par exemple liste compréhensions).

Il y a quelques questions connexes, mais ils abordent des problèmes plus complexes (petite taille de la fenêtre, pondération exponentielle) ou ne sont pas spécifiques à Python:

Était-ce utile?

La solution

Vous pouvez écrire un générateur:

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

Ou, étant donné un générateur de nombres Cauchy et une fonction d'utilité pour un générateur de somme en cours d'exécution, vous pouvez avoir une expression de générateur propre:

# 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

Autres conseils

Vous pouvez utiliser coroutines. Ils sont semblables à des générateurs, mais vous permet d'envoyer des valeurs. Coroutines a été ajouté en Python 2.5, cela ne fonctionnera pas dans les versions avant.

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,)

En compréhension de la liste:

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

Edits:

  • Utilisation de la fonction next() au lieu de la méthode it.next(). Il en est ainsi, il travaillera également avec Python 3. La fonction next() a également été de retour à Python porté 2.6+.
    En Python 2.5, vous pouvez remplacer les appels avec it.next() ou définir une fonction next vous.
    (Merci Adam Parkin)

J'ai deux solutions possibles ici pour vous. Les deux ne sont que des fonctions de moyenne de fonctionnement génériques qui fonctionnent sur une liste de numéros. (Pourrait être fait pour fonctionner avec tout itérable)

Générateur à base:

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))

Liste basée Comprehension (vraiment le même code que le plus tôt):

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

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

Générateur Générateur compatabile basé:

Modifier : Ce que je viens de tester pour voir si je pouvais faire ma solution compatible avec les générateurs et facilement ce que ses performances serait. C'est ce que je suis venu avec.

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

Voir les statistiques de performance ci-dessous, valent la peine.

Caractéristiques de performance:

Modifier :. J'ai aussi décidé de tester l'utilisation intéressante de OriP de plusieurs générateurs pour voir l'impact sur la performance

Utilisation timeit et les suivantes (1.000.000 itérations 3 fois):

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())

Je reçois les résultats suivants:

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

Voir les commentaires pour le code:

Orip's genEx based: 4.31488609314, 4.29926609993, 4.30518198013 

Les résultats sont en quelques secondes, et afficher la page LC nouveau générateur compatible avec la méthode de générateur pour être toujours plus rapide, vos résultats peuvent varier cependant. Je pense que le énorme différence entre mon générateur d'origine et le nouveau est le fait que la somme ne soit pas calculée à la volée.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top