Вычисление скользящего среднего для значений в словаре с ключами в определенном диапазоне - PullRequest
1 голос
/ 07 декабря 2011

Пока это мое решение.Интересно, есть ли какой-нибудь более элегантный / эффективный способ?

import datetime as dt

example = {dt.datetime(2008, 1, 1) : 5, dt.datetime(2008, 1, 2) : 6, dt.datetime(2008, 1, 3) : 7, dt.datetime(2008, 1, 4) : 9, dt.datetime(2008, 1, 5) : 12, 
dt.datetime(2008, 1, 6) : 15, dt.datetime(2008, 1, 7) : 20, dt.datetime(2008, 1, 8) :     22, dt.datetime(2008, 1, 9) : 25, dt.datetime(2008, 1, 10) : 35} 

def calculateMovingAverage(prices, period):
    #calculates the moving average between each datapoint and two days before (usually 3! datapoints     included)
    average_dict = {}
    for price in prices:
        pricepoints = [prices[x] for x in prices.keys() if price - dt.timedelta(period) <= x <= price]
        average = reduce(lambda x, y: x + y, pricepoints) / len(pricepoints)
        average_dict[price] = average
    return average_dict

print calculateMovingAverage(example, 2)

Я не уверен, стоит ли мне использовать здесь списочное понимание.но я не нашел его.

Ответы [ 2 ]

2 голосов
/ 07 декабря 2011

Если вы ищете другие интересные способы решения проблемы, вот ответ, используя itertools :

import datetime as dt
from collections import deque
from itertools import tee, islice, izip

def dayiter(start, end):
    one = dt.timedelta(days=1)
    day = start
    while day <= end:
        yield day
        day += one

def moving_average(mapping, window, dft=0):
    n = float(window)
    t1, t2 = tee(dayiter(min(mapping), max(mapping)))
    s = sum(mapping.get(day, dft) for day in islice(t2, window))
    yield s / n
    for olddate, newdate in izip(t1, t2):
        oldvalue = mapping.get(olddate, dft)
        newvalue = mapping.get(newdate, dft)
        s += newvalue - oldvalue
        yield s / n

example = {dt.datetime(2008, 1, 1) : 5, dt.datetime(2008, 1, 2) : 6, dt.datetime(2008, 1, 3) : 7, dt.datetime(2008, 1, 4) : 9, dt.datetime(2008, 1, 5) : 12,
dt.datetime(2008, 1, 6) : 15, dt.datetime(2008, 1, 7) : 20, dt.datetime(2008, 1, 8) :     22, dt.datetime(2008, 1, 9) : 25, dt.datetime(2008, 1, 10) : 35}

for ma in moving_average(example, window=3):
    print ma

Идеи включают в себя:

  • Используйте простой генератор, чтобы сделать итератор даты, который циклически повторяется в течение последовательных дней от самого низкого до самого высокого.

  • Используйте itertools.tee длясоздайте пару итераторов для самых старых данных и самых новых данных (передняя часть окна данных и задняя часть).

  • Храните промежуточную сумму в переменной s .На каждой итерации обновляйте s , вычитая самое старое значение и добавляя самое новое значение.

  • Это решение экономит пространство (оно сохраняет не более окна значений в памяти), и это экономит время, одно сложение и одно вычитание для каждого дня независимо от размера окна.

  • Обработка пропущенных дней по умолчанию на ноль.Существуют и другие стратегии, которые можно использовать для пропущенных дней (например, использование текущей скользящей средней по умолчанию или настройка n вверх и вниз, чтобы отразить количество фактических точек данных в окне).

1 голос
/ 07 декабря 2011

Проблема использования списочного понимания в этом случае заключается в том, что неэффективно выполнять поиск по всему набору цен на каждой итерации цикла.Понимание списка в вашем коде проверяет каждый элемент prices.keys() на каждой итерации цикла for price in prices:.

Что вы действительно хотите сделать, так это воспользоваться тем, что даты являются последовательными, и обработать их впорядок.Таким образом, когда вы исключаете дату из рассмотрения на текущей итерации цикла, вы можете исключить ее из рассмотрения на всех последующих итерациях цикла.

Вот пример:

def calculateMovingAverage(prices, period):
    dates = list(prices.keys())
    dates.sort()
    total = 0.0
    count = 0
    average_dict = {}

    for i, d in enumerate(dates):
        # search through prior dates and eliminate any that are too old
        old = [e for e in dates[i-count:i] if (d-e).days > period]
        total -= sum(prices[o] for o in old)
        count -= len(old)

        # add in the current date
        total += prices[d]
        count += 1

        average_dict[d] = total / count

    return average_dict

Вместо того, чтобы проверять каждый элемент prices.keys() на каждой итерации цикла, этот код выполняет поиск назад от текущей даты до списка дат, включенных в total.Когда он находит слишком старую дату, он удаляет ее из total, и, поскольку мы обрабатываем даты по порядку, ему больше не нужно просматривать эту дату снова.

...