-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathforecasting.py
More file actions
67 lines (59 loc) · 2.02 KB
/
Copy pathforecasting.py
File metadata and controls
67 lines (59 loc) · 2.02 KB
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
"""Simple ETS forecast on monthly median price series with rolling backtest."""
from __future__ import annotations
import numpy as np
import pandas as pd
from statsmodels.tsa.holtwinters import ExponentialSmoothing
def monthly_median_price(
df: pd.DataFrame,
town: str | None = None,
) -> pd.Series:
d = df.copy()
if town is not None and "town" in d.columns:
d = d[d["town"].astype(str).str.upper() == town.upper()]
d = d.dropna(subset=["month", "resale_price"])
s = d.groupby(d["month"].dt.to_period("M").dt.to_timestamp())["resale_price"].median()
return s.sort_index()
def forecast_ets(
series: pd.Series,
horizon: int = 6,
) -> pd.Series:
if len(series) < 24:
return pd.Series(dtype=float)
try:
# Pass ndarray to avoid statsmodels "date index has no frequency" warnings
y = series.astype(float).to_numpy()
model = ExponentialSmoothing(
y,
seasonal_periods=12,
trend="add",
seasonal="add",
).fit()
fcast = np.asarray(model.forecast(horizon), dtype=float)
if isinstance(series.index, pd.DatetimeIndex) and len(series) > 0:
last = series.index.max()
idx = pd.date_range(last, periods=horizon + 1, freq="MS")[1:]
return pd.Series(fcast, index=idx)
return pd.Series(fcast)
except Exception:
return pd.Series(dtype=float)
def backtest_rmse(
series: pd.Series,
*,
test_months: int = 6,
) -> float:
if len(series) <= test_months + 12:
return float("nan")
train = series.iloc[:-test_months]
test = series.iloc[-test_months:]
try:
model = ExponentialSmoothing(
train.astype(float).to_numpy(),
seasonal_periods=12,
trend="add",
seasonal="add",
).fit()
pred = model.forecast(test_months)
pv = np.asarray(pred, dtype=float)
return float(np.sqrt(((test.values - pv) ** 2).mean()))
except Exception:
return float("nan")