Estimate Migration Flows to Match Net Totals via Linear Programming
Source:R/net_matrix_lp.R
net_matrix_lp.Rd
Solves for an origin-destination flow matrix that satisfies directional net migration constraints while minimizing the total absolute deviation from a prior matrix. This method uses linear programming with split variables to minimize L1 error, optionally respecting a structural zero mask.
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
, whereTRUE
indicates forbidden (structurally zero) flows. Defaults to disallowing diagonal flows.- tol
A numeric tolerance for checking that
sum(net_tot) == 0
. Default is1e-6
.
Value
A named list with components:
n
Estimated matrix of flows satisfying the net constraints.
it
Number of iterations (always
1
for LP method).tol
Tolerance used for checking net flow balance.
value
Total L1 deviation from prior matrix
m
.convergence
Logical indicator of successful solve.
message
Text summary of convergence status.
Details
This function uses lpSolve::lp()
to solve a linear program. The estimated matrix minimizes the sum of absolute deviations from the prior matrix m
, subject to directional net flow constraints:
$$\sum_j x_{ji} - \sum_j x_{ij} = \text{net}_i$$
Structural zeros are enforced by the zero_mask
. All flows are constrained to be non-negative.
See also
net_matrix_entropy()
for KL divergence minimization,
net_matrix_ipf()
for iterative proportional fitting (IPF),
and net_matrix_optim()
for least-squares (L2) flow estimation.
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_lp(net_tot = net, m = m)
result$n |>
addmargins() |>
round(2)
#> dest
#> orig A B C D Sum
#> A 0 0 0 0 0
#> B 0 0 0 0 0
#> C 0 15 0 0 15
#> D 30 25 0 0 55
#> Sum 30 40 0 0 70
sum_region(result$n)
#> # A tibble: 4 × 5
#> region out_mig in_mig turn net
#> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 A 0 30 30 30
#> 2 B 0 40 40 40
#> 3 C 15 0 15 -15
#> 4 D 55 0 55 -55