2009-11-24 4 views
13

Есть ли способ pythonic для создать список, содержащий текущее среднее значение некоторой функции?Среднее значение в Python

После прочтения забавную вещицу о Martians, black boxes, and the Cauchy distribution, я думал, что это было бы интересно, чтобы вычислить скользящее среднее распределение Коши сам:

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 

Я думаю, что этот подход работает, но мне интересно, если может возникнуть более элегантный подход к составлению списка running_avg, чем использование циклов и счетчиков (например, list comprehensions).

Есть некоторые смежные вопросы, но они решают более сложные задачи (небольшие размеры окна, экспоненциальное взвешивание) или не являются специфическими для Python:

+1

Мне интересно, почему это помечено перемещение среднего, так как вы на самом деле заинтересованы в увеличении (окна) обкатки в среднем, не скользящего среднего (фиксированного окна) ? Если вы хотите сделать скользящее среднее, есть хороший рецепт в http://docs.python.org/library/collections.html#deque-recipes :) –

+0

@ Джеффри: вы правы - это было в основном для помощи людям которые не знают правильной фразы. Но с полезной ссылкой, которую вы опубликовали, возможно, было бы разумно оставить там скользящий средний тэг? :) –

+0

@JeffreyHarris, не скользящие средние и бегущие в среднем то же самое? –

ответ

15

Вы можете написать генератору:

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

Или, учитывая генератор для чисел Коши и функция полезности для генератора текущей суммы, вы можете иметь опрятный выражение генератора:

# 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

Удивительный. Чтобы быть понятным, будет ли использоваться ваш первый пример: running_avg = [running_average(). Next() для i в диапазоне (10)]? –

+0

Да, вы могли бы использовать его так, или вы могли бы использовать 'itertools.islice', как во втором примере:' для avg в itertools.islice (running_average(), 10): ' – orip

+0

Аккуратное использование генераторов в этом решении, однако из-за того, сколько у вас происходит, это, кажется, ~ 2x медленнее, чем более простое решение LC, хотя это может быть просто компромиссом для того, чтобы вы могли обрабатывать генераторы, где для решения LC требуется список. –

4

У меня есть два возможных решения здесь для вас. Оба являются всего лишь средними средними функциями, которые работают с любым списком чисел. (Может быть сделано, чтобы работать с любым Iterable)

Генератор на основе:

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

Список Постижение основе (на самом деле один и тот же код, что и ранее):

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

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

Генератор -compatabile Генератор на основе:

Редактировать: Это я только что протестировал, чтобы проверить, могу ли я легко решить мое решение, совместимое с генераторами, и какова его производительность. Это то, что я придумал.

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

См. Статистику производительности ниже, ну стоит.

Эксплуатационные характеристики:

Редактировать: Я тоже решил испытать интересное применение Orip в нескольких генераторов, чтобы увидеть влияние на производительность.

Использование timeit и следующее (1.000.000 итераций 3 раза):

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

Я получаю следующие результаты:

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 

Смотреть комментарии для кода:

Orip's genEx based: 4.31488609314, 4.29926609993, 4.30518198013 

Результаты в секундах и показать LC новый генератор-совместимый ge nerator, чтобы быть последовательно быстрее, ваши результаты могут отличаться. Я ожидаю, что огромная разница между моим оригинальным генератором и новым заключается в том, что сумма не рассчитывается «на лету».

+0

Интересно. Любая идея (с точки зрения производительности), как эти подходы сравниваются с первым примером генератора orip? –

+0

Вы повторно генерировали числа Коши каждый раз, как решение выше? Если нет, вы определяете время генерации чисел, а также среднее значение. – orip

+0

Сроки, которые я получаю: ваше решение LC (с генерированием чисел каждый раз): [16.687758600203807, 16.715932782820914, 16.738767166880578], мое первое решение для генератора: [14.070051607753044, 14.052854863427882, 14.08185000440764], мое выражение генератора: [15.121694400936235, 15.14989374874375, 15.192127309105331] – orip

6

Вы можете использовать сопрограммы. Они похожи на генераторы, но позволяют отправлять значения. Coroutines был добавлен в Python 2.5, так что это не будет работать в версиях до этого.

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

В списке понимание:

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

редактирует:

  • Используя next() функцию вместо метода it.next(). Это значит, что он также будет работать с Python 3. Функция next() также была перенесена обратно в Python 2.6+.
    В Python 2.5 вы можете либо заменить вызовы it.next(), либо определить функцию next.
    (Спасибо Адам Паркин)
+0

Вау, это довольно гибко. Я понятия не имел, что вы можете использовать выход для отправки функции _into_. –

+0

+1, полностью удивительный – orip

+0

Примечание в Python 3 следующий синтаксис немного отличается, вместо 'ravg.ne' –

Смежные вопросы