Estimate Migration Flows to Match Net Totals via Quadratic Optimization
Source:R/net_matrix_optim.R
net_matrix_optim.RdSolves for an origin–destination flow matrix that satisfies directional net migration constraints while minimizing squared deviation from a prior matrix.
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.- maxit
Maximum number of iterations to perform. Default is
500.- tol
Numeric tolerance for checking whether
sum(net_tot) == 0. Default is1e-6.
Value
A named list with components:
nEstimated matrix of flows satisfying the net constraints.
itNumber of optimization iterations (if available).
tolTolerance used for the net flow balance check.
valueObjective function value (sum of squared deviations).
convergenceLogical indicating successful convergence.
messageSolver message or status.
Details
The function minimizes:
$$\sum_{i,j} (y_{ij} - m_{ij})^2$$
subject to directional net flow constraints:
$$\sum_j y_{ji} - \sum_j y_{ij} = \text{net}_i$$
and non-negativity constraints on all flows. Structural zeros are enforced using zero_mask.
Internally uses optim() or a constrained quadratic programming solver.
See also
net_matrix_entropy() for KL divergence minimization,
net_matrix_ipf() for iterative proportional fitting,
and net_matrix_lp() for linear programming with L1 loss.
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_optim(net_tot = net, m = m)
result$n |>
addmargins() |>
round(2)
#> dest
#> orig A B C D Sum
#> A 0.00 90.97 27.22 62.55 180.74
#> B 73.37 0.00 53.30 0.56 127.23
#> C 75.07 32.34 0.00 35.32 142.74
#> D 62.29 43.92 47.22 0.00 153.43
#> Sum 210.74 167.23 127.74 98.43 604.13
sum_region(result$n)
#> # A tibble: 4 × 5
#> region out_mig in_mig turn net
#> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 A 181. 211. 391. 30.0
#> 2 B 127. 167. 294. 40.0
#> 3 C 143. 128. 270. -15.0
#> 4 D 153. 98.4 252. -55.0