The SVARtca package implements Transmission Channel Analysis for structural vector autoregressive (SVAR) models, following the methodology of Wegner, Lieb, Smeekes and Wilms (2025).
TCA answers the question: How much of a shockâs total effect on a target variable passes through a particular intermediate variable (channel)?
The method works by:
library(SVARtca)
# Define a 4-variable VAR(1) model
K <- 4
A1 <- matrix(c( 0.7, -0.1, 0.05, -0.05,
-0.3, 0.6, 0.10, -0.10,
-0.2, 0.1, 0.70, 0.05,
-0.1, 0.2, 0.05, 0.65), K, K, byrow = TRUE)
Sigma <- matrix(c(1.00, 0.30, 0.20, 0.10,
0.30, 1.50, 0.25, 0.15,
0.20, 0.25, 0.80, 0.10,
0.10, 0.15, 0.10, 0.60), K, K, byrow = TRUE)
Phi0 <- t(chol(Sigma))
var_names <- c("IntRate", "GDP", "Inflation", "Wages")
# Step 1: Build systems form
sf <- tca_systems_form(Phi0, list(A1), h = 20)
# Step 2: Run TCA
result <- tca_analyze(
from = 1,
B = sf$B,
Omega = sf$Omega,
intermediates = c(2, 4),
K = K,
h = 20,
order = 1:K,
mode = "exhaustive_4way",
var_names = var_names
)
print(result, target = 3)
#>
#> TCA Results (mode: exhaustive_4way)
#> Shock from: IntRate | Horizon: 20
#> Response variable: Inflation
#> ----------------------------------------------------------------------
#> h | Total | GDP only | Wages only | GDP & Wages | Direct
#> ----------------------------------------------------------------------
#> 0 | 0.200000 | 0.040426 | 0.000000 | 0.000000 | 0.159574
#> 1 | -0.025000 | 0.020023 | 0.004727 | 0.000273 | -0.050023
#> 2 | -0.161750 | -0.019683 | 0.002645 | 0.002605 | -0.147317
#> 4 | -0.279529 | -0.094721 | -0.007391 | -0.000064 | -0.177354
#> 8 | -0.253949 | -0.138419 | -0.016737 | -0.015861 | -0.082932
#> 12 | -0.158588 | -0.101321 | -0.011863 | -0.020979 | -0.024425
#> 16 | -0.086618 | -0.057959 | -0.005694 | -0.017520 | -0.005445
#> 20 | -0.044372 | -0.029408 | -0.002182 | -0.011907 | -0.000876
#> ----------------------------------------------------------------------Each channel is defined as
through(j) = total - not_through(j). Channels may overlap,
so their sum can differ from the total.
res_ov <- tca_analyze(
from = 1, B = sf$B, Omega = sf$Omega,
intermediates = c(2, 4), K = K, h = 20,
order = 1:K, mode = "overlapping", var_names = var_names
)
print(res_ov, target = 3)
#>
#> TCA Results (mode: overlapping)
#> Shock from: IntRate | Horizon: 20
#> Response variable: Inflation
#> ----------------------------------------------------------------------
#> h | Total | Through GDP | Through Wage | Direct
#> ----------------------------------------------------------------------
#> 0 | 0.200000 | 0.040426 | 0.000000 | 0.159574
#> 1 | -0.025000 | 0.020296 | 0.005000 | -0.050023
#> 2 | -0.161750 | -0.017078 | 0.005250 | -0.147317
#> 4 | -0.279529 | -0.094784 | -0.007454 | -0.177354
#> 8 | -0.253949 | -0.154280 | -0.032598 | -0.082932
#> 12 | -0.158588 | -0.122300 | -0.032842 | -0.024425
#> 16 | -0.086618 | -0.075479 | -0.023214 | -0.005445
#> 20 | -0.044372 | -0.041314 | -0.014088 | -0.000876
#> ----------------------------------------------------------------------Non-overlapping decomposition into: (1) through var1 inclusive, (2) through var2 only, (3) direct. The sum equals the total.
res_3w <- tca_analyze(
from = 1, B = sf$B, Omega = sf$Omega,
intermediates = c(2, 4), K = K, h = 20,
order = 1:K, mode = "exhaustive_3way", var_names = var_names
)
print(res_3w, target = 3)
#>
#> TCA Results (mode: exhaustive_3way)
#> Shock from: IntRate | Horizon: 20
#> Response variable: Inflation
#> ----------------------------------------------------------------------
#> h | Total | Through GDP | Through Wage | Direct
#> ----------------------------------------------------------------------
#> 0 | 0.200000 | 0.040426 | 0.000000 | 0.159574
#> 1 | -0.025000 | 0.020296 | 0.004727 | -0.050023
#> 2 | -0.161750 | -0.017078 | 0.002645 | -0.147317
#> 4 | -0.279529 | -0.094784 | -0.007391 | -0.177354
#> 8 | -0.253949 | -0.154280 | -0.016737 | -0.082932
#> 12 | -0.158588 | -0.122300 | -0.011863 | -0.024425
#> 16 | -0.086618 | -0.075479 | -0.005694 | -0.005445
#> 20 | -0.044372 | -0.041314 | -0.002182 | -0.000876
#> ----------------------------------------------------------------------Full inclusion-exclusion: (1) var1 only, (2) var2 only, (3) both, (4) direct. The sum equals the total.
res_4w <- tca_analyze(
from = 1, B = sf$B, Omega = sf$Omega,
intermediates = c(2, 4), K = K, h = 20,
order = 1:K, mode = "exhaustive_4way", var_names = var_names
)
print(res_4w, target = 3)
#>
#> TCA Results (mode: exhaustive_4way)
#> Shock from: IntRate | Horizon: 20
#> Response variable: Inflation
#> ----------------------------------------------------------------------
#> h | Total | GDP only | Wages only | GDP & Wages | Direct
#> ----------------------------------------------------------------------
#> 0 | 0.200000 | 0.040426 | 0.000000 | 0.000000 | 0.159574
#> 1 | -0.025000 | 0.020023 | 0.004727 | 0.000273 | -0.050023
#> 2 | -0.161750 | -0.019683 | 0.002645 | 0.002605 | -0.147317
#> 4 | -0.279529 | -0.094721 | -0.007391 | -0.000064 | -0.177354
#> 8 | -0.253949 | -0.138419 | -0.016737 | -0.015861 | -0.082932
#> 12 | -0.158588 | -0.101321 | -0.011863 | -0.020979 | -0.024425
#> 16 | -0.086618 | -0.057959 | -0.005694 | -0.017520 | -0.005445
#> 20 | -0.044372 | -0.029408 | -0.002182 | -0.011907 | -0.000876
#> ----------------------------------------------------------------------tca_validate_additivity() checks Theorem 2(ii) of the
paper: for every variable j, the effect of the paths
through j is recomputed with AND conditions (partitioning
the paths by the first node of j they visit), added to the
not-through effect and compared with the total effect. The residual is
of the order of machine precision for a valid systems form. With
pair, the inclusion-exclusion identity behind the 4-way
mode is checked in the same way (this needs (h+1)^2 linear
solves, so a shorter horizon is used here):
tca_validate_additivity(
from = 1, B = sf$B, Omega = sf$Omega,
K = K, h = 20, order = 1:K, var_names = var_names
)
#> ===== Binary Additivity Test =====
#> IntRate: max |total - (through + not_through)| = 1.39e-17
#> GDP: max |total - (through + not_through)| = 1.11e-16
#> Inflation: max |total - (through + not_through)| = 1.11e-16
#> Wages: max |total - (through + not_through)| = 1.11e-16
#>
#> B strictly lower triangular: yes
#> Overall max |residual| = 1.11e-16 (tolerance 1.0e-10)
#> PASSED
sf8 <- tca_systems_form(Phi0, list(A1), h = 8, order = 1:K)
ok <- tca_validate_additivity(
from = 1, B = sf8$B, Omega = sf8$Omega,
K = K, h = 8, order = 1:K, var_names = var_names, pair = c(2, 4),
verbose = FALSE
)
attr(ok, "pair_residual")
#> [1] 1.027824e-16If you have estimated a VAR using vars::VAR(), you can
use the convenience wrapper:
Wegner, E., Lieb, L., Smeekes, S. and Wilms, I. (2025). Transmission Channel Analysis in Dynamic Models. arXiv:2405.18987. https://doi.org/10.48550/arXiv.2405.18987