到目前为止,这是我的解决方案。我想知道是否有更优雅/更有效的方式?
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)
我不确定,我是否应该在这里使用list-comprehension。
这个地方可能有一些功能,但我找不到它。
答案 0 :(得分:2)
如果您正在寻找其他有趣的方法来解决问题,可以使用 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 。
这个解决方案节省空间(它在内存中保留不超过 window 值)并且它是时间有效的,每天一次加法和一次减法,无论窗口的大小如何
通过默认为零来处理错过的日子。还有其他策略可用于缺失天数(例如使用当前移动平均值作为默认值或上下调整 n 以反映窗口中实际数据点的数量)。
答案 1 :(得分:1)
在这种情况下使用列表推导的问题在于,在循环的每次迭代中搜索整个价格集都是低效的。代码中的列表理解会在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
中删除,因为我们按顺序处理日期,所以再也不需要查看该日期了。