-
Notifications
You must be signed in to change notification settings - Fork 0
/
simpletsmodel.py
123 lines (111 loc) · 4.28 KB
/
simpletsmodel.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import numpy as np
import pandas as pd
# written by [Michael Richman](https://github.com/zgana)
class SimpleTSModel:
"""Simple TS model base class."""
def __init__(self, y):
"""Determine y data and sampling frequency."""
if not hasattr(y, 'index'):
y = pd.Series(y)
if hasattr(y.index, 'inferred_freq'):
self.y = y.asfreq(y.index.inferred_freq)
self.freq = self.y.index.freq
else:
self.y = y.copy()
self.freq = None
def fit(self):
"""Nothing to do here, but in other libraries this is a method that does things."""
return self
def forecast(self, dt=None, end=None, periods=None):
"""Make a forward-looking prediction."""
assert sum([dt is None, end is None, periods is None]) == 2
tmax = self.y.index.max()
if dt is not None:
end = tmax + (pd.to_timedelta(dt) if self.freq else dt)
elif end is not None:
end = pd.to_datetime(end) if self.freq else end
elif periods is not None:
end = tmax + periods * (self.freq or 1)
return self.predict(tmax + 1 * (self.freq or 1), end)
def _normalize_times(self, start, end):
"""Do some tedious datetime manipulation."""
Y = self.y
t0 = Y.index.min()
if start is None:
start = t0
if end is None:
end = Y.index.max()
if self.freq is not None:
start = pd.to_datetime(start)
end = pd.to_datetime(end)
if self.freq:
index = pd.date_range(t0, end, freq=self.freq)
else:
index = np.arange(t0, end+1)
return start, end, index
class TSMean(SimpleTSModel):
"""The future will look like the average of the past."""
def predict(self, start=None, end=None):
# value is always the mean
Y = self.y
start, end, index = self._normalize_times(start, end)
m = Y.mean()
out = pd.Series(m, index=index)
out = out.loc[start:].copy()
return out
class TSNaive(SimpleTSModel):
"""Tomorrow will look like today."""
def predict(self, start=None, end=None):
# tomorrow probably same as today
Y = self.y
start, end, index = self._normalize_times(start, end)
out = pd.Series(np.nan, index=index)
out.loc[:Y.index.max()] = Y
out = out.shift(1)
out.loc[Y.index.max():] = Y.iloc[-1]
out = out.loc[start:].copy()
return out.copy()
class TSNaiveSeasonal(SimpleTSModel):
"""Next year will fluctuate the same way as this year."""
def __init__(self, y, lag):
super(TSNaiveSeasonal, self).__init__(y)
self.lag = lag
assert self.y.index.min() + lag * (self.freq or 1) < self.y.index.max(), \
'lag must be less than input timeseries'
def predict(self, start=None, end=None):
# tomorrow probably same as this time last year/month/whatever
Y, lag = self.y, self.lag
start, end, index = self._normalize_times(start, end)
out = pd.Series(np.nan, index=index)
out.loc[:Y.index.max()] = Y
out = out.shift(lag)
i = 0
while np.isnan(out.iloc[-1]):
mask = out.isna()
out[mask] = out.shift(lag)[mask]
i += 1
out = out.loc[start:].copy()
return out.copy()
class TSDrift(SimpleTSModel):
"""Draw a line from t=0 thru today, and extrapolate to tomorrow."""
def predict(self, start=None, end=None):
# value extrapolated based on slope wrt first observation
# TODO: might be slightly wrong
# doesn't *quite* agree with R's RW(Y~drift()) ?
Y = self.y
Y0 = Y.values[0]
YT = Y.shift(-1)
start, end, index = self._normalize_times(start, end)
YT = pd.Series(np.nan, index=index)
YT.loc[Y.index.min():Y.index.max()] = Y
YT = YT.shift(1)
YT.iloc[0] = Y.iloc[0]
h = pd.Series(1, index=index)
extrap_mask = YT.isna()
h.loc[YT.isna()] = np.arange(1, extrap_mask.sum()+1)
YT.loc[extrap_mask] = Y.iloc[-1]
x = np.maximum(1, np.arange(len(YT)) - 1)
out = YT + h * ((YT - Y0) / x)
out.iloc[0] = np.nan
out = out.loc[start:].copy()
return out.copy()