Het voortschrijdend gemiddelde berekenen voor waarden in een woordenboek met toetsen in een specifiek bereik

Tot nu toe is dit mijn oplossing. Ik vraag me af of er een meer elegante/efficiënte manier is?

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)

Ik weet het niet zeker of ik hier lijstbegrip moet gebruiken.

Er is waarschijnlijk ergens een functie voor dit, maar ik heb het niet gevonden.

1

2 antwoord

Als u op zoek bent naar andere interessante manieren om het probleem op te lossen, volgt hier een antwoord met 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

De betrokken ideeën zijn:

  • Gebruik een eenvoudige generator om een ​​iterator voor de datum te maken die over opeenvolgende dagen loopt van de laagste naar de hoogste.

  • Gebruik itertools.tee om een ​​paar iterators te construeren over de oudste gegevens en de nieuwste gegevens (de voorkant van het gegevensvenster en de achterkant).

  • Een lopende som behouden in een variabele s . Werk bij elke iteratie s bij door de oudste waarde af te trekken en de nieuwste waarde toe te voegen.

  • Deze oplossing is ruimtebesparend (deze bewaart niet meer dan venster -waarden in het geheugen) en is tijdbesparend, één optelling en één aftrekking voor elke dag, ongeacht de grootte van het venster .

  • Omgaan met ontbrekende dagen door standaard op nul te zetten. Er zijn andere strategieën die kunnen worden gebruikt voor ontbrekende dagen (zoals het huidige voortschrijdend gemiddelde als standaard gebruiken of n omhoog en omlaag aanpassen om het aantal actuele gegevenspunten in het venster weer te geven).

2
toegevoegd
Bedankt, dat ziet er echt veelbelovend uit en helpt me bovendien om verder te komen met mijn pythonvaardigheden. Maar ik moet nog steeds een manier bedenken om ittools vaker te gebruiken.
toegevoegd de auteur Randomtheories, de bron

Het probleem met het gebruik van lijstbegrip is in dit geval dat het inefficiënt is om de hele reeks prijzen in elke iteratie van uw lus te doorzoeken. De lijstbegrip in uw code controleert elk element van prices.keys() bij elke iteratie van de voor prijs in prijzen: lus.

Wat u echt wilt doen, is profiteren van het feit dat datums sequentieel zijn en ze in volgorde verwerken. Op die manier kunt u, wanneer u een datum elimineert van overweging op de huidige iteratie van de lus, deze in overweging nemen in alle volgende iteraties van uw lus.

Hier is een voorbeeld:

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

In plaats van elk element van prices.keys() te controleren bij elke iteratie van de lus, zoekt deze code vanaf de huidige datum terug door de lijst met datums die zijn opgenomen in totaal . Als het een datum vindt die te oud is, wordt deze verwijderd uit totaal en omdat we de datums in volgorde verwerken, hoeft deze nooit meer op die datum te worden bekeken.

1
toegevoegd
Dat was precies het probleem waar ik naar op zoek was. Ik wist dat ik iets miste, maar ik wist niet meteen wat het probleem was. Bedankt!
toegevoegd de auteur Randomtheories, de bron