Skip to content
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 forecastvar and histvar and minor changes #2

Merged
merged 2 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ LocalProjections = "0.2"
MatrixEquations = "2"
Roots = "1, 2"
StatsAPI = "1.2"
StatsBase = "0.33"
StatsBase = "0.33, 0.34"
Tables = "1"
julia = "1.3"

Expand Down
13 changes: 9 additions & 4 deletions src/AutoregressiveModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ module AutoregressiveModels

using Base: ReshapedArray
using LinearAlgebra: Cholesky, cholesky!, cholesky, UpperTriangular, LowerTriangular,
lu!, ldiv!, rdiv!, inv!, mul!, diagm, eigen!, eigen, I, dot
lu!, ldiv!, rdiv!, inv!, mul!, diagm, eigen!, eigen, I
using MatrixEquations: lyapd
using Random: randn!
using Roots: find_zero, Brent
using StatsAPI: StatisticalModel, RegressionModel
using StatsAPI: RegressionModel
using StatsBase: sample
using Tables
using Tables: getcolumn

import Base: show
import StatsAPI: coef, modelmatrix, residuals, dof_residual, fit
import StatsAPI: response, modelmatrix, coef, residuals, dof_residual, fit

# Reexport objects from StatsAPI
export coef, modelmatrix, residuals, dof_residual, fit
export response, modelmatrix, coef, residuals, dof_residual, fit

export VARProcess,
nvar,
Expand All @@ -28,8 +28,13 @@ export VARProcess,
simulate,
impulse!,
impulse,
forecastvar!,
forecastvar,
histvar!,
histvar,

OLS,
intercept,
coefcorrected,
residvcov,
residchol,
Expand Down
1 change: 1 addition & 0 deletions src/bootstrap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ end
# An unsafe version for bootstrap
function _fitvar!(data::Matrix, m::OLS, nlag, nocons)
i0 = nocons ? 0 : 1
# Y in OLS is left untouched but use resid in-place
Y = residuals(m)
X = modelmatrix(m)
N = size(Y, 2)
Expand Down
22 changes: 15 additions & 7 deletions src/estimation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ struct OLS{TF<:AbstractFloat, TI<:Union{Vector{TF}, Nothing},
TC<:Union{Matrix{TF}, Nothing},
CF<:Union{Cholesky{TF, Matrix{TF}}, Nothing},
CL<:Union{Matrix{TF}, Nothing}} <: RegressionModel
Y::Matrix{TF}
X::Matrix{TF}
crossXcache::Matrix{TF}
coef::Matrix{TF}
Expand All @@ -23,6 +24,7 @@ end

function OLS(Y::AbstractMatrix, X::AbstractMatrix, esample::BitVector, dofr::Int,
nocons::Bool, cholresid::Bool)
Y = convert(Matrix, Y)
X = convert(Matrix, X)
crossX = X'X
coef = ldiv!(cholesky!(crossX), X'Y)
Expand All @@ -34,7 +36,8 @@ function OLS(Y::AbstractMatrix, X::AbstractMatrix, esample::BitVector, dofr::Int
coefB = coef'[:,2:end]
intercept = coef[1,:]
end
resid = mul!(Y, X, coef, -1.0, 1.0)
resid = copy(Y)
resid = mul!(resid, X, coef, -1.0, 1.0)
dofr = max(1, dofr)
residvcov = rdiv!(resid'resid, dofr)
if cholresid
Expand All @@ -47,10 +50,11 @@ function OLS(Y::AbstractMatrix, X::AbstractMatrix, esample::BitVector, dofr::Int
residchol = nothing
residcholL = nothing
end
return OLS(X, crossX, coef, coefB, intercept, nothing, resid, residvcov, residchol,
return OLS(Y, X, crossX, coef, coefB, intercept, nothing, resid, residvcov, residchol,
residcholL, esample, dofr)
end

response(m::OLS) = m.Y
modelmatrix(m::OLS) = m.X
coef(m::OLS) = m.coef
residuals(m::OLS) = m.resid
Expand Down Expand Up @@ -89,21 +93,23 @@ function _fillYX!(Y, X, esampleT, aux, tb, idx, nlag, subset, nocons)
end

"""
VectorAutoregression{TE, HasIntercept} <: StatisticalModel
VectorAutoregression{TE, HasIntercept} <: RegressionModel

Results from vector autoregression estimation.
"""
struct VectorAutoregression{TE, HasIntercept} <: StatisticalModel
struct VectorAutoregression{TE, HasIntercept} <: RegressionModel
est::TE
names::Vector{Symbol}
lookup::Dict{Symbol,Int}
VectorAutoregression(est, names::Vector{Symbol}, lookup::Dict{Symbol,Int},
nocons::Bool) = new{typeof(est), !nocons}(est, names, lookup)
end

response(r::VectorAutoregression) = response(r.est)
modelmatrix(r::VectorAutoregression) = modelmatrix(r.est)
coef(r::VectorAutoregression) = coef(r.est)
residuals(r::VectorAutoregression) = residuals(r.est)
intercept(r::VectorAutoregression) = intercept(r.est)

"""
coefcorrected(r::VectorAutoregression)
Expand Down Expand Up @@ -211,6 +217,8 @@ function fit(::Type{<:VARProcess}, data, names, nlag::Integer;
names = Symbol[_toname(data, n) for n in names]

Tfull = Tables.rowcount(data)
subset === nothing || length(subset) == Tfull ||
throw(ArgumentError("length of subset ($(length(subset))) does not match the number of rows in data ($Tfull)"))
T = Tfull - nlag
Y = Matrix{TF}(undef, T, N)
X = Matrix{TF}(undef, T, nocons ? N*nlag : 1+N*nlag)
Expand Down Expand Up @@ -291,7 +299,7 @@ function impulse!(out::AbstractArray, r::VectorAutoregression,
if choleskyshock
chol = residchol(r)
chol === nothing && throw(ArgumentError(
"cholesky factorization is not taken for r; see the choleskyresid option of fit"))
"Cholesky factorization is not taken for r; see the choleskyresid option of fit"))
# view allocates if array dimension changes
ishock isa Integer && (ishock = ishock:ishock)
ε0 = view(residchol(r), :, ishock)
Expand All @@ -315,7 +323,7 @@ function impulse(r::VectorAutoregression, ishock::Union{Integer, AbstractRange},
if choleskyshock
chol = residchol(r)
chol === nothing && throw(ArgumentError(
"cholesky factorization is not taken for r; see the choleskyresid option of fit"))
"Cholesky factorization is not taken for r; see the choleskyresid option of fit"))
ishock isa Integer && (ishock = ishock:ishock)
ε0 = view(residchol(r), :, ishock)
impulse(VARProcess(coefB(r.est)), ε0, nhorz; nlag=nlag)
Expand Down Expand Up @@ -387,7 +395,7 @@ function biascorrect(r::VectorAutoregression;
coefc = Matrix{Float64}(undef, NP, N)
copyto!(coefc, view(B, 1:N, :)')
m = r.est
olsc = OLS(m.X, m.crossXcache, m.coef, m.coefB, m.intercept, coefc, m.resid,
olsc = OLS(m.Y, m.X, m.crossXcache, m.coef, m.coefB, m.intercept, coefc, m.resid,
m.residvcov, m.residchol, m.residcholL, m.esample, m.dofr)
return VectorAutoregression(olsc, r.names, r.lookup, !hasintercept(r)), δ
end
Expand Down
74 changes: 73 additions & 1 deletion src/process.jl
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ end

const _impulse_common_docstr = """
Same as [`impulse!`](@ref), but allocates an array for storing the results.
The number of horizons needs to be specified explicitely."""
The number of horizons needs to be specified explicitly with `nhorz`."""

"""
impulse(var::VARProcess, ε0::AbstractVecOrMat, nhorz::Integer; kwargs...)
Expand Down Expand Up @@ -253,6 +253,78 @@ function impulse(var::VARProcess, ishock::Union{Integer, AbstractRange}, nhorz::
return out
end

"""
forecastvar!(out::AbstractArray{T,3}, irfs::AbstractArray{T,3})

Compute the variance of forecast error over each horizon
given orthogonalized impulse response coefficients specified with `irfs`
and store the results in an array `out`.
The number of horizons computed is determined by the second dimension of `irfs`
and `out` should have the same size as `irfs`.
See also [`forecastvar`](@ref).
"""
function forecastvar!(out::AbstractArray{T,3}, irfs::AbstractArray{T,3}) where T
size(out) == size(irfs) ||
throw(DimensionMismatch("out and irfs do not have the same size"))
out .= irfs.^2
# cumsum! allocates and is slower
t2 = first(axes(out, 2)) + 1
@inbounds for s in axes(out, 3)
for t in t2:last(axes(out, 2))
for i in axes(out, 1)
out[i,t,s] += out[i,t-1,s]
end
end
end
return out
end

"""
forecastvar(irfs::AbstractArray)

Same as [`forecastvar!`](@ref), but allocates an array for storing the results.
"""
forecastvar(irfs::AbstractArray) = forecastvar!(similar(irfs), irfs)

"""
histvar!(out::AbstractArray{TF,3}, irfs::AbstractArray{TF,3}, shocks::AbstractMatrix)

Compute the variance to be used for historical decomposition
given impulse response coefficients specified with `irfs` and historical `shocks`.
Results are stored in an array `out`.
See also [`histvar`](@ref).
"""
function histvar!(out::AbstractArray{TF,3}, irfs::AbstractArray{TF,3},
shocks::AbstractMatrix) where TF
N, T, S = size(out)
N1, nhorz, S1 = size(irfs)
T2, S2 = size(shocks)
N == N1 || throw(DimensionMismatch("the first dimension of out is expected to be $N1"))
T == T2 || throw(DimensionMismatch("the second dimension of out is expected to be $T2"))
S1 == S2 || throw(DimensionMismatch(
"the numbers of shocks in irfs and shocks do not match"))
S == S1 || throw(DimensionMismatch("the thrid dimension of out is expected to be $S"))
for s in 1:S
for t in 1:T
hs = 1:min(nhorz, t)
bs = t:-1:max(t-nhorz+1, 1)
mul!(view(out,:,t,s), view(irfs,:,hs,s), view(shocks,bs,s))
end
end
return out
end

"""
histvar(irfs::AbstractArray, shocks::AbstractMatrix)

Same as [`histvar!`](@ref), but allocates an array for storing the results.
"""
function histvar(irfs::AbstractArray, shocks::AbstractMatrix)
T, S = size(shocks)
out = similar(irfs, (size(irfs,1), T, S))
return histvar!(out, irfs, shocks)
end

show(io::IO, var::VARProcess) =
print(io, size(var.B,1), '×', size(var.B,2), " ", typeof(var))

Expand Down
11 changes: 11 additions & 0 deletions test/estimation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@test maorder(r) == 0
@test hasintercept(r)
ols = r.est
@test size(response(ols)) == (258, 5)
@test size(modelmatrix(ols)) == (258, 61)
@test size(coef(ols)) == (61, 5)
@test size(residuals(ols)) == (258, 5)
Expand All @@ -26,10 +27,12 @@
0.19390"""
end

@test size(response(r)) == (258, 5)
@test coef(r) == coef(ols)
@test coef(r, 2, 2) == coef(ols)[3,2]
@test coef(r, :ff, :logcpi, 3) == coef(ols)[12,3]
@test coef(r, 2, :constant) == coef(ols)[1,2]
@test intercept(r) == coef(r)[1,:]
@test residvcov(r) == residvcov(ols)
@test residvcov(r, 1) == residvcov(ols)[1]
@test residvcov(r, :logip, 3) == residvcov(ols)[2,3]
Expand Down Expand Up @@ -115,4 +118,12 @@
impulse!(irf2, r1, 3:-1:2, choleskyshock=true)
@test irf2 ≈ irf
@test impulse(r1, 3:-1:2, 13, choleskyshock=true) ≈ irf

sirf = impulse(r1, 1:4, 13, choleskyshock=true)
fev = forecastvar(sirf)
@test fev ≈ cumsum(sirf.^2, dims=2)

shocks = residuals(r1) / residchol(r1)'
hv = histvar(sirf, shocks)
@test sum(view(hv,:,1,:)) ≈ sum(residuals(r1)[1,:])
end
Loading