Introduction to Transmission Channel Analysis

Muhammad Alkhalaf

2026-10-01

Overview

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:

  1. Converting the VAR into a systems form representation.
  2. Selectively blocking transmission paths in the systems form DAG.
  3. Computing the difference between total and blocked effects.

Quick Start

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
#> ----------------------------------------------------------------------

Decomposition Modes

Overlapping

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
#> ----------------------------------------------------------------------

Exhaustive 3-Way

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
#> ----------------------------------------------------------------------

Exhaustive 4-Way

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
#> ----------------------------------------------------------------------

Plotting

plot_tca(res_4w, target = 3, type = "bar")

plot_tca(res_ov, target = 3, type = "line")

Validating Additivity

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-16

Using with the vars Package

If you have estimated a VAR using vars::VAR(), you can use the convenience wrapper:

library(vars)
data(Canada)
var_est <- VAR(Canada, p = 2, type = "const")
result <- tca_from_var(var_est, from = "e",
                        intermediates = c("prod", "rw"),
                        h = 20, mode = "exhaustive_4way")
plot_tca(result, target = "U")

Reference

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