| Title: | Causal Effect Estimation Under Spatial Confounding and Interference |
|---|---|
| Description: | Implements the distance-adjusted propensity score with interference (iDAPS) and recoverU+ methods for estimating the average treatment effect on the treated (ATT) from spatial observational data in the presence of both spatial confounding and spatial interference. iDAPS matches units on a data-driven composite of propensity-score distance, spatial proximity and neighbourhood-exposure distance. recoverU+ is a doubly robust estimator that augments the propensity-score and control-outcome models with a partially recovered spatial confounder and a neighbourhood-exposure term. The package also provides the naive propensity score, DAPS and recoverU comparators, a simulator for the spatial confounding/interference data-generating process and spatial inference tools |
| Authors: | Isqeel Ogunsola [aut, cre], Olatunji Johnson [aut], Thomas House [aut] |
| Maintainer: | Isqeel Ogunsola <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.2.0 |
| Built: | 2026-10-07 22:18:02 UTC |
| Source: | https://github.com/ogunsolaia/spaci |
Estimates an upper bound on the confounding bias of an idaps() matching
estimate, following the variogram bias bound. A matched-pair difference is a
spatial-differencing operation, so the estimator's bias decomposes into three
pieces controlled by the matched sample:
where are within-pair covariate and neighbourhood-
exposure differences, is the (semi)variogram of the unmeasured
spatial confounder evaluated at the matched distances , and is
a selection constant (see c_overlap). The covariate and exposure
coefficients are taken from a working outcome model, and the confounding term
from the fitted spatial variogram of that model's residuals (whose sill
carries ). The confounding term shrinks as
the matched distances shrink, so a smaller caliper yields a smaller bound.
bias_bound( object, Y, Z, X, coords, tau = 0.1, c_overlap = 1.2, matern_method = c("mle", "geoR"), matern_nu = 0.5 )bias_bound( object, Y, Z, X, coords, tau = 0.1, c_overlap = 1.2, matern_method = c("mle", "geoR"), matern_nu = 0.5 )
object |
An |
Y, Z, X, coords
|
The same outcome, treatment, covariates and coordinates
passed to |
tau |
Exposure-kernel bandwidth, used only if |
c_overlap |
Selection inflation constant on the confounding term
(default |
matern_method, matern_nu
|
Passed to the residual variogram fit; see
|
An object of class spaci_bias_bound: a list with the covariate,
exposure and confounding bound terms (term_X, term_E, term_U), their
total, the mean matched distance, and the fitted variogram parameters.
sim <- simulate_spatial_causal(n = 50, seed = 1) fit <- idaps(sim$Y, sim$Z, sim$X, sim$coords, tau = 0.1, seed = 1) bias_bound(fit, sim$Y, sim$Z, sim$X, sim$coords)sim <- simulate_spatial_causal(n = 50, seed = 1) fit <- idaps(sim$Y, sim$Z, sim$X, sim$coords, tau = 0.1, seed = 1) bias_bound(fit, sim$Y, sim$Z, sim$X, sim$coords)
Resamples square spatial blocks of units with replacement to build a
bootstrap distribution that reflects spatial dependence. For the doubly robust
estimators (recoverU/recoverU+) it block-resamples the estimator's
influence values (a fast, stable linearised bootstrap that avoids re-kriging
resampled coordinates); for the matching estimators it re-runs the whole
pipeline on each resample (recomputing neighbourhood exposures, with a tiny
coordinate jitter to avoid exact-duplicate ties).
boot_spatial( Y, Z, X, coords, method = c("recoverUplus", "recoverU", "idaps", "daps", "naive_ps"), block_size = "auto", B = 200, tau = 0.1, caliper = 0.25, matern_method = c("mle", "geoR"), matern_nu = 0.5, level = 0.95, seed = NULL )boot_spatial( Y, Z, X, coords, method = c("recoverUplus", "recoverU", "idaps", "daps", "naive_ps"), block_size = "auto", B = 200, tau = 0.1, caliper = 0.25, matern_method = c("mle", "geoR"), matern_nu = 0.5, level = 0.95, seed = NULL )
Y, Z, X, coords
|
Data, as passed to the estimators. |
method |
Estimator to bootstrap: one of |
block_size |
Side length of the square blocks: |
B |
Number of bootstrap resamples (default |
tau, caliper, matern_method, matern_nu
|
Estimator arguments (as relevant). |
level |
Confidence level. |
seed |
Optional integer seed for reproducibility. |
An object of class spaci_boot with the bootstrap se, percentile
ci, point estimate, and the retained resample estimates.
sim <- simulate_spatial_causal(n = 50, seed = 1) boot_spatial(sim$Y, sim$Z, sim$X, sim$coords, method = "recoverUplus", B = 50)sim <- simulate_spatial_causal(n = 50, seed = 1) boot_spatial(sim$Y, sim$Z, sim$X, sim$coords, method = "recoverUplus", B = 50)
Estimates the ATT by matching on a convex combination of propensity-score
distance and spatial (Euclidean) distance,
, adjusting for spatial
confounding but not interference. The weight is chosen over a
grid to minimise covariate imbalance in the matched sample
(Papadogeorgou et al., 2019).
daps( Y, Z, X, coords, caliper = 0.25, alpha_grid_step = 0.1, match_method = c("greedy", "optimal"), seed = NULL, level = 0.95 )daps( Y, Z, X, coords, caliper = 0.25, alpha_grid_step = 0.1, match_method = c("greedy", "optimal"), seed = NULL, level = 0.95 )
Y |
Numeric outcome vector. |
Z |
Binary treatment vector (0/1). |
X |
Covariate matrix or data frame. |
coords |
Two-column matrix or data frame of spatial coordinates. |
caliper |
Maximum acceptable matching distance (default |
alpha_grid_step |
Grid step for the |
match_method |
Matching algorithm: |
seed |
Optional integer seed for the (randomised) matching order. |
level |
Confidence level for the reported interval. |
An idaps_fit object; the selected is
reported in weights.
sim <- simulate_spatial_causal(n = 50, seed = 1) daps(sim$Y, sim$Z, sim$X, sim$coords)sim <- simulate_spatial_causal(n = 50, seed = 1) daps(sim$Y, sim$Z, sim$X, sim$coords)
Estimates the ATT by matching on the composite distance of Equation (2.3),
, where the
interference component is the absolute difference in neighbourhood exposure.
The weights sum to one and are chosen over a grid
to minimise a composite balance score (covariate balance, spatial proximity
and exposure balance), rather than tuned by hand. Setting
recovers DAPS and recovers naive PS.
idaps( Y, Z, X, coords, tau = 0.1, caliper = 0.25, pi_grid_step = 0.1, normalize = TRUE, match_method = c("greedy", "optimal"), seed = NULL, level = 0.95 )idaps( Y, Z, X, coords, tau = 0.1, caliper = 0.25, pi_grid_step = 0.1, normalize = TRUE, match_method = c("greedy", "optimal"), seed = NULL, level = 0.95 )
Y |
Numeric outcome vector. |
Z |
Binary treatment vector (0/1). |
X |
Covariate matrix or data frame. |
coords |
Two-column matrix or data frame of spatial coordinates. |
tau |
Positive spatial decay parameter of the exposure kernel (default
|
caliper |
Maximum acceptable matching distance (default |
pi_grid_step |
Grid step for the weight search over the simplex. |
normalize |
Logical; row-normalise the exposure kernel (default |
match_method |
Matching algorithm: |
seed |
Optional integer seed for the (randomised) matching order. |
level |
Confidence level for the reported interval. |
An idaps_fit object; the selected weights are
reported in weights.
sim <- simulate_spatial_causal(n = 50, seed = 1) idaps(sim$Y, sim$Z, sim$X, sim$coords, tau = 0.1)sim <- simulate_spatial_causal(n = 50, seed = 1) idaps(sim$Y, sim$Z, sim$X, sim$coords, tau = 0.1)
Estimates the ATT by greedy 1:1 matching on the (normalised) difference in estimated propensity scores. This is the baseline that ignores both spatial confounding and spatial interference.
naive_ps( Y, Z, X, caliper = 0.25, match_method = c("greedy", "optimal"), seed = NULL, level = 0.95 )naive_ps( Y, Z, X, caliper = 0.25, match_method = c("greedy", "optimal"), seed = NULL, level = 0.95 )
Y |
Numeric outcome vector. |
Z |
Binary treatment vector (0/1). |
X |
Covariate matrix or data frame. |
caliper |
Maximum acceptable matching distance (default |
match_method |
Matching algorithm: |
seed |
Optional integer seed for the (randomised) matching order. |
level |
Confidence level for the reported interval. |
An idaps_fit object.
sim <- simulate_spatial_causal(n = 50, seed = 1) naive_ps(sim$Y, sim$Z, sim$X)sim <- simulate_spatial_causal(n = 50, seed = 1) naive_ps(sim$Y, sim$Z, sim$X)
Constructs the neighbourhood-exposure mapping
that summarises spatial interference,
where is an exponential kernel of the distance between units
and , with
. This is the exposure used by idaps() and the
recoverU family.
neighbourhood_exposure(coords, Z, tau = 0.1, normalize = TRUE)neighbourhood_exposure(coords, Z, tau = 0.1, normalize = TRUE)
coords |
A two-column matrix or data frame of spatial coordinates. |
Z |
Binary treatment vector (0/1), one entry per row of |
tau |
Positive spatial decay (bandwidth) parameter of the exponential kernel. Smaller values concentrate exposure on nearer neighbours. |
normalize |
Logical; if |
A list with components
Numeric vector of neighbourhood exposures, one per unit.
The (possibly row-normalised) kernel matrix with zero diagonal.
set.seed(1) coords <- cbind(runif(20), runif(20)) Z <- rbinom(20, 1, 0.5) ex <- neighbourhood_exposure(coords, Z, tau = 0.1) head(ex$E)set.seed(1) coords <- cbind(runif(20), runif(20)) Z <- rbinom(20, 1, 0.5) ex <- neighbourhood_exposure(coords, Z, tau = 0.1) head(ex$E)
Draws a forest (caterpillar) plot of the estimated ATT and its confidence interval for each method, with a reference line at zero and, optionally, at the true effect. This reproduces the style of Figure 2.5 of the report and is the recommended way to compare methods visually.
plot_ate( x, true_att = NULL, null_line = 0, col = "#4C72B0", pt_col = "#1A1A1A", xlab = "Average treatment effect on the treated (ATT)", main = "Estimated ATT and confidence intervals", ... )plot_ate( x, true_att = NULL, null_line = 0, col = "#4C72B0", pt_col = "#1A1A1A", xlab = "Average treatment effect on the treated (ATT)", main = "Estimated ATT and confidence intervals", ... )
x |
Either the data frame returned by |
true_att |
Optional true effect; drawn as a dashed vertical reference line when supplied. |
null_line |
Position of the "no effect" reference line (default |
col, pt_col
|
Colours for the confidence intervals and point estimates. |
xlab, main
|
Axis label and title. |
... |
Passed to |
The plotted data frame, invisibly.
sim <- simulate_spatial_causal(n = 50, seed = 1) res <- spatial_ate(sim$Y, sim$Z, sim$X, sim$coords, seed = 1) plot_ate(res, true_att = sim$true_att)sim <- simulate_spatial_causal(n = 50, seed = 1) res <- spatial_ate(sim$Y, sim$Z, sim$X, sim$coords, seed = 1) plot_ate(res, true_att = sim$true_att)
Plot a single estimate
## S3 method for class 'idaps_fit' plot(x, ...)## S3 method for class 'idaps_fit' plot(x, ...)
x |
An |
... |
Passed to |
The plotted data frame, invisibly.
Print method for idaps fit objects
## S3 method for class 'idaps_fit' print(x, digits = 4, ...)## S3 method for class 'idaps_fit' print(x, digits = 4, ...)
x |
An |
digits |
Number of significant digits. |
... |
Ignored. |
x, invisibly.
Print method for a bias bound
## S3 method for class 'spaci_bias_bound' print(x, digits = 4, ...)## S3 method for class 'spaci_bias_bound' print(x, digits = 4, ...)
x |
A |
digits |
Number of significant digits. |
... |
Ignored. |
x, invisibly.
Print method for a spatial block bootstrap
## S3 method for class 'spaci_boot' print(x, digits = 4, ...)## S3 method for class 'spaci_boot' print(x, digits = 4, ...)
x |
A |
digits |
Significant digits. |
... |
Ignored. |
x, invisibly.
Print method for a randomization test
## S3 method for class 'spaci_randtest' print(x, digits = 4, ...)## S3 method for class 'spaci_randtest' print(x, digits = 4, ...)
x |
A |
digits |
Significant digits. |
... |
Ignored. |
x, invisibly.
Print method for a spatial HAC variance
## S3 method for class 'spaci_vcov' print(x, digits = 4, ...)## S3 method for class 'spaci_vcov' print(x, digits = 4, ...)
x |
A |
digits |
Significant digits. |
... |
Ignored. |
x, invisibly.
Design-based test of the sharp null that the treatment has no direct effect on
any unit. Treatment is redrawn from the estimated propensity score (so the
reference distribution respects the observed treatment mechanism),
neighbourhood exposures are recomputed, and the estimator is recomputed for
each draw. The two-sided p-value is
.
rand_test( Y, Z, X, coords, method = c("recoverUplus", "recoverU", "idaps", "daps", "naive_ps"), R = 200, tau = 0.1, caliper = 0.25, matern_method = c("mle", "geoR"), matern_nu = 0.5, seed = NULL )rand_test( Y, Z, X, coords, method = c("recoverUplus", "recoverU", "idaps", "daps", "naive_ps"), R = 200, tau = 0.1, caliper = 0.25, matern_method = c("mle", "geoR"), matern_nu = 0.5, seed = NULL )
Y, Z, X, coords
|
Data, as passed to the estimators. |
method |
Estimator to bootstrap: one of |
R |
Number of randomization draws (default |
tau, caliper, matern_method, matern_nu
|
Estimator arguments (as relevant). |
seed |
Optional integer seed for reproducibility. |
An object of class spaci_randtest with the observed statistic, the
p-value and the null draws.
sim <- simulate_spatial_causal(n = 50, seed = 1) rand_test(sim$Y, sim$Z, sim$X, sim$coords, method = "naive_ps", R = 50) # refits the full recoverU+ pipeline on every draw, so it is slower rand_test(sim$Y, sim$Z, sim$X, sim$coords, method = "recoverUplus", R = 20)sim <- simulate_spatial_causal(n = 50, seed = 1) rand_test(sim$Y, sim$Z, sim$X, sim$coords, method = "naive_ps", R = 50) # refits the full recoverU+ pipeline on every draw, so it is slower rand_test(sim$Y, sim$Z, sim$X, sim$coords, method = "recoverUplus", R = 20)
Doubly robust ATT estimator that adjusts for spatial confounding through a
partially recovered spatial confounder (Pokal et al., 2023), but does not
adjust the propensity-score / control-outcome models for spatial
interference. Provided as a comparator to recoverUplus().
recoverU( Y, Z, X, coords, tau = 0.1, normalize = TRUE, matern_method = c("mle", "geoR"), matern_nu = 0.5, level = 0.95 )recoverU( Y, Z, X, coords, tau = 0.1, normalize = TRUE, matern_method = c("mle", "geoR"), matern_nu = 0.5, level = 0.95 )
Y |
Numeric outcome vector. |
Z |
Binary treatment vector (0/1). |
X |
Covariate matrix or data frame. |
coords |
Two-column matrix or data frame of coordinates. |
tau |
Positive spatial decay parameter for the exposure kernel used in
the initial outcome model (default |
normalize |
Logical; row-normalise the exposure kernel (default |
matern_method |
Matern estimation engine: |
matern_nu |
Fixed Matern smoothness for the |
level |
Confidence level for the reported interval. |
An idaps_fit object.
sim <- simulate_spatial_causal(n = 50, seed = 1) recoverU(sim$Y, sim$Z, sim$X, sim$coords)sim <- simulate_spatial_causal(n = 50, seed = 1) recoverU(sim$Y, sim$Z, sim$X, sim$coords)
The recoverU+ estimator augments the doubly robust ATT with both a partially
recovered spatial confounder and a neighbourhood-exposure term, so that the
propensity-score and control-outcome models adjust for spatial confounding
and spatial interference simultaneously (Equations 2.10-2.12). Compared with
recoverU(), the neighbourhood exposure G is included in the PS and
control-outcome models.
recoverUplus( Y, Z, X, coords, tau = 0.1, normalize = TRUE, matern_method = c("mle", "geoR"), matern_nu = 0.5, level = 0.95 )recoverUplus( Y, Z, X, coords, tau = 0.1, normalize = TRUE, matern_method = c("mle", "geoR"), matern_nu = 0.5, level = 0.95 )
Y |
Numeric outcome vector. |
Z |
Binary treatment vector (0/1). |
X |
Covariate matrix or data frame. |
coords |
Two-column matrix or data frame of coordinates. |
tau |
Positive spatial decay parameter for the exposure kernel used in
the initial outcome model (default |
normalize |
Logical; row-normalise the exposure kernel (default |
matern_method |
Matern estimation engine: |
matern_nu |
Fixed Matern smoothness for the |
level |
Confidence level for the reported interval. |
An idaps_fit object.
sim <- simulate_spatial_causal(n = 50, seed = 1) recoverUplus(sim$Y, sim$Z, sim$X, sim$coords)sim <- simulate_spatial_causal(n = 50, seed = 1) recoverUplus(sim$Y, sim$Z, sim$X, sim$coords)
Draws one data set from the data-generating process of the report
(Equation 2.13 and Section 2.3): an unmeasured spatial confounder
drawn from an exponential Gaussian random field drives both treatment and
outcome, and the outcome further depends on a neighbourhood-exposure term so
that spatial interference is present. The true ATT is true_att.
simulate_spatial_causal( n = 150, true_att = 2, beta0 = 2.5, beta1 = 1, beta2 = 0.5, theta_spatial = 0.4, gamma_interference = 1.5, sigma_eps = 1, delta_u = 2, u_phi = 0.2, tau_exp = 0.1, normalize = TRUE, seed = NULL )simulate_spatial_causal( n = 150, true_att = 2, beta0 = 2.5, beta1 = 1, beta2 = 0.5, theta_spatial = 0.4, gamma_interference = 1.5, sigma_eps = 1, delta_u = 2, u_phi = 0.2, tau_exp = 0.1, normalize = TRUE, seed = NULL )
n |
Number of units. |
true_att |
True average treatment effect on the treated. |
beta0, beta1, beta2
|
Outcome-model intercept and covariate coefficients. |
theta_spatial |
Coefficient of the spatial confounder in the outcome. |
gamma_interference |
Coefficient of neighbourhood exposure in the outcome (interference strength). |
sigma_eps |
Outcome noise standard deviation. |
delta_u |
Strength of the spatial confounder in the treatment model (controls the degree of spatial confounding). |
u_phi |
Range parameter of the confounder's exponential covariance. |
tau_exp |
Spatial decay parameter of the exposure kernel. |
normalize |
Logical; row-normalise the exposure kernel (default |
seed |
Optional integer seed. |
The Gaussian random field is generated directly from its covariance matrix (exponential covariance, i.e. Matern with smoothness 1/2), so the simulator has no external dependency.
A list with Y, Z, X (matrix with columns X1, X2), coords,
the exposure E, the latent confounder U, and true_att.
sim <- simulate_spatial_causal(n = 50, seed = 42) str(sim)sim <- simulate_spatial_causal(n = 50, seed = 42) str(sim)
Convenience wrapper that runs the naive propensity score, DAPS, iDAPS, recoverU and recoverU+ estimators on the same data and collects the point estimates, standard errors and confidence intervals in a single data frame.
spatial_ate( Y, Z, X, coords, tau = 0.1, caliper = 0.25, matern_method = c("mle", "geoR"), level = 0.95, seed = NULL, methods = c("Naive PS", "DAPS", "iDAPS", "recoverU", "recoverU+") )spatial_ate( Y, Z, X, coords, tau = 0.1, caliper = 0.25, matern_method = c("mle", "geoR"), level = 0.95, seed = NULL, methods = c("Naive PS", "DAPS", "iDAPS", "recoverU", "recoverU+") )
Y |
Numeric outcome vector. |
Z |
Binary treatment vector (0/1). |
X |
Covariate matrix or data frame. |
coords |
Two-column matrix or data frame of spatial coordinates. |
tau |
Positive spatial decay parameter of the exposure kernel (default
|
caliper |
Maximum acceptable matching distance (default |
matern_method |
Matern estimation engine for the recoverU family,
|
level |
Confidence level for the reported interval. |
seed |
Optional integer seed for the (randomised) matching order. |
methods |
Character vector selecting which estimators to run. Defaults to all five. |
A data frame with one row per method (Method, ATT, SE,
Lower, Upper), with the fitted objects attached as the "fits"
attribute.
sim <- simulate_spatial_causal(n = 50, seed = 1) spatial_ate(sim$Y, sim$Z, sim$X, sim$coords, seed = 1)sim <- simulate_spatial_causal(n = 50, seed = 1) spatial_ate(sim$Y, sim$Z, sim$X, sim$coords, seed = 1)
Computes a spatial heteroskedasticity- and autocorrelation-consistent
(Conley) variance for a recoverU() or recoverUplus() estimate,
where are the estimator's influence values and is a
kernel with bandwidth . Unlike the default i.i.d. standard error, this
accounts for spatial correlation of the influence values, which otherwise
makes intervals too narrow.
vcov_hac( object, bandwidth = "auto", kernel = c("bartlett", "uniform"), level = 0.95 )vcov_hac( object, bandwidth = "auto", kernel = c("bartlett", "uniform"), level = 0.95 )
object |
A |
bandwidth |
Kernel bandwidth: |
kernel |
|
level |
Confidence level for the returned interval. |
An object of class spaci_vcov: a list with the HAC variance, se,
confidence interval ci, the att, and the bandwidth/kernel used.
sim <- simulate_spatial_causal(n = 50, seed = 1) fit <- recoverUplus(sim$Y, sim$Z, sim$X, sim$coords) vcov_hac(fit)sim <- simulate_spatial_causal(n = 50, seed = 1) fit <- recoverUplus(sim$Y, sim$Z, sim$X, sim$coords) vcov_hac(fit)