Estimate Migration Flows to Match Net Totals via Entropy Minimization
Source:R/net_matrix_entropy.R
net_matrix_entropy.RdSolves for an origin–destination flow matrix that satisfies directional net migration constraints while minimizing Kullback–Leibler (KL) divergence from a prior matrix. This yields a smooth, information-theoretically regularized solution that balances fidelity to prior patterns with net flow requirements.
Arguments
- net_tot
A numeric vector of net migration totals for each region. Must sum to zero.
- m
A square numeric matrix providing prior flow estimates. Must have dimensions
length(net_tot) × length(net_tot).- zero_mask
A logical matrix of the same dimensions as
m, whereTRUEindicates forbidden (structurally zero) flows. Defaults to disallowing diagonal flows.- tol
Numeric tolerance for checking whether
sum(net_tot) == 0. Default is1e-6.- verbose
Logical flag to print solver diagnostics from
CVXR. Default isFALSE.
Value
A named list with components:
nEstimated matrix of flows satisfying the net constraints.
itNumber of iterations (always
1for this solver).tolTolerance used for the net flow balance check.
valueSum of squared deviation from target net flows.
convergenceLogical indicating successful optimization.
messageSolver message returned by
CVXR.
Details
This function minimizes the KL divergence between the estimated matrix \(y_{ij}\) and the prior matrix \(m_{ij}\):
$$\sum_{i,j} \left[y_{ij} \log\left(\frac{y_{ij}}{m_{ij}}\right) - y_{ij} + m_{ij}\right]$$
subject to directional net flow constraints:
$$\sum_j y_{ji} - \sum_j y_{ij} = \text{net}_i$$
All flows are constrained to be non-negative. Structural zeros are enforced via zero_mask.
Internally uses CVXR::kl_div() for DCP-compliant KL minimization.
See also
net_matrix_lp() for linear programming using L1 loss,
net_matrix_ipf() for iterative proportional fitting with multiplicative scaling,
and net_matrix_optim() for quadratic loss minimization.
Examples
m <- matrix(c(0, 100, 30, 70,
50, 0, 45, 5,
60, 35, 0, 40,
20, 25, 20, 0),
nrow = 4, byrow = TRUE,
dimnames = list(orig = LETTERS[1:4], dest = LETTERS[1:4]))
addmargins(m)
#> dest
#> orig A B C D Sum
#> A 0 100 30 70 200
#> B 50 0 45 5 100
#> C 60 35 0 40 135
#> D 20 25 20 0 65
#> Sum 130 160 95 115 500
sum_region(m)
#> # A tibble: 4 × 5
#> region out_mig in_mig turn net
#> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 A 200 130 330 -70
#> 2 B 100 160 260 60
#> 3 C 135 95 230 -40
#> 4 D 65 115 180 50
net <- c(30, 40, -15, -55)
result <- net_matrix_entropy(net_tot = net, m = m)
result$n |>
addmargins() |>
round(2)
#> dest
#> orig A B C D Sum
#> A 0.00 80.58 26.05 34.78 141.41
#> B 62.05 0.00 48.48 3.08 113.62
#> C 69.11 32.48 0.00 22.89 124.48
#> D 40.25 40.55 34.95 0.00 115.75
#> Sum 171.41 153.62 109.48 60.75 495.26
sum_region(result$n)
#> # A tibble: 4 × 5
#> region out_mig in_mig turn net
#> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 A 141. 171. 313. 30.0
#> 2 B 114. 154. 267. 40.0
#> 3 C 124. 109. 234. -15.0
#> 4 D 116. 60.8 177. -55.0