-
-
Notifications
You must be signed in to change notification settings - Fork 304
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add filer and smoothing for regime switching model #250
base: master
Are you sure you want to change the base?
Changes from 15 commits
134b51b
bbe317d
cf44482
434c4d8
83f0401
42fdb18
fe1c914
1fa42bd
b50f6b1
eefba9c
9e0e884
9be82ac
9639d93
666e265
6fce054
99d5ca5
601bdf8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -85,3 +85,206 @@ function hamilton_filter(y::AbstractVector, h::Integer) | |
y_trend = y - y_cycle | ||
return y_cycle, y_trend | ||
end | ||
|
||
""" | ||
- `g::Function`: Parameterized `Function` that recieves three arguments | ||
(data at each period, state at each period, parameter) and return the density. | ||
For example, if the data `x <: Real` is generated by `N(mu_s, sigma_s)` where mean `mu` and std `sigma` is | ||
state dependent, `g` should be defined as | ||
``` | ||
g(x, s, parameter) = exp(-((x-parameter.mu[s])^2)/(2*parameter.sigma[s]^2))/sqrt(2*pi*parameter.sigma[s]^2) | ||
``` | ||
- `y::AbstractArray`: Collection of data whose row corresponds to periods | ||
- `parameter`: Collection of parameters in the model. Each parameter must be a vector that stores the values for each state. | ||
For example, if you want to set `mu` at state 1 is 0 and `mu` at state 2 is 0.5 and | ||
`sigma` at state 1 is 3 and `sigma` at state 2 is 4, `parameter` should be | ||
``` | ||
parameter = (mu = [0, 0.5], sigma = [3, 4]) | ||
``` | ||
Alternatively, you can define your own type and use it: | ||
``` | ||
struct ParaType | ||
mu::Vector | ||
sigma::Vector | ||
end | ||
parameter = ParaType([0, 0.5], [3, 4]) | ||
``` | ||
- `P::AbstractMatrix`: Transition matrix of state. It must be square. | ||
- `M::Integer`: Number of states. It must be same as the number of rows and columns of `P`. If `M` is not passed, | ||
number of rows of `P` is set as `M`. | ||
""" | ||
mutable struct RegimeSwitchingModel{TD <: AbstractArray, TP <: AbstractMatrix, TPS <: AbstractMatrix} | ||
g::Function | ||
y::TD | ||
parameter | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason that we don't want this typed? By having a mutable structure and not having this argument typed, we can change it to anything we want -- For example, I can set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Kind of, I thought some may want to use user-defined There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perfect -- This is a use case Julia should handle very cleanly! You can add a type parameter to mutable struct RegimeSwitchingModel{TD <: AbstractArray, TP <: AbstractMatrix, TPS <: AbstractMatrix, TPRM}
g::Function
y::TD
parameter::TPRM
... This will allow the functions to specialize to the user's type (there will be a new type of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I see, that makes sense and is way better. Thank you!! |
||
P::TP | ||
M::Int | ||
prob_smoothed::TPS | ||
logL::Float64 | ||
function RegimeSwitchingModel(g, y, parameter, P, M, prob_smoothed, logL) | ||
if size(P, 1) != size(P, 2) | ||
error("P must be square") | ||
end | ||
if size(P, 1) != M | ||
error("the number of rows and columns of P must be equal to M") | ||
end | ||
return new{typeof(y), typeof(P), typeof(prob_smoothed)}(g, y, parameter, P, M, prob_smoothed, logL) | ||
end | ||
end | ||
RegimeSwitchingModel(g::Function, y::AbstractArray, parameter, P::AbstractMatrix) = | ||
RegimeSwitchingModel(g, y, parameter, P, size(P, 1), fill(NaN, size(y, 1), size(P, 1)), NaN) | ||
|
||
""" | ||
Apply the filter developed by Hamilton (1989, Econometrica) to a regime switching model. | ||
|
||
##### Arguments | ||
- `rsm::RegimeSwitchingModel`: `RegimeSwitchingModel` | ||
- `prob_update_pre::AbstractArray`: Probability distribution of state at period 0 conditional on the information up to | ||
period 0. | ||
|
||
##### Returns | ||
- `p`: likelihood at each period | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It wouldn't hurt to emphasize that the returns are each of size There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's fair. I'll do so. |
||
- `p_s_update`: Probability of period `t` data conditional on the information up to period `t`. | ||
- `p_s_forecast`: Probability of period `t` data conditional on the information up to period `t-1`. | ||
""" | ||
function filter!(rsm::RegimeSwitchingModel; | ||
prob_update_pre::AbstractArray = stationary_distributions(MarkovChain(rsm.P))[1]) | ||
T = size(rsm.y, 1) | ||
p_s_forecast = Matrix{Float64}(undef, T, rsm.M) | ||
p_s_update = Matrix{Float64}(undef, T, rsm.M) | ||
p, _, _ = filter!(rsm, p_s_update, p_s_forecast, prob_update_pre = prob_update_pre) | ||
return p, p_s_update, p_s_forecast | ||
end | ||
|
||
""" | ||
Apply the filter developed by Hamilton (1989, Econometrica) to a regime switching model. | ||
|
||
##### Arguments | ||
- `rsm::RegimeSwitchingModel`: `RegimeSwitchingModel` specifying the model | ||
- `p_s_update::AbstractMatrix`: Probability of period `t` data conditional on the information up to period `t`. | ||
- `p_s_forecast::AbstractMatrix`: Probability of period `t` data conditional on the information up to period `t-1`. | ||
- `prob_update_pre::AbstractArray`: Probability distribution of state at period 0 conditional on the information up to | ||
period 0. | ||
|
||
##### Returns | ||
- `p`: Likelihood at each period. | ||
- `p_s_update`: Probability of period `t` data conditional on the information up to period `t`. | ||
- `p_s_forecast`: Probability of period `t` data conditional on the information up to period `t-1`. | ||
""" | ||
function filter!(rsm::RegimeSwitchingModel, | ||
p_s_update::AbstractMatrix, p_s_forecast::AbstractMatrix; | ||
prob_update_pre::AbstractArray = stationary_distributions(MarkovChain(rsm.P))[1]) | ||
g, y = rsm.g, rsm.y | ||
T = size(y, 1) | ||
p = Vector{Float64}(undef, T) | ||
logL = 0 | ||
for t in 1:T | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like how the code is structured that allows you to write such a simple (and beautiful!) for-loop to do the core of the computation. |
||
forecast!(view(p_s_forecast, t, :), prob_update_pre, rsm.P) | ||
p[t] = update!(view(p_s_update, t, :), rsm, p_s_forecast[t, :], t) | ||
logL += log(p[t]) | ||
prob_update_pre .= p_s_update[t, :] | ||
end | ||
rsm.logL = logL | ||
return p, p_s_update, p_s_forecast | ||
end | ||
|
||
""" | ||
Given the state distribution of period `t-1` conditional on the information up to period `t-1`, | ||
forecast the distribution of period `t`. | ||
|
||
##### Arguments | ||
- `p_s_forecast::AbstractVector`: Probability distribution of state at period `t` conditional on the | ||
information up to period `t-1`. | ||
- `p_s_update_pre::AbstractArray`: Probability distribution of state at period `t-1` conditional on the | ||
information up to period `t-1`. | ||
- `P::AbstractMatrix`: Transition matrix of state. | ||
|
||
#### Return | ||
- `nothing` | ||
""" | ||
function forecast!(p_s_forecast::AbstractVector, p_s_update_pre::AbstractArray, P::AbstractMatrix) | ||
p_s_forecast .= P' * p_s_update_pre | ||
return nothing | ||
end | ||
|
||
""" | ||
Given the state distribution of period `t` conditional on the information up to period `t-1`, | ||
update the distribution using the infomration at period `t`. | ||
|
||
##### Arguments | ||
- `p_s_update::AbstractVector`: Probability distribution of state at period `t` conditional on the | ||
information up to period `t`. | ||
- `rsm::RegimeSwitchingModel`: `RegimeSwitchingModel` that specifies the model. | ||
- `p_s_forecast::Array`: Probability distribution of state at period `t` conditional on the | ||
information up to period `t-1`. | ||
- `t::Integer`: Period. | ||
|
||
#### Returns | ||
- `p`: Likelihood of data at period `t`. | ||
""" | ||
function update!(p_s_update::AbstractVector, rsm::RegimeSwitchingModel, p_s_forecast::AbstractArray, t::Integer) | ||
y_at_period_t = get_y_at_period_t(rsm.y, t) | ||
eta = [max(0, rsm.g(y_at_period_t, s, rsm.parameter)) for s in 1:rsm.M] | ||
tmp = eta .* p_s_forecast | ||
p = sum(tmp) | ||
p_s_update .= tmp./p | ||
return p | ||
end | ||
get_y_at_period_t(y::AbstractMatrix, t::Union{Integer, AbstractVector}) = y[t, :] | ||
get_y_at_period_t(y::AbstractVector, t::Union{Integer, AbstractVector}) = y[t] | ||
|
||
""" | ||
Apply the backward smoother developed by Kim to a regime switching model. | ||
|
||
##### Arguments | ||
- `rsm::RegimeSwitchingModel`: `RegimeSwitchingModel` specifying the model. | ||
- `prob_update_pre::AbstractArray`: Probability distribution of state at period 0 conditional on the information up to | ||
period 0. | ||
- `verbose::Bool`: If `true`, the funciton returns joint distribution. If `false`, `nothing` is returned. | ||
|
||
##### Returns (only when `verbose` is `true`) | ||
- `p_s_joint_smoothed`: Joint distribution of state at period `t` and `t+1` data conditional on the all information. | ||
""" | ||
function smooth!(rsm::RegimeSwitchingModel; | ||
prob_update_pre::AbstractArray = stationary_distributions(MarkovChain(rsm.P))[1], | ||
verbose::Bool=false) | ||
p_s_joint_smoothed = Array{Float64, 3}(undef, size(rsm.y, 1), rsm.M, rsm.M) | ||
smooth!(rsm, p_s_joint_smoothed, prob_update_pre = prob_update_pre) | ||
verbose ? (return p_s_joint_smoothed) : (return nothing) | ||
end | ||
|
||
""" | ||
Apply the backward smoother developed by Kim to a regime switching model. | ||
|
||
Same as `smooth` except that the result is stored in the perallocated first argument. | ||
|
||
##### Arguments | ||
- `rsm::RegimeSwitchingModel`: `RegimeSwitchingModel` specifying the model. | ||
- `p_s_joint_smoothed`: `T x n_state x n_state` | ||
- `prob_update_pre::AbstractArray`: Probability distribution of state at period 0 conditional on the information up to | ||
period 0. | ||
|
||
##### Returns | ||
- `nothing` | ||
""" | ||
function smooth!(rsm::RegimeSwitchingModel, | ||
p_s_joint_smoothed::AbstractArray; | ||
prob_update_pre::AbstractArray = stationary_distributions(MarkovChain(rsm.P))[1], | ||
verbose::Bool=false) | ||
y = rsm.y | ||
M = rsm.M | ||
T = size(y, 1) | ||
rsm_tmp = RegimeSwitchingModel(rsm.g, y, rsm.parameter, rsm.P) | ||
ps, p_s_update, p_s_forecast = filter!(rsm_tmp, prob_update_pre = prob_update_pre) | ||
p_s_init = Vector{Float64}(undef, M) | ||
rsm.prob_smoothed[T, :] = p_s_update[T, :] | ||
for t = T-1:-1:1 | ||
for s in 1:M | ||
for sp in 1:M | ||
p_s_joint_smoothed[t, s, sp] = rsm.prob_smoothed[t+1, sp] * p_s_update[t, s] * rsm.P[s, sp]/p_s_forecast[t+1, sp] | ||
end | ||
rsm.prob_smoothed[t, s] = sum(p_s_joint_smoothed[t, s, :]) | ||
end | ||
end | ||
verbose ? (return p_s_joint_smoothed) : (return nothing) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this if statement in return cause confusion to the compiler? This could prevent it from inferring the type of the output of this function. Is there a reason to not just return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it does create a problem -- It identifies it as a union of nothing and the array type:
It does add "complexity" relative to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No, when I realized |
||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should break these lines a little more frequently than they're currently broken -- I don't remember what the maximum number of characters per line we decided to enforce for Jula though.
@sglyon should remember.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cc7768 It totally slipped my mind. I think it is 80.