Skip to content

Commit b03a49d

Browse files
Forecast with confidence intervals (#62)
* forecast working * fix 0.95 ci * take out push loadpath
1 parent bc5e7af commit b03a49d

File tree

3 files changed

+56
-19
lines changed

3 files changed

+56
-19
lines changed

examples/inflow_lognormal.jl

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,27 @@ inflow = [
4646

4747
# Convert data to vector
4848
y = Vector{Float64}(vec(inflow'))
49+
y_train = y[1:400]
50+
y_test = y[401:end]
4951

5052
# Specify GAS model: here we use lag 1 for trend characterization and lag 12 for seasonality characterization
51-
gas = GAS.Model([1, 12], [1, 12], LogNormal, 0.0)
53+
gas = GAS.Model([1, 2, 11, 12], [1, 2, 11, 12], LogNormal, 0.0; time_varying_params = [1])
5254

5355
# Define initial_params with
54-
initial_params = dynamic_initial_params(y, gas)
56+
initial_params = dynamic_initial_params(y_train, gas)
5557

5658
# Estimate the model via MLE
57-
fit!(gas, y; initial_params = initial_params)
59+
fit!(gas, y_train; initial_params = initial_params, opt_method = NelderMead(gas, 100))
5860

5961
# Obtain in-sample estimates for the inflow
60-
y_gas = fitted_mean(gas, y; initial_params = initial_params)
62+
y_fitted = fitted_mean(gas, y_train; initial_params = initial_params)
6163

6264
# Compare observations and in-sample estimates
63-
plot(y, label = "historical inflow")
64-
plot!(y_gas, label = "in-sample estimates")
65+
plot(y_train, label = "In-sample inflow")
66+
plot!(y_fitted, label = "in-sample estimates")
67+
68+
# Forecasts with 95% confidence interval
69+
forec = GAS.forecast(y_train, gas, 80; initial_params = initial_params, ci = [0.95])
70+
71+
plot(y_test, label = "Out-of-sample inflow")
72+
plot!(forec, label = "Forecast", color = "Steel Blue")

src/simulate.jl

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ function simulate(serie::Vector{T}, gas::Model{D, T}, N::Int, S::Int;
1717
# Create scenarios matrix
1818
scenarios = Matrix{T}(undef, N, S)
1919
for scenario in 1:S
20-
sim, param = simulate_recursion(gas, N + biggest_lag; initial_params = params_simulation)
21-
scenarios[:, scenario] = sim[biggest_lag + 1:end]
20+
sim, param = simulate_recursion(gas, N + biggest_lag + 1; initial_params = params_simulation)
21+
scenarios[:, scenario] = sim[biggest_lag + 2:end]
2222
end
2323

2424
return scenarios
@@ -29,20 +29,45 @@ end
2929
forecast(serie::Vector{T}, gas::Model{D, T}, N::Int; kwargs...) where {D, T}
3030
3131
Forecast future values of a time series by updating the GAS recursion `N` times and
32-
taking the mean of the distribution at each time.
32+
taking the mean of the distribution at each time. You can pass the desired confidence interval
33+
as a `Vector{T}`. The forecast will be the first column and the confidence intervals are the remaining
34+
columns.
35+
36+
The forecast is build using Monte Carlo method as in Blasques, Francisco, Siem Jan Koopman,
37+
Katarzyna Lasak and Andre Lucas (2016): "In-Sample Confidence Bounds and Out-of-Sample Forecast
38+
Bands for Time-Varying Parameters in Observation Driven Models", International Journal of Forecasting, 32(3), 875-887.
39+
40+
By default 1000 scenarios are used but one can change by switching the `S` keyword argument.
3341
"""
3442
function forecast(serie::Vector{T}, gas::Model{D, T}, N::Int;
35-
initial_params::Matrix{T} = stationary_initial_params(gas)) where {D, T}
36-
# Filter params estimated on the time series
37-
params = score_driven_recursion(gas, serie; initial_params = initial_params)
43+
initial_params::Matrix{T} = stationary_initial_params(gas),
44+
ci::Vector{T} = T.([0.95]), S::Int = 1000) where {D, T}
3845

39-
biggest_lag = number_of_lags(gas)
40-
41-
params_simulation = params[(end - biggest_lag):(end - 1), :]
42-
# Create scenarios matrix
43-
forec = Vector{T}(undef, N)
44-
sim, param = simulate_recursion(gas, N + biggest_lag; initial_params = params_simulation, update = mean)
45-
forec = sim[biggest_lag + 1:end]
46+
scenarios = simulate(serie, gas, N, S; initial_params = initial_params)
47+
48+
quantiles = get_quantiles(ci)
4649

50+
forec = Matrix{T}(undef, N, length(quantiles) + 1)
51+
52+
forec[:, 1] = mean(scenarios, dims = 2)
53+
for t in 1:N
54+
for (i, q) in enumerate(quantiles)
55+
forec[t, i + 1] = quantile(scenarios[t, :], q)
56+
end
57+
end
58+
4759
return forec
60+
end
61+
62+
function get_quantiles(ci::Vector{T}) where T
63+
@assert all((ci .< 1.0) .& (ci .> 0.0))
64+
quantiles = zeros(2 * length(ci))
65+
i = 1
66+
for v in ci
67+
quantiles[i] = 0.5 + v/2
68+
i += 1
69+
quantiles[i] = 0.5 - v/2
70+
i += 1
71+
end
72+
return quantiles
4873
end

src/univariate_score_driven_recursion.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ return the fitted mean.. #TODO
134134
function fitted_mean(gas::Model{D, T}, observations::Vector{T};
135135
initial_params::Matrix{T} = stationary_initial_params(gas)) where {D, T}
136136
params_fitted = score_driven_recursion(gas, observations; initial_params = initial_params)
137+
138+
# Discard last step of the update
139+
params_fitted = params_fitted[1:end-1, :]
140+
137141
n = size(params_fitted, 1)
138142
fitted_mean = Vector{T}(undef, n)
139143

0 commit comments

Comments
 (0)