Skip to content

Commit 8c671ec

Browse files
authoredMay 12, 2021
Merge branch 'master' into naive_models
2 parents 72b32b2 + 809189e commit 8c671ec

19 files changed

+777
-33
lines changed
 

‎Project.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
1717
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
1818

1919
[compat]
20-
Distributions = "0.23, 0.24"
20+
Distributions = "0.16, 0.17, 0.18, 0.19, 0.20, 0.21, 0.22, 0.23, 0.24"
2121
MatrixEquations = "1"
22-
Optim = "0.20, 0.21, 0.22, 1.2"
22+
Optim = "0.17, 0.18, 0.19, 0.20, 0.21, 0.22, 1.2"
2323
OrderedCollections = "1"
2424
Polynomials = "1"
2525
RecipesBase = "1"
2626
ShiftedArrays = "1"
27-
StatsBase = "0.33"
27+
StatsBase = "0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33"
2828
julia = "1"
2929

3030
[extras]

‎datasets/sunspot_year.csv

+290
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
year,value
2+
1700,5
3+
1701,11
4+
1702,16
5+
1703,23
6+
1704,36
7+
1705,58
8+
1706,29
9+
1707,20
10+
1708,10
11+
1709,8
12+
1710,3
13+
1711,0
14+
1712,0
15+
1713,2
16+
1714,11
17+
1715,27
18+
1716,47
19+
1717,63
20+
1718,60
21+
1719,39
22+
1720,28
23+
1721,26
24+
1722,22
25+
1723,11
26+
1724,21
27+
1725,40
28+
1726,78
29+
1727,122
30+
1728,103
31+
1729,73
32+
1730,47
33+
1731,35
34+
1732,11
35+
1733,5
36+
1734,16
37+
1735,34
38+
1736,70
39+
1737,81
40+
1738,111
41+
1739,101
42+
1740,73
43+
1741,40
44+
1742,20
45+
1743,16
46+
1744,5
47+
1745,11
48+
1746,22
49+
1747,40
50+
1748,60
51+
1749,80.9
52+
1750,83.4
53+
1751,47.7
54+
1752,47.8
55+
1753,30.7
56+
1754,12.2
57+
1755,9.6
58+
1756,10.2
59+
1757,32.4
60+
1758,47.6
61+
1759,54
62+
1760,62.9
63+
1761,85.9
64+
1762,61.2
65+
1763,45.1
66+
1764,36.4
67+
1765,20.9
68+
1766,11.4
69+
1767,37.8
70+
1768,69.8
71+
1769,106.1
72+
1770,100.8
73+
1771,81.6
74+
1772,66.5
75+
1773,34.8
76+
1774,30.6
77+
1775,7
78+
1776,19.8
79+
1777,92.5
80+
1778,154.4
81+
1779,125.9
82+
1780,84.8
83+
1781,68.1
84+
1782,38.5
85+
1783,22.8
86+
1784,10.2
87+
1785,24.1
88+
1786,82.9
89+
1787,132
90+
1788,130.9
91+
1789,118.1
92+
1790,89.9
93+
1791,66.6
94+
1792,60
95+
1793,46.9
96+
1794,41
97+
1795,21.3
98+
1796,16
99+
1797,6.4
100+
1798,4.1
101+
1799,6.8
102+
1800,14.5
103+
1801,34
104+
1802,45
105+
1803,43.1
106+
1804,47.5
107+
1805,42.2
108+
1806,28.1
109+
1807,10.1
110+
1808,8.1
111+
1809,2.5
112+
1810,0
113+
1811,1.4
114+
1812,5
115+
1813,12.2
116+
1814,13.9
117+
1815,35.4
118+
1816,45.8
119+
1817,41.1
120+
1818,30.1
121+
1819,23.9
122+
1820,15.6
123+
1821,6.6
124+
1822,4
125+
1823,1.8
126+
1824,8.5
127+
1825,16.6
128+
1826,36.3
129+
1827,49.6
130+
1828,64.2
131+
1829,67
132+
1830,70.9
133+
1831,47.8
134+
1832,27.5
135+
1833,8.5
136+
1834,13.2
137+
1835,56.9
138+
1836,121.5
139+
1837,138.3
140+
1838,103.2
141+
1839,85.7
142+
1840,64.6
143+
1841,36.7
144+
1842,24.2
145+
1843,10.7
146+
1844,15
147+
1845,40.1
148+
1846,61.5
149+
1847,98.5
150+
1848,124.7
151+
1849,96.3
152+
1850,66.6
153+
1851,64.5
154+
1852,54.1
155+
1853,39
156+
1854,20.6
157+
1855,6.7
158+
1856,4.3
159+
1857,22.7
160+
1858,54.8
161+
1859,93.8
162+
1860,95.8
163+
1861,77.2
164+
1862,59.1
165+
1863,44
166+
1864,47
167+
1865,30.5
168+
1866,16.3
169+
1867,7.3
170+
1868,37.6
171+
1869,74
172+
1870,139
173+
1871,111.2
174+
1872,101.6
175+
1873,66.2
176+
1874,44.7
177+
1875,17
178+
1876,11.3
179+
1877,12.4
180+
1878,3.4
181+
1879,6
182+
1880,32.3
183+
1881,54.3
184+
1882,59.7
185+
1883,63.7
186+
1884,63.5
187+
1885,52.2
188+
1886,25.4
189+
1887,13.1
190+
1888,6.8
191+
1889,6.3
192+
1890,7.1
193+
1891,35.6
194+
1892,73
195+
1893,85.1
196+
1894,78
197+
1895,64
198+
1896,41.8
199+
1897,26.2
200+
1898,26.7
201+
1899,12.1
202+
1900,9.5
203+
1901,2.7
204+
1902,5
205+
1903,24.4
206+
1904,42
207+
1905,63.5
208+
1906,53.8
209+
1907,62
210+
1908,48.5
211+
1909,43.9
212+
1910,18.6
213+
1911,5.7
214+
1912,3.6
215+
1913,1.4
216+
1914,9.6
217+
1915,47.4
218+
1916,57.1
219+
1917,103.9
220+
1918,80.6
221+
1919,63.6
222+
1920,37.6
223+
1921,26.1
224+
1922,14.2
225+
1923,5.8
226+
1924,16.7
227+
1925,44.3
228+
1926,63.9
229+
1927,69
230+
1928,77.8
231+
1929,64.9
232+
1930,35.7
233+
1931,21.2
234+
1932,11.1
235+
1933,5.7
236+
1934,8.7
237+
1935,36.1
238+
1936,79.7
239+
1937,114.4
240+
1938,109.6
241+
1939,88.8
242+
1940,67.8
243+
1941,47.5
244+
1942,30.6
245+
1943,16.3
246+
1944,9.6
247+
1945,33.2
248+
1946,92.6
249+
1947,151.6
250+
1948,136.3
251+
1949,134.7
252+
1950,83.9
253+
1951,69.4
254+
1952,31.5
255+
1953,13.9
256+
1954,4.4
257+
1955,38
258+
1956,141.7
259+
1957,190.2
260+
1958,184.8
261+
1959,159
262+
1960,112.3
263+
1961,53.9
264+
1962,37.5
265+
1963,27.9
266+
1964,10.2
267+
1965,15.1
268+
1966,47
269+
1967,93.8
270+
1968,105.9
271+
1969,105.5
272+
1970,104.5
273+
1971,66.6
274+
1972,68.9
275+
1973,38
276+
1974,34.5
277+
1975,15.5
278+
1976,12.6
279+
1977,27.5
280+
1978,92.5
281+
1979,155.4
282+
1980,154.7
283+
1981,140.5
284+
1982,115.9
285+
1983,66.6
286+
1984,45.9
287+
1985,17.9
288+
1986,13.4
289+
1987,29.2
290+
1988,100.2

‎docs/src/manual.md

+2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ The package provides a variaty of pre-defined models. If there is any model that
4646
UnobservedComponents
4747
ExponentialSmoothing
4848
SARIMA
49+
DAR
4950
BasicStructural
51+
BasicStructuralExplanatory
5052
LinearRegression
5153
LocalLevel
5254
LocalLevelCycle

‎src/StateSpaceModels.jl

+4
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,14 @@ include("models/locallevelcycle.jl")
4141
include("models/locallevelexplanatory.jl")
4242
include("models/locallineartrend.jl")
4343
include("models/basicstructural.jl")
44+
include("models/basicstructural_explanatory.jl")
4445
include("models/basicstructural_multivariate.jl")
4546
include("models/sarima.jl")
4647
include("models/linear_regression.jl")
4748
include("models/unobserved_components.jl")
4849
include("models/exponential_smoothing.jl")
4950
include("models/naive_models.jl")
51+
include("models/dar.jl")
5052

5153
include("visualization/forecast.jl")
5254
include("visualization/components.jl")
@@ -56,6 +58,8 @@ include("visualization/diagnostics.jl")
5658
# Exported types and structs
5759
export BasicStructural
5860
export ExperimentalSeasonalNaive
61+
export BasicStructuralExplanatory
62+
export DAR
5963
export ExponentialSmoothing
6064
export LinearMultivariateTimeInvariant
6165
export LinearMultivariateTimeVariant

‎src/datasets.jl

+12-1
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,15 @@ Federal Reserve Bank of St Louis.
9797
# References
9898
* Hyndman, Rob J., Athanasopoulos, George. "Forecasting: Principles and Practice"
9999
"""
100-
const US_CHANGE = joinpath(dirname(@__DIR__()), "datasets", "uschange.csv")
100+
const US_CHANGE = joinpath(dirname(@__DIR__()), "datasets", "uschange.csv")
101+
102+
@doc raw"""
103+
SUNSPOTS_YEAR
104+
105+
Yearly numbers of sunspots from 1700 to 1988 (rounded to one digit).
106+
Federal Reserve Bank of St Louis.
107+
108+
# References
109+
* H. Tong (1996) Non-Linear Time Series. Clarendon Press, Oxford, p. 471.
110+
"""
111+
const SUNSPOTS_YEAR = joinpath(dirname(@__DIR__()), "datasets", "sunspot_year.csv")

‎src/kalman_filter_and_smoother.jl

+5-3
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,11 @@ function kalman_filter(model::StateSpaceModel; filter::KalmanFilter=default_filt
181181
assert_possible_to_filter(model)
182182
filter_output = FilterOutput(model)
183183
reset_filter!(filter)
184-
free_unconstrained_values = get_free_unconstrained_values(model)
185-
update_model_hyperparameters!(model, free_unconstrained_values)
186-
update_filter_hyperparameters!(filter, model)
184+
if has_hyperparameter(model)
185+
free_unconstrained_values = get_free_unconstrained_values(model)
186+
update_model_hyperparameters!(model, free_unconstrained_values)
187+
update_filter_hyperparameters!(filter, model)
188+
end
187189
return kalman_filter!(filter_output, model.system, filter)
188190
end
189191

+215
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
@doc raw"""
2+
BasicStructuralExplanatory(y::Vector{Fl}, s::Int, X::Matrix{Fl}) where Fl
3+
4+
It is defined by:
5+
```math
6+
\begin{gather*}
7+
\begin{aligned}
8+
y_{t} &= \mu_{t} + \gamma_{t} + \beta_{t, i}X_{t, i} \varepsilon_{t} \quad &\varepsilon_{t} \sim \mathcal{N}(0, \sigma^2_{\varepsilon})\\
9+
\mu_{t+1} &= \mu_{t} + \nu_{t} + \xi_{t} \quad &\xi_{t} \sim \mathcal{N}(0, \sigma^2_{\xi})\\
10+
\nu_{t+1} &= \nu_{t} + \zeta_{t} \quad &\zeta_{t} \sim \mathcal{N}(0, \sigma^2_{\zeta})\\
11+
\gamma_{t+1} &= -\sum_{j=1}^{s-1} \gamma_{t+1-j} + \omega_{t} \quad & \omega_{t} \sim \mathcal{N}(0, \sigma^2_{\omega})\\
12+
\beta_{t+1} &= \beta_{t}
13+
\end{aligned}
14+
\end{gather*}
15+
```
16+
17+
# Example
18+
```jldoctest
19+
julia> model = BasicStructuralExplanatory(rand(100), 12, rand(100, 2))
20+
BasicStructuralExplanatory model
21+
```
22+
23+
# References
24+
* Durbin, James, & Siem Jan Koopman. (2012). "Time Series Analysis by State Space Methods: Second Edition." Oxford University Press.
25+
"""
26+
mutable struct BasicStructuralExplanatory <: StateSpaceModel
27+
hyperparameters::HyperParameters
28+
system::LinearUnivariateTimeVariant
29+
seasonality::Int
30+
results::Results
31+
exogenous::Matrix
32+
33+
function BasicStructuralExplanatory(y::Vector{Fl}, s::Int, X::Matrix{Fl}) where Fl
34+
35+
@assert length(y) == size(X, 1)
36+
37+
num_observations = size(X, 1)
38+
num_exogenous = size(X, 2)
39+
40+
Z = [vcat([1; 0; 1; zeros(Fl, s - 2)], X[t, :]) for t in 1:num_observations]
41+
T = [[
42+
1 1 zeros(Fl, 1, s - 1) zeros(Fl, 1, num_exogenous)
43+
0 1 zeros(Fl, 1, s - 1) zeros(Fl, 1, num_exogenous)
44+
0 0 -ones(Fl, 1, s - 1) zeros(Fl, 1, num_exogenous)
45+
zeros(Fl, s - 2, 2) Matrix{Fl}(I, s - 2, s - 2) zeros(Fl, s - 2) zeros(Fl, 10, num_exogenous)
46+
zeros(Fl, num_exogenous, 13) Matrix{Fl}(I, num_exogenous, num_exogenous)
47+
] for _ in 1:num_observations]
48+
R = [[
49+
Matrix{Fl}(I, 3, 3)
50+
zeros(Fl, s - 2, 3)
51+
zeros(num_exogenous, 3)
52+
] for _ in 1:num_observations]
53+
d = [zero(Fl) for _ in 1:num_observations]
54+
c = [zeros(Fl, size(T[1], 1)) for _ in 1:num_observations]
55+
H = [one(Fl) for _ in 1:num_observations]
56+
Q = [zeros(Fl, 3, 3) for _ in 1:num_observations]
57+
58+
system = LinearUnivariateTimeVariant{Fl}(y, Z, T, R, d, c, H, Q)
59+
60+
names = [["sigma2_ε", "sigma2_ξ", "sigma2_ζ", "sigma2_ω"]; ["β_$i" for i in 1:num_exogenous]]
61+
62+
hyperparameters = HyperParameters{Fl}(names)
63+
64+
return new(hyperparameters, system, s, Results{Fl}(), X)
65+
end
66+
end
67+
68+
function default_filter(model::BasicStructuralExplanatory)
69+
Fl = typeof_model_elements(model)
70+
steadystate_tol = Fl(1e-5)
71+
a1 = zeros(Fl, num_states(model))
72+
P1 = Fl(1e6) .* Matrix{Fl}(I, num_states(model), num_states(model))
73+
return UnivariateKalmanFilter(a1, P1, num_states(model), steadystate_tol)
74+
end
75+
76+
function initial_hyperparameters!(model::BasicStructuralExplanatory)
77+
Fl = typeof_model_elements(model)
78+
initial_hyperparameters = Dict{String,Fl}(
79+
"sigma2_ε" => one(Fl),
80+
"sigma2_ξ" => one(Fl),
81+
"sigma2_ζ" => one(Fl),
82+
"sigma2_ω" => one(Fl),
83+
)
84+
initial_exogenous = model.exogenous \ model.system.y
85+
for i in axes(model.exogenous, 2)
86+
initial_hyperparameters[get_beta_name(model, i)] = initial_exogenous[i]
87+
end
88+
set_initial_hyperparameters!(model, initial_hyperparameters)
89+
return model
90+
end
91+
92+
function get_beta_name(model::BasicStructuralExplanatory, i::Int)
93+
return model.hyperparameters.names[i + 4]
94+
end
95+
96+
function constrain_hyperparameters!(model::BasicStructuralExplanatory)
97+
for i in axes(model.exogenous, 2)
98+
constrain_identity!(model, get_beta_name(model, i))
99+
end
100+
constrain_variance!(model, "sigma2_ε")
101+
constrain_variance!(model, "sigma2_ξ")
102+
constrain_variance!(model, "sigma2_ζ")
103+
constrain_variance!(model, "sigma2_ω")
104+
return model
105+
end
106+
107+
function unconstrain_hyperparameters!(model::BasicStructuralExplanatory)
108+
for i in axes(model.exogenous, 2)
109+
unconstrain_identity!(model, get_beta_name(model, i))
110+
end
111+
unconstrain_variance!(model, "sigma2_ε")
112+
unconstrain_variance!(model, "sigma2_ξ")
113+
unconstrain_variance!(model, "sigma2_ζ")
114+
unconstrain_variance!(model, "sigma2_ω")
115+
return model
116+
end
117+
118+
function fill_model_system!(model::BasicStructuralExplanatory)
119+
H = get_constrained_value(model, "sigma2_ε")
120+
fill_H_in_time(model, H)
121+
for t in 1:length(model.system.Q)
122+
model.system.Q[t][1] = get_constrained_value(model, "sigma2_ξ")
123+
model.system.Q[t][5] = get_constrained_value(model, "sigma2_ζ")
124+
model.system.Q[t][end] = get_constrained_value(model, "sigma2_ω")
125+
end
126+
return model
127+
end
128+
129+
function fill_H_in_time(model::BasicStructuralExplanatory, H::Fl) where Fl
130+
return fill_system_matrice_with_value_in_time(model.system.H, H)
131+
end
132+
133+
function reinstantiate(model::BasicStructuralExplanatory, y::Vector{Fl}, X::Matrix{Fl}) where Fl
134+
return BasicStructuralExplanatory(y, model.seasonality, X)
135+
end
136+
137+
has_exogenous(::BasicStructuralExplanatory) = true
138+
139+
# BasicStructuralExplanatory requires a custom simulation
140+
141+
function simulate_scenarios(
142+
model::BasicStructuralExplanatory,
143+
steps_ahead::Int,
144+
n_scenarios::Int,
145+
new_exogenous::Matrix{Fl};
146+
filter::KalmanFilter=default_filter(model),
147+
) where Fl
148+
@assert steps_ahead == size(new_exogenous, 1) "new_exogenous must have the same dimension as steps_ahead"
149+
# Query the type of model elements
150+
fo = kalman_filter(model)
151+
last_state = fo.a[end]
152+
num_series = size(model.system.y, 2)
153+
154+
scenarios = Array{Fl,3}(undef, steps_ahead, num_series, n_scenarios)
155+
for s in 1:n_scenarios
156+
scenarios[:, :, s] = simulate(model, last_state, steps_ahead, new_exogenous)
157+
end
158+
return scenarios
159+
end
160+
161+
function simulate_scenarios(
162+
model::BasicStructuralExplanatory,
163+
steps_ahead::Int,
164+
n_scenarios::Int,
165+
new_exogenous::Array{Fl, 3};
166+
filter::KalmanFilter=default_filter(model),
167+
) where Fl
168+
@assert steps_ahead == size(new_exogenous, 1) "new_exogenous must have the same dimension of steps_ahead"
169+
@assert n_scenarios == size(new_exogenous, 3) "new_exogenous must have the same number of scenarios of n_scenarios"
170+
# Query the type of model elements
171+
fo = kalman_filter(model)
172+
last_state = fo.a[end]
173+
num_series = size(model.system.y, 2)
174+
175+
scenarios = Array{Fl,3}(undef, steps_ahead, num_series, n_scenarios)
176+
for s in 1:n_scenarios
177+
scenarios[:, :, s] = simulate(model, last_state, steps_ahead, new_exogenous[:, :, s])
178+
end
179+
return scenarios
180+
end
181+
182+
function simulate(
183+
model::BasicStructuralExplanatory,
184+
initial_state::Vector{Fl},
185+
n::Int,
186+
new_exogenous::Matrix{Fl};
187+
return_simulated_states::Bool=false,
188+
) where Fl
189+
sys = model.system
190+
m = size(sys.T[1], 1)
191+
y = Vector{Fl}(undef, n)
192+
alpha = Matrix{Fl}(undef, n + 1, m)
193+
# Sampling errors
194+
chol_H = sqrt(sys.H[1])
195+
chol_Q = cholesky(sys.Q[1])
196+
standard_ε = randn(n)
197+
standard_η = randn(n + 1, size(sys.Q[1], 1))
198+
199+
# The first state of the simulation is the update of a_0
200+
alpha[1, :] .= initial_state
201+
sys.Z[1][14:end] .= new_exogenous[1, :]
202+
y[1] = dot(sys.Z[1], initial_state) + sys.d[1] + chol_H * standard_ε[1]
203+
alpha[2, :] = sys.T[1] * initial_state + sys.c[1] + sys.R[1] * chol_Q.L * standard_η[1, :]
204+
# Simulate scenarios
205+
for t in 2:n
206+
sys.Z[t][14:end] .= new_exogenous[t, :]
207+
y[t] = dot(sys.Z[t], alpha[t, :]) + sys.d[t] + chol_H * standard_ε[t]
208+
alpha[t + 1, :] = sys.T[t] * alpha[t, :] + sys.c[t] + sys.R[t] * chol_Q.L * standard_η[t, :]
209+
end
210+
211+
if return_simulated_states
212+
return y, alpha[1:n, :]
213+
end
214+
return y
215+
end

‎src/models/common.jl

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
typeof_model_elements(model::StateSpaceModel) = eltype(model.system.y)
22
num_states(model::StateSpaceModel) = num_states(system(model))
3+
num_series(model::StateSpaceModel) = num_series(system(model))
34
system(model::StateSpaceModel) = model.system
45
isunivariate(model::StateSpaceModel) = isa(model.system.y, Vector)
56
model_name(model::StateSpaceModel) = "$(typeof(model))"
67
length_observations(model::StateSpaceModel) = length(model.system.y)
7-
observations(model::StateSpaceModel) = model.system.y
8+
observations(model::StateSpaceModel) = model.system.y
9+
10+
function lagmat(y::Vector{Fl}, k::Int) where Fl
11+
X = Matrix{Fl}(undef, length(y) - k, k)
12+
for i in 1:k
13+
X[:, i] = lag(y, i)[k + 1:end]
14+
end
15+
return X
16+
end

‎src/models/dar.jl

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
@doc raw"""
2+
DAR(y::Vector{Fl}, lags::Int) where Fl
3+
4+
A Dynamic Autorregressive model is defined by:
5+
```math
6+
\begin{gather*}
7+
\begin{aligned}
8+
y_{t} &= \phi_0 + \sum_{i=1}^{lags} \phi_{i, t} y_{t - i} + \varepsilon_{t} \quad \varepsilon_{t} \sim \mathcal{N}(0, \sigma^2_{\varepsilon})\\
9+
\phi_{i, t+1} &= \phi_{i, t} + \eta{i, t} \quad \eta{i, t} \sim \mathcal{N}(0, \sigma^2_{i, \eta})\\
10+
\end{aligned}
11+
\end{gather*}
12+
```
13+
14+
!!!! note System matrices
15+
When building the system matrices we get rid of the first `lags` observations
16+
in order to build all system matrices respecting the correspondent timestamps
17+
18+
!!! warning Forecasting
19+
The dynamic autorregressive model does not have the [`forecast`](@ref) method implemented yet.
20+
If you wish to perform forecasts with this model please open an issue.
21+
22+
# Example
23+
```jldoctest
24+
julia> model = DAR(randn(100), 2)
25+
DAR model
26+
```
27+
"""
28+
mutable struct DAR <: StateSpaceModel
29+
lags::Int
30+
first_observations::Vector{<:Real}
31+
hyperparameters::HyperParameters
32+
system::LinearUnivariateTimeVariant
33+
results::Results
34+
35+
function DAR(y::Vector{Fl}, lags::Int) where Fl
36+
37+
X = lagmat(y, lags)
38+
num_observations = size(X, 1)
39+
first_observations = y[1:lags]
40+
41+
# Define system matrices
42+
Z = [X[t, :] for t in 1:num_observations]
43+
T = [Matrix{Fl}(I, lags, lags) for _ in 1:num_observations]
44+
R = [Matrix{Fl}(I, lags, lags) for _ in 1:num_observations]
45+
d = [zero(Fl) for _ in 1:num_observations]
46+
c = [zeros(lags) for _ in 1:num_observations]
47+
H = [one(Fl) for _ in 1:num_observations]
48+
Q = [Matrix{Fl}(I, lags, lags) for _ in 1:num_observations]
49+
50+
system = LinearUnivariateTimeVariant{Fl}(y[lags+1:end], Z, T, R, d, c, H, Q)
51+
52+
# Define hyperparameters names
53+
names = [["sigma2_ε", "intercept"];["sigma2_ar_$i" for i in 1:lags]]
54+
hyperparameters = HyperParameters{Fl}(names)
55+
56+
return new(lags, first_observations, hyperparameters, system, Results{Fl}())
57+
end
58+
end
59+
60+
# Obligatory methods
61+
function default_filter(model::DAR)
62+
Fl = typeof_model_elements(model)
63+
a1 = zeros(Fl, num_states(model))
64+
# P1 is identity on sigma \eta and 0 otherwise.
65+
P1 = Matrix{Fl}(I, num_states(model), num_states(model))
66+
steadystate_tol = Fl(1e-5)
67+
return UnivariateKalmanFilter(a1, P1, 0, steadystate_tol)
68+
end
69+
70+
function get_sigma_ar_name(model::DAR, i::Int)
71+
return model.hyperparameters.names[i + 2]
72+
end
73+
74+
function initial_hyperparameters!(model::DAR)
75+
Fl = typeof_model_elements(model)
76+
observed_variance = var(model.system.y[findall(!isnan, model.system.y)])
77+
observed_mean = mean(model.system.y[findall(!isnan, model.system.y)])
78+
initial_hyperparameters = Dict{String,Fl}(
79+
"sigma2_ε" => observed_variance, "intercept" => observed_mean
80+
)
81+
for i in axes(model.system.T[1], 2)
82+
initial_hyperparameters[get_sigma_ar_name(model, i)] = one(Fl)
83+
end
84+
set_initial_hyperparameters!(model, initial_hyperparameters)
85+
return model
86+
end
87+
88+
function constrain_hyperparameters!(model::DAR)
89+
for i in axes(model.system.T[1], 2)
90+
constrain_variance!(model, get_sigma_ar_name(model, i))
91+
end
92+
constrain_variance!(model, "sigma2_ε")
93+
constrain_identity!(model, "intercept")
94+
return model
95+
end
96+
97+
function unconstrain_hyperparameters!(model::DAR)
98+
for i in axes(model.system.T[1], 2)
99+
unconstrain_variance!(model, get_sigma_ar_name(model, i))
100+
end
101+
unconstrain_variance!(model, "sigma2_ε")
102+
unconstrain_identity!(model, "intercept")
103+
return model
104+
end
105+
106+
function fill_model_system!(model::DAR)
107+
H = get_constrained_value(model, "sigma2_ε")
108+
fill_H_in_time(model, H)
109+
intercept = get_constrained_value(model, "intercept")
110+
for t in 1:length(model.system.Q)
111+
fill!(model.system.d, intercept)
112+
for i in axes(model.system.Q[1], 1)
113+
model.system.Q[t][i, i] = get_constrained_value(model, get_sigma_ar_name(model, i))
114+
end
115+
end
116+
return model
117+
end
118+
119+
function fill_H_in_time(model::DAR, H::Fl) where Fl
120+
return fill_system_matrice_with_value_in_time(model.system.H, H)
121+
end
122+
123+
function reinstantiate(model::DAR, y::Vector{Fl}) where Fl
124+
return DAR(y, model.lags)
125+
end
126+
127+
# This model needs a custom made forecasting routine
128+
function forecast(
129+
model::DAR, steps_ahead::Int; filter::KalmanFilter=default_filter(model)
130+
)
131+
return error("Forecasting is currently nor implemented for the DAR model.")
132+
end

‎src/models/locallevelexplanatory.jl

+2-11
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ mutable struct LocalLevelExplanatory <: StateSpaceModel
3737
# Define system matrices
3838
Z = [vcat(ones(Fl, 1), X[t, :]) for t in 1:num_observations]
3939
T = [Matrix{Fl}(I, m, m) for _ in 1:num_observations]
40-
R = [ones(Fl, 1, 1) for _ in 1:num_observations]
40+
R = [vcat(one(Fl), zeros(Fl, m-1, 1)) for _ in 1:num_observations]
4141
d = [zero(Fl) for _ in 1:num_observations]
4242
c = [zeros(m) for _ in 1:num_observations]
4343
H = [one(Fl) for _ in 1:num_observations]
@@ -57,9 +57,7 @@ end
5757
function default_filter(model::LocalLevelExplanatory)
5858
Fl = typeof_model_elements(model)
5959
a1 = zeros(Fl, num_states(model))
60-
# P1 is identity on sigma \eta and 0 otherwise.
61-
P1 = zeros(Fl, num_states(model), num_states(model))
62-
P1[1] = Fl(1e6)
60+
P1 = Fl(1e6) .* Matrix{Fl}(I, num_states(model), num_states(model))
6361
steadystate_tol = Fl(1e-5)
6462
return UnivariateKalmanFilter(a1, P1, num_states(model), steadystate_tol)
6563
end
@@ -110,13 +108,6 @@ function fill_model_system!(model::LocalLevelExplanatory)
110108
return model
111109
end
112110

113-
function fill_model_filter!(filter::KalmanFilter, model::LocalLevelExplanatory)
114-
for i in axes(model.exogenous, 2)
115-
filter.kalman_state.a[i + 1] = get_constrained_value(model, get_beta_name(model, i))
116-
end
117-
return filter
118-
end
119-
120111
function fill_H_in_time(model::LocalLevelExplanatory, H::Fl) where Fl
121112
return fill_system_matrice_with_value_in_time(model.system.H, H)
122113
end

‎src/models/sarima.jl

-8
Original file line numberDiff line numberDiff line change
@@ -361,14 +361,6 @@ function SARIMA_exact_initialization!(kalman_state,
361361
return nothing
362362
end
363363

364-
function lagmat(y::Vector{Fl}, k::Int) where Fl
365-
X = Matrix{Fl}(undef, length(y) - k, k)
366-
for i in 1:k
367-
X[:, i] = lag(y, i)[k + 1:end]
368-
end
369-
return X
370-
end
371-
372364
function concatenate_on_bottom(X1::Matrix{Fl}, X2::Matrix{Fl}) where Fl
373365
n = min(size(X1, 1), size(X2, 1))
374366
return hcat(X1[end-n+1:end, :], X2[end-n+1:end, :])

‎src/models/unobserved_components.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ than captured by the seasonal component. The parameter ``\lambda_c`` is the freq
221221
and it is estimated via maximum likelihood. The inclusion of error terms allows the cycle
222222
effects to vary over time. The modelling options can be expressed in terms
223223
of `"deterministic"` or `"stochastic"` and the damping effect as a string, i.e.,
224-
`cycle = "stochastic"`, `cycle = "deterministic"` or `cycle = "damped"`.
224+
`cycle = "stochastic"`, `cycle = "deterministic"` or `cycle = "stochastic damped"`.
225225
226226
The UnobservedComponents model has some dedicated Plot Recipes, see [Visualization](@ref)
227227

‎src/smoothers/kalman_smoother.jl

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
function kalman_smoother!(
22
smoother_output::SmootherOutput,
3-
system::LinearUnivariateTimeInvariant,
3+
system::Union{LinearUnivariateTimeInvariant,
4+
LinearUnivariateTimeVariant,
5+
LinearMultivariateTimeInvariant},
46
filter_output::FilterOutput,
57
)
68
multivariate_system = to_multivariate_time_variant(system)

‎src/systems.jl

+58-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ linear models.
66
"""
77
abstract type StateSpaceSystem end
88

9+
num_series(sys::StateSpaceSystem) = size(sys.y, 2)
10+
911
@doc raw"""
1012
LinearUnivariateTimeInvariant{Fl}(
1113
y::Vector{Fl},
@@ -134,7 +136,16 @@ mutable struct LinearUnivariateTimeVariant{Fl<:AbstractFloat} <: StateSpaceSyste
134136
Q::Vector{Matrix{Fl}},
135137
) where Fl <: AbstractFloat
136138

137-
# TODO assert dimensions
139+
@assert ( length(y) == length(Z) == length(T) == length(R) ==
140+
length(d) == length(c) == length(H) == length(Q) )
141+
142+
mz = length(Z[1])
143+
mt1, mt2 = size(T[1])
144+
mr, rr = size(R[1])
145+
mc = length(c[1])
146+
rq1, rq2 = size(Q[1])
147+
@assert (mz == mt1 == mt2 == mr == mc)
148+
@assert (rr == rq1 == rq2)
138149

139150
return new{Fl}(y, Z, T, R, d, c, H, Q)
140151
end
@@ -273,6 +284,20 @@ function to_multivariate_time_variant(system::LinearUnivariateTimeInvariant)
273284
return to_multivariate_time_variant(univariate_time_variant)
274285
end
275286

287+
# LinearMultivariateTimeInvariant to (LinearMultivariateTimeVariant)
288+
function to_multivariate_time_variant(system::LinearMultivariateTimeInvariant{Fl}) where Fl
289+
n = length(system.y)
290+
y = system.y
291+
Z = repeat_system_matrice_n_times(system.Z, n)
292+
T = repeat_system_matrice_n_times(system.T, n)
293+
R = repeat_system_matrice_n_times(system.R, n)
294+
d = repeat_system_matrice_n_times(system.d, n)
295+
c = repeat_system_matrice_n_times(system.c, n)
296+
H = repeat_system_matrice_n_times(system.H, n)
297+
Q = repeat_system_matrice_n_times(system.Q, n)
298+
return LinearMultivariateTimeVariant{Fl}(y, Z, T, R, d, c, H, Q)
299+
end
300+
276301
# LinearUnivariateTimeVariant to (LinearMultivariateTimeVariant)
277302
function to_multivariate_time_variant(system::LinearUnivariateTimeVariant{Fl}) where Fl
278303
y = system.y[:, :]
@@ -319,3 +344,35 @@ function simulate(
319344
end
320345
return y
321346
end
347+
348+
function simulate(
349+
sys::LinearMultivariateTimeInvariant{Fl},
350+
initial_state::Vector{Fl},
351+
n::Int;
352+
return_simulated_states::Bool=false,
353+
) where Fl
354+
p = size(sys.y, 2)
355+
m = size(sys.T, 1)
356+
y = Matrix{Fl}(undef, n, p)
357+
alpha = Matrix{Fl}(undef, n + 1, m)
358+
# Sampling errors
359+
chol_H = cholesky(sys.H)
360+
chol_Q = cholesky(sys.Q)
361+
standard_ε = randn(n, size(sys.H, 1))
362+
standard_η = randn(n + 1, size(sys.Q, 1))
363+
364+
# The first state of the simulation is the update of a_0
365+
alpha[1, :] .= initial_state
366+
y[1, :] = sys.Z * initial_state + sys.d + chol_H.L * standard_ε[1, :]
367+
alpha[2, :] = sys.T * initial_state + sys.c + sys.R * chol_Q.L * standard_η[1, :]
368+
# Simulate scenarios
369+
for t in 2:n
370+
y[t, :] = sys.Z * alpha[t, :] + sys.d + chol_H.L * standard_ε[t, :]
371+
alpha[t + 1, :] = sys.T * alpha[t, :] + sys.c + sys.R * chol_Q.L * standard_η[t, :]
372+
end
373+
374+
if return_simulated_states
375+
return y, alpha[1:n, :]
376+
end
377+
return y
378+
end
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
@testset "Basic Structural With Explanatory Model" begin
2+
@test has_fit_methods(BasicStructuralExplanatory)
3+
y = CSV.File(StateSpaceModels.AIR_PASSENGERS) |> DataFrame
4+
logap = log.(y.passengers)
5+
X = rand(length(logap), 2)
6+
model = BasicStructuralExplanatory(logap, 12, X)
7+
fit!(model)
8+
model.results
9+
# forecasting
10+
# For a fixed forecasting explanatory the variance must not decrease
11+
forec = forecast(model, ones(10, 2))
12+
@test monotone_forecast_variance(forec)
13+
kf = kalman_filter(model)
14+
ks = kalman_smoother(model)
15+
@test_throws AssertionError simulate_scenarios(model, 10, 1000, ones(5, 2))
16+
scenarios = simulate_scenarios(model, 10, 1000, ones(10, 2))
17+
test_scenarios_adequacy_with_forecast(forec, scenarios)
18+
scenarios = simulate_scenarios(model, 10, 500, ones(10, 2, 500))
19+
test_scenarios_adequacy_with_forecast(forec, scenarios)
20+
end

‎test/models/basicstructural_multivariate.jl

+3
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,7 @@
77
fit!(model)
88
forec = forecast(model, 10)
99
@test monotone_forecast_variance(forec)
10+
# simualting
11+
scenarios = simulate_scenarios(model, 10, 10_000)
12+
test_scenarios_adequacy_with_forecast(forec, scenarios)
1013
end

‎test/models/dar.jl

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@testset "DAR Model" begin
2+
# Univariate
3+
sunspot_year = CSV.File(StateSpaceModels.SUNSPOTS_YEAR) |> DataFrame
4+
5+
@test has_fit_methods(DAR)
6+
7+
model = DAR(sunspot_year.value, 9)
8+
fit!(model)
9+
# Runned on Python statsmodels
10+
@test loglike(model) -1175.9129 atol = 1e-5 rtol = 1e-5
11+
12+
# forecasting
13+
@test_throws ErrorException forecast(model, 10)
14+
end

‎test/models/locallevelexplanatory.jl

+1-3
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,5 @@
4242
forec = forecast(model, ones(5, 2))
4343
@test monotone_forecast_variance(forec)
4444
kf = kalman_filter(model)
45-
a = get_predictive_state(kf)
46-
@test a[1, 2] a[end, 2] atol=1e-3
47-
@test a[1, 3] a[end, 3] atol=1e-3
45+
ks = kalman_smoother(model)
4846
end

‎test/runtests.jl

+2
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ include("models/locallineartrend.jl")
1919
include("models/locallevelcycle.jl")
2020
include("models/locallevelexplanatory.jl")
2121
include("models/basicstructural.jl")
22+
include("models/basicstructural_explanatory.jl")
2223
include("models/basicstructural_multivariate.jl")
2324
include("models/sarima.jl")
2425
include("models/unobserved_components.jl")
2526
include("models/linear_regression.jl")
2627
include("models/exponential_smoothing.jl")
2728
include("models/naive_models.jl")
29+
include("models/dar.jl")
2830

2931
# Visualization
3032
include("visualization/forecast.jl")

0 commit comments

Comments
 (0)
Please sign in to comment.