Conformal Prediction for Spatially and Spatio-Temporally Dependent Data in R
spconform provides distribution-free prediction intervals with finite-sample coverage properties for spatial and spatio-temporal data. It relaxes the exchangeability assumption of standard conformal prediction by using spatial-distance and graph-neighbourhood kernel weights, offering a unified framework for both geostatistical (point-referenced) and areal (lattice) data structures.
Why spconform?
Standard conformal prediction assumes exchangeable data — an assumption routinely violated in spatial settings where nearby observations are more similar than distant ones. Existing R packages address either purely temporal dependence (conformalForecast, AdaptiveConformal) or i.i.d./exchangeable data (conformalInference, conformalClassification, cfcausal), but none provides a documented, unit-tested, unified solution for spatial data on CRAN.
| Feature | conformalInference |
conformalForecast |
scp (GitHub) |
geoconformal (Python) |
spconform |
|---|---|---|---|---|---|
| Language | R | R | R | Python | R |
| Geostatistical (point-ref.) | — | — | ✓ | ✓ | ✓ |
| Areal (lattice) | — | — | — | — | ✓ |
| Spatio-temporal | — | — (temp. only) | — | — | ✓ (opt.) |
| Model-agnostic | ✓ | ✓ | ✓ | ✓ | ✓ |
| Unit-tested / CRAN-ready | ✓ | ✓ | — | — | ✓ |
spconform is, to our knowledge, the first R package to offer conformal prediction spanning both major spatial data structures with optional spatio-temporal extension.
> Status: spconform has passed R CMD check --as-cran with 0 errors, > 0 warnings, and 0 notes on Windows 11 (R 4.6.1), win-builder (R-devel), > and R-hub v2 (Linux, Windows, macOS, donttest). The package is CRAN-ready > and will be submitted to CRAN as soon as the submission form re-opens. > A permanent, citable snapshot of version 0.1.0 is archived on Zenodo > (DOI above). The accompanying manuscript is currently in preparation > for submission to the Journal of Statistical Software.
Installation
# Install the development version from GitHub
remotes::install_github("amjed-droid/spconform")
# Once accepted on CRAN:
# install.packages("spconform")**Dependencies: The package imports only stats (base R). Suggested packages (‘sp’, ‘knitr’, ‘rmarkdown’) are used to build and run the vignette; ‘mgcv’, ‘ranger’, and ‘bmstdr’ are only needed to reproduce the extended examples shown in the accompanying paper and are not required for core package functionality.
Quick start
1. Geostatistical data (point-referenced)
library(spconform)
library(sp)
data(meuse)
s <- as.matrix(meuse[, c("x", "y")])
y <- log(meuse$zinc)
# Any user-supplied point predictor
pred_fun <- function(s_train, y_train, s_new) {
fit <- lm(y_train ~ s_train[, 1] + s_train[, 2] +
I(s_train[, 1]^2) + I(s_train[, 2]^2))
cbind(1, s_new[, 1], s_new[, 2],
s_new[, 1]^2, s_new[, 2]^2) %*% coef(fit)
}
# 90% locally weighted conformal intervals
set.seed(123)
idx <- sample(nrow(s), floor(0.7 * nrow(s)))
out <- scp_geostatistical(s[idx, ], y[idx], s[-idx, ], pred_fun, alpha = 0.1)
print(out)
#> <spconform> geostatistical conformal prediction
#> Target coverage: 90.0%
#> Number of prediction points: 47
coverage_report(out, y[-idx])
#> $coverage
#> [1] 0.957
#> $mean_width
#> [1] 2.212. Comprehensive Spatial Diagnostics
‘spconform’ includes a multi-panel diagnostic suite (diagnose()) to evaluate marginal coverage, conditional coverage across spatial strata, boundary effects, and the distribution of nonconformity scores: # Run diagnostics and produce publication-quality multi-panel plot diag <- diagnose(out, y_true = y[-idx], s_test = s[-idx], plot = TRUE)
View textual diagnostic summary
print(diag) === spconform Diagnostic Report ===
Marginal coverage: Empirical: 0.9574 (nominal: 0.9) Mean width: 2.2105 n = 47 , covered = 45
Conditional coverage by spatial bin: Q1-1: 1.0000 (n=7, width=3.529) Q1-2: 1.0000 (n=6, width=2.975) Q4-4: 0.8889 (n=9, width=1.930)
Boundary effect: Near boundary: 0.9583 (n=24) Far from boundary: 0.9565 (n=23) ### 3. Areal / lattice data
# Aggregate Meuse to a 6x6 grid (21 occupied cells)
xbreaks <- seq(min(meuse$x), max(meuse$x), length.out = 7)
ybreaks <- seq(min(meuse$y), max(meuse$y), length.out = 7)
meuse$cell_x <- cut(meuse$x, xbreaks, include.lowest = TRUE, labels = FALSE)
meuse$cell_y <- cut(meuse$y, ybreaks, include.lowest = TRUE, labels = FALSE)
meuse$cell_id <- (meuse$cell_y - 1) * 6 + meuse$cell_x
agg <- aggregate(log(zinc) ~ cell_id, data = meuse, FUN = mean)
names(agg) <- c("cell_id", "y")
cell_coords <- unique(meuse[, c("cell_id", "cell_x", "cell_y")])
agg <- merge(agg, cell_coords, by = "cell_id")
agg <- agg[order(agg$cell_id), ]
# Build adjacency matrix (Queen contiguity)
n_cells <- nrow(agg)
adj <- matrix(0, n_cells, n_cells)
for (i in 1:n_cells) {
for (j in 1:n_cells) {
if (i != j) {
dx <- abs(agg$cell_x[i] - agg$cell_x[j])
dy <- abs(agg$cell_y[i] - agg$cell_y[j])
if (dx <= 1 && dy <= 1) adj[i, j] <- 1
}
}
}
# 80% neighbourhood-weighted conformal intervals
out_areal <- scp_areal(agg$y, adjacency = adj, alpha = 0.2, decay = 0.5)
coverage_report(out_areal, agg$y)
#> $coverage
#> [1] 0.81
#> $mean_width
#> [1] 1.794. Spatio-temporal data
library(mgcv)
library(bmstdr)
data("nysptime")
df <- nysptime[complete.cases(nysptime[, c("utmx", "utmy", "y8hrmax", "Day", "Month")]), ]
df$day_idx <- ifelse(df$Month == 7, df$Day, 31 + df$Day)
s <- as.matrix(df[, c("utmx", "utmy")])
t <- df$day_idx
s_3d <- cbind(s, t)
y <- df$y8hrmax
# Spatio-temporal GAM predictor
pred_fun_st <- function(s_train, y_train, s_new) {
train_df <- data.frame(x = s_train[, 1], y = s_train[, 2],
day = s_train[, 3], z = y_train)
fit <- gam(z ~ te(x, y, day, k = c(8, 8, 4)), data = train_df)
new_df <- data.frame(x = s_new[, 1], y = s_new[, 2], day = s_new[, 3])
as.numeric(predict(fit, newdata = new_df))
}
set.seed(123)
n <- nrow(s_3d)
train_idx <- sample(n, floor(0.7 * n))
out_st <- scp_geostatistical(
s_train = s_3d[train_idx, ],
y_train = y[train_idx],
s0 = s_3d[-train_idx, ],
pred_fun = pred_fun_st,
t_train = t[train_idx],
t0 = t[-train_idx],
temporal_bandwidth = 5,
alpha = 0.1,
split = 0.5
)
coverage_report(out_st, y[-train_idx])
#> $coverage
#> [1] 0.909
#> $mean_width
#> [1] 41.7Quality Assurance
spconform has been rigorously tested across all major platforms to ensure CRAN-readiness:
| Platform | R Version | Status |
|---|---|---|
| Linux (Ubuntu) | R-devel | ✅ Pass |
| macOS (Sequoia) | R-devel | ✅ Pass |
| Windows | R-devel | ✅ Pass |
The package passes R CMD check --as-cran with 0 errors, 0 warnings, and 0 notes on all tested platforms. Continuous integration is monitored via GitHub Actions.
Key features
- Model-agnostic: Works with any user-supplied point predictor (kriging, GAM, random forest, linear model, …).
- Finite-sample coverage: Maintains coverage close to nominal level regardless of predictor misspecification (under local exchangeability).
-
Lightweight: Imports only
stats; no heavy spatial-modelling dependencies. -
Fully documented: S3 methods (
print,summary,plot,coverage_report) included, plus a full introductory vignette.
Citation
If you use spconform in your research, please cite:
Jabbar, A. S. (2026). spconform: Conformal Prediction for Spatially and Spatio-Temporally Dependent Data in R (Version 0.1.0) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.21862025
A companion manuscript describing the package methodology is currently submitted to the Journal of Statistical Software and will be cited here upon acceptance.
citation("spconform")Getting help
- Bug reports & feature requests: GitHub Issues
-
Documentation:
?scp_geostatistical,?scp_areal,vignette("spconform-intro", package = "spconform") -
Reproducible scripts: See
inst/scripts/in the package source.