netmatchRI provides network-constrained matching and
randomization-based inference for observational network data.
Dual-penalty matching minimizes total covariate dissimilarity while
enforcing penalties on network proximity between and within treatment
arms in matched sets. Randomization-based inference combines evidence
from correlated matched sets based on this design.
The data frame should contain one row per unit, a binary treatment, baseline covariates, and an outcome. The network may be supplied as an adjacency or graph-distance matrix, with rows and columns in the same order as the data. The bundled example contains 300 units simulated under no treatment effect and network dependence. It was selected to illustrate how accounting for dependence can change the inferential conclusion.
Construct a dual-penalty design with kappa = 2, so that
every pair of units within a matched set is separated by a network
distance greater than two. HiGHS is the default open-source solver;
Gurobi and GLPK can be selected with the solver
argument.
The summary reports matched sample sizes, effective sample sizes,
matched-set sizes, balance, network distances, and solver status.
diagnose_match() checks covariate balance and network
distance within matched sets.
summary(m)
#> Matched design summary
#>
#> Sample counts
#> group original matched unmatched
#> overall 300 300 0
#> treated 64 64 0
#> control 236 236 0
#>
#> Effective sample size
#> group ess
#> overall 218.3393
#> treated 64.0000
#> control 159.9529
#>
#> Matched-set sizes
#> subclass n nt nc
#> 1 4 1 3
#> 2 4 1 3
#> 3 7 1 6
#> 4 3 1 2
#> 5 5 1 4
#> 6 8 1 7
#> 7 4 1 3
#> 8 9 1 8
#> 9 3 1 2
#> 10 7 1 6
#> 11 6 1 5
#> 12 4 1 3
#> 13 5 1 4
#> 14 7 1 6
#> 15 5 1 4
#> 16 5 1 4
#> 17 2 1 1
#> 18 3 1 2
#> 19 6 1 5
#> 20 3 1 2
#> 21 7 1 6
#> 22 6 1 5
#> 23 2 1 1
#> 24 8 1 7
#> 25 5 1 4
#> 26 6 1 5
#> 27 3 1 2
#> 28 4 1 3
#> 29 2 1 1
#> 30 5 1 4
#> 31 13 1 12
#> 32 5 1 4
#> 33 5 1 4
#> 34 2 1 1
#> 35 4 1 3
#> 36 10 1 9
#> 37 2 1 1
#> 38 3 1 2
#> 39 3 1 2
#> 40 3 1 2
#> 41 4 1 3
#> 42 2 1 1
#> 43 7 1 6
#> 44 4 1 3
#> 45 4 1 3
#> 46 3 1 2
#> 47 3 1 2
#> 48 5 1 4
#> 49 4 1 3
#> 50 5 1 4
#> 51 4 1 3
#> 52 2 1 1
#> 53 3 1 2
#> 54 8 1 7
#> 55 7 1 6
#> 56 8 1 7
#> 57 3 1 2
#> 58 4 1 3
#> 59 2 1 1
#> 60 2 1 1
#> 61 5 1 4
#> 62 7 1 6
#> 63 3 1 2
#> 64 3 1 2
#>
#> Covariate balance (absolute SMD)
#> covariate before_abs_smd after_abs_smd
#> X1 0.19125705 0.08900321
#> X2 0.07108809 0.05751692
#> X3 0.23389449 0.06847942
#>
#> Network summary
#> n_pairs n_finite_pairs n_disconnected_pairs min_distance mean_distance
#> 711 675 36 3 4.30963
#> max_distance
#> 8
#>
#> Solver: highs (OPTIMAL)
diag <- diagnose_match(m)
diag$covariate_balance
#> covariate before_abs_smd after_abs_smd
#> 1 X1 0.19125705 0.08900321
#> 2 X2 0.07108809 0.05751692
#> 3 X3 0.23389449 0.06847942
diag$network_summary
#> n_pairs n_finite_pairs n_disconnected_pairs min_distance mean_distance
#> 1 711 675 36 3 4.30963
#> max_distance
#> 1 8Pass all three matching designs as a named list. The two plots
compare covariate balance and covariate dissimilarity across these
designs. Set include_before = FALSE to omit the original
unmatched sample.
m_covariate <- netmatch(
dat, "Z", c("X1", "X2", "X3"), net_dist,
method = "covariate"
)
m_single <- netmatch(
dat, "Z", c("X1", "X2", "X3"), net_dist,
method = "single", kappa = 2
)
designs <- list(
"Covariate-only" = m_covariate,
"Single-penalty" = m_single,
"Dual-penalty" = m
)
plot_covariate_balance(designs, include_before = TRUE)We use the dual-penalty design m to test the sharp null
that no unit’s outcome is affected by any unit’s treatment. This
includes both direct effects and treatment interference. The tests
combine matched-set Mann–Whitney U statistics and calculate two-sided
p-values using a normal approximation.
RI_unadjusted() ignores covariance across matched sets.
RI_design() uses the design-based covariance bounds, while
RI_adjusted() scales these bounds using the specified
eta and rho. These parameters describe assumed
bounds on covariance, rather than estimates of covariance from the
data.
ri_unadjusted <- RI_unadjusted(m, "Y")
ri_design <- RI_design(m, "Y")
ri_adjusted <- RI_adjusted(m, "Y", eta = 0.05, rho = 0.20)
p_values <- data.frame(
inference = c("Unadjusted", "Design-based", "Adjusted (eta = 0.05, rho = 0.20)"),
p_value = c(ri_unadjusted$result$p_value,
ri_design$result$p_value,
ri_adjusted$result$p_value)
)
p_values
#> inference p_value
#> 1 Unadjusted 0.00517865
#> 2 Design-based 0.67130599
#> 3 Adjusted (eta = 0.05, rho = 0.20) 0.05072345Evaluate the p-value over a grid of eta and rho values, and compute
the critical curve for a chosen test level. Both plots below use the
dual-penalty design m; the dashed black line in the p-value
plot is its unadjusted p-value, and the dotted red line marks the 0.05
test level.
sens <- sensitivity_grid(
m,
"Y",
eta = c(0, 0.01, 0.05, 0.10, 0.15, 0.20),
rho = seq(0, 0.5, by = 0.05)
)
plot_sensitivity(sens)
crit <- critical_sensitivity(m, "Y", rho = seq(0, 0.5, by = 0.01))
crit
#> Critical eta (rho = 1): 0.0244
#> Critical ratio: 2.37%
#> Settings: alpha = 0.05; kappa = 2
crit$summary
#> rho eta_critical critical_ratio
#> 1 1 0.02439996 0.02369962
plot_sensitivity(crit)The sensitivity grid shows how the p-value changes as
eta scales the design-based covariance bounds and
rho controls their decay with the minimum network distance
between matched sets. Smaller rho means faster decay;
rho = 1 means no decay. Setting eta = 0 gives
the unadjusted analysis, while eta = rho = 1 gives the
design-based analysis.
The critical curve gives the value of eta at each
rho for which the p-value equals alpha = 0.05.
Within [0, 1], values below the curve retain rejection and
values above it do not.
The summary reports the critical eta under no decay
(rho = 1) and the critical ratio. The latter compares the
average weighted covariance bound across relevant pairs of matched sets
with the average weighted within-set variance at the critical boundary.
If the unadjusted p-value is greater than alpha, there is
no critical boundary.
The inferential assumptions are described in Dong and Lee (2026),
cited by citation("netmatchRI"). In particular, the
adjusted analysis requires the specified sensitivity bounds to cover the
cross-matched-set covariances.