In an origin-destination (OD) model, each observation is a flow between two regions. Migration counts between US states, commuting flows between zip codes, or trade flows between countries all fit this pattern.
The random-effects design has two indicators per observation: one for the origin region and one for the destination region. With \(G\) regions, the random-effects matrix \(Z\) is \(N \times 2G\), and every row sums to 2.
The natural covariance structure on the resulting \(2G\)-vector of random effects is the Kronecker form \[ \Sigma_\alpha \;=\; \underbrace{\Sigma_{2\times 2}}_{\text{origin/dest}} \;\otimes\; \underbrace{\Sigma_{\text{spatial}}}_{\text{between regions}}, \] where \(\Sigma_{2\times 2}\) captures the origin/destination variance plus their covariance, and \(\Sigma_{\text{spatial}}\) (\(G \times G\)) captures spatial dependence between regions (often known a priori from geography).
This reduces \(2G(2G+1)/2\) free covariance parameters to just 3 for \(\Sigma_{2\times 2}\) — a substantial saving when \(G\) is moderate.
The package ships a simulated OD migration dataset of \(N = 3000\) observations: \(G = 10\) regions over 30 years, with all \(10 \times 10\) origin–destination pairs observed annually.
path <- system.file("extdata", "od_migration.csv", package = "cevcmm")
od <- read.csv(path)
str(od)
#> 'data.frame': 3000 obs. of 6 variables:
#> $ origin : int 1 1 1 1 1 1 1 1 1 1 ...
#> $ dest : int 1 2 3 4 5 6 7 8 9 10 ...
#> $ year : int 1 1 1 1 1 1 1 1 1 1 ...
#> $ t : num 0 0 0 0 0 0 0 0 0 0 ...
#> $ wage_diff: num 0.208 1.409 -1.489 -1.476 0.991 ...
#> $ log_flow : num 3.75 4.52 3.97 3.01 3.35 ...
head(od)
#> origin dest year t wage_diff log_flow
#> 1 1 1 1 0 0.2082 3.7485
#> 2 1 2 1 0 1.4087 4.5180
#> 3 1 3 1 0 -1.4891 3.9731
#> 4 1 4 1 0 -1.4758 3.0113
#> 5 1 5 1 0 0.9908 3.3466
#> 6 1 6 1 0 -0.8832 5.2626The columns:
origin, dest: integer region IDs in \(\{1, \dots, 10\}\)year: 1 through 30t: year normalised to \([0,
1]\)wage_diff: a continuous covariate whose effect on flows
is allowed to vary smoothly with tlog_flow: the response (log migration count)G <- 10L
N <- nrow(od)
Z <- matrix(0, N, 2L * G)
Z[cbind(seq_len(N), od$origin)] <- 1 # origin indicators in cols 1..G
Z[cbind(seq_len(N), G + od$dest)] <- 1 # destination indicators in cols (G+1)..2G
# Verify the OD structure: every row sums to 2 (one origin + one dest)
table(rowSums(Z))
#>
#> 2
#> 3000In a real analysis \(\Sigma_{\text{spatial}}\) would come from geographic distance. Here we use an exponential decay in region-index distance as a reasonable proxy:
A single vcmm() call with
re_cov = "kronecker". The package detects the constant
row-sum automatically and applies the identifiability shift described in
vignette("distributed-fitting").
fit <- vcmm(y = od$log_flow,
X = od$wage_diff,
Z = Z,
t = od$t,
method = "csl",
re_cov = "kronecker",
n_groups = G,
Sigma_spatial_init = Sigma_spatial,
control = vcmm_control(
sigma_eps = 0.6,
sigma_alpha = sqrt(0.5),
update_variance = TRUE))
fit
#> <vcmm_fit> Varying Coefficient Mixed-Effects Model fit
#> method : CSL
#> n_obs : 3000
#> p (fixed) : 19
#> q (random) : 20
#> RE cov : kronecker
#> pilot iter : 4 (converged)
#> newton step : 1
#> sigma_eps : 0.5991
#> Sigma_2x2 :
#> [0.9444 0.7649]
#> [0.7649 0.8597]
#> OD corr : 0.8489
#> Sigma_spatial: 10 x 10 (G = 10 groups)
#> elapsed : 0.0010 sec (pilot <0.001s + newton <0.001s)Sigma_2x2_hat <- fit$re_cov_state$Sigma_left
round(Sigma_2x2_hat, 3)
#> [,1] [,2]
#> [1,] 0.944 0.765
#> [2,] 0.765 0.860
# Correlation between origin and destination effects
corr_OD <- Sigma_2x2_hat[1, 2] /
sqrt(Sigma_2x2_hat[1, 1] * Sigma_2x2_hat[2, 2])
round(corr_OD, 3)
#> [1] 0.849The diagonal entries give the origin and destination variance components; the off-diagonal entry summarises whether a region that tends to send many migrants also tends to receive many. A positive correlation says yes — high-traffic regions are high-traffic in both directions.
The true simulation values were \(\Sigma_{2\times2} = \begin{pmatrix} 0.60 & 0.25 \\ 0.25 & 0.50 \end{pmatrix}\) (correlation 0.46).
On small \(G\), the estimated \(\hat\Sigma_{2\times 2}\) will differ from truth. The 3 free parameters of \(\Sigma_{2\times 2}\) are estimated from effectively a single \(G \times 2\) realisation of \(M\), plus an EM correction for the posterior uncertainty in \(\alpha\) given the data. For \(G = 10\), sampling variability in the empirical \(\text{cor}(M)\) has standard error \(\approx 1/\sqrt{G-2} \approx 0.35\), and the EM correction inflates the diagonals further to account for posterior uncertainty. The qualitative pattern — positive variance components, positive OD correlation, “high-traffic” regions that send and receive heavily — is the right takeaway at this \(G\); the exact numerical values rely on a larger network or repeated realisations.
The internal column-stacking convention is \(\alpha = \mathrm{vec}_{\text{col}}(M)\) where \(M\) is \(G \times 2\) — the first column holds origin effects, the second holds destination effects.
alpha_hat <- fit$alpha
M_hat <- matrix(alpha_hat, nrow = G, ncol = 2L)
colnames(M_hat) <- c("origin", "dest")
rownames(M_hat) <- paste0("region_", seq_len(G))
round(M_hat, 3)
#> origin dest
#> region_1 0.876 0.629
#> region_2 1.004 1.201
#> region_3 0.450 -0.041
#> region_4 0.109 -0.313
#> region_5 0.494 0.288
#> region_6 0.905 0.677
#> region_7 -0.887 -0.466
#> region_8 -1.426 -0.911
#> region_9 -0.903 -0.254
#> region_10 -0.778 -0.655A quick visual of the two effect series. Notice how
origin and dest track each other closely
across regions — that’s the visual signature of the high empirical OD
correlation discussed above.
matplot(seq_len(G), M_hat, type = "b", pch = 19, lty = 1, lwd = 2,
col = c("steelblue", "darkorange"),
xlab = "Region", ylab = "Random effect")
abline(h = 0, lty = 2, col = "grey60")
legend("topright", c("origin", "dest"),
col = c("steelblue", "darkorange"), pch = 19, lwd = 2, bty = "n")Estimated origin and destination effects per region.
t_grid <- seq(0, 1, length.out = 100L)
vc <- varying_coef(fit, t_new = t_grid, k = 1L, se.fit = TRUE)
plot(t_grid, vc$fit, type = "l", lwd = 2, col = "steelblue",
xlab = "t (year, normalised)",
ylab = expression(hat(beta)[1](t)),
ylim = range(vc$fit - 2 * vc$se.fit, vc$fit + 2 * vc$se.fit,
1.5 * sin(2 * pi * t_grid)))
polygon(c(t_grid, rev(t_grid)),
c(vc$fit + 2 * vc$se.fit, rev(vc$fit - 2 * vc$se.fit)),
col = adjustcolor("steelblue", alpha.f = 0.25), border = NA)
lines(t_grid, 1.5 * sin(2 * pi * t_grid),
col = "red", lty = 2, lwd = 2)
legend("topright", c("estimate", "truth"),
col = c("steelblue", "red"), lty = c(1, 2), lwd = 2, bty = "n")Estimated time-varying effect of wage_diff on log flow.
The wage-difference effect oscillates with time: in the simulation it follows \(1.5\sin(2\pi t)\). Unlike \(\Sigma_{2\times 2}\), the varying-coefficient curve is supported by all \(N = 3000\) observations and tracks the truth essentially perfectly across the 30-year window.
The same OD problem can be fit in the distributed setting. Each node
computes a node_summary() on its slice and ships the
result; the central node aggregates and calls
fit_from_summaries() with
rowsum_constant = 2 so the identifiability
shift matches what vcmm() applies automatically:
# Split N obs across 3 nodes
node_id <- sample.int(3L, N, replace = TRUE)
splits <- split(seq_len(N), node_id)
design <- build_vcmm_design(X = od$wage_diff, t = od$t)
X_d <- design$X_design
summaries <- lapply(splits, function(idx)
node_summary(od$log_flow[idx],
X_d[idx, , drop = FALSE],
Z[idx, , drop = FALSE]))
fit_dist <- fit_from_summaries(
summaries,
penalty = design$penalty,
control = vcmm_control(sigma_eps = 0.6,
sigma_alpha = sqrt(0.5),
update_variance = TRUE),
method = "csl",
re_cov = "kronecker",
n_groups = G,
Sigma_spatial_init = Sigma_spatial,
rowsum_constant = 2
)
# Same answer as the pooled fit above, up to BLAS noise
max(abs(fit$beta - fit_dist$beta))
max(abs(fit$alpha - fit_dist$alpha))See vignette("distributed-fitting", package = "cevcmm")
for the full explanation of the distributed API.
vignette("getting-started", package = "cevcmm").vignette("distributed-fitting", package = "cevcmm").Jalili, L. and Lin, L.-H. (2025). Scalable and Communication-Efficient Varying Coefficient Mixed Effect Models: Methodology, Theory, and Applications. arXiv:2511.12732; under review at Journal of the American Statistical Association.