---
title: "An Analysis Workflow with netmatchRI"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{An Analysis Workflow with netmatchRI}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE, comment = "#>",
  fig.width = 10, fig.height = 5, out.width = "75%", fig.align = "center", dpi = 150
)
```

`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.

## Data

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.

```{r}
library(netmatchRI)

data("netmatch_example", package = "netmatchRI")
sim <- netmatch_example
dat <- sim$data
net_dist <- sim$net_dist

dim(dat)
table(dat$Z)
```

## Match

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.

```{r}
m <- netmatch(
  data = dat,
  treat = "Z",
  covariates = c("X1", "X2", "X3"),
  network = net_dist,
  method = "dual",
  kappa = 2,
  estimand = "ATT"
)

m
```

## Summary and diagnostics

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.

```{r}
summary(m)

diag <- diagnose_match(m)
diag$covariate_balance
diag$network_summary
```

## Compare matching designs

Pass 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.

```{r, fig.width=7, fig.height=6}
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)
plot_covariate_similarity(designs, include_before = TRUE)
```

## Randomization-based inference

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.

```{r}
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
```

## Sensitivity

Evaluate 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.

```{r}
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
crit$summary
plot_sensitivity(crit)
```

## Interpretation

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.
