The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.

Dependence-Robust Inference in gdpar (Axis 2)

Making the uncertainty honest under temporal and spatial dependence, without modelling it (Block 9, Axis 2)

José Mauricio Gómez Julián

2026-07-06

1. What this vignette covers

gdpar’s Path 1 estimates — Empirical-Bayes (gdpar_eb) or full-Bayes (gdpar) — assume the observations are conditionally independent given the mean structure. When the data carry temporal (serial) or spatial autocorrelation, that assumption is violated: the point estimates remain consistent if the mean structure is correctly specified, but the model-based (posterior / Laplace) standard errors are too narrow and the intervals under-cover. Sections 2–3 use the Empirical-Bayes path; Section 4 shows the identical workflow on a full-Bayes fit.

Axis 2 of Block 9 is the inferential response to this. It is deliberately not a model of the dependence:

Honest scope. gdpar does not model the correlated noise or the spatial random field (that is Axis 1 / a later modelling block, deferred and evidence-gated). Axis 2 only makes the inference robust to dependence. The stance is the working-independence + robust-variance estimator of Liang & Zeger (1986): the point estimates are unchanged; only the reported uncertainty is re-estimated to be dependence-robust.

Two symmetric pairs of functions, sharing one internal refit engine:

Dependence Diagnostic Robust SE / intervals
Temporal gdpar_dependence_diagnostic() gdpar_dependence_robust()
Spatial gdpar_spatial_dependence_diagnostic() gdpar_spatial_dependence_robust()

The workflow is always the same: diagnose first, and only if dependence is flagged, re-estimate the uncertainty.

2. The temporal pair (recap)

library(gdpar)

set.seed(1)
n <- 150
x <- rnorm(n)
# AR(1) errors: serial dependence the working-independence fit ignores.
y <- 1 + 0.5 * x + as.numeric(stats::arima.sim(list(ar = 0.6), n))
df <- data.frame(x = x, y = y, t = seq_len(n))

fit <- gdpar_eb(y ~ x, amm = amm_spec(a = ~ x), data = df,
                chains = 2, iter_warmup = 300, iter_sampling = 300)

# Step 1 — diagnose. Lag-1 autocorrelation, Durbin-Watson, Ljung-Box.
gdpar_dependence_diagnostic(fit, index = df$t)

# Step 2 — if flagged, re-estimate the uncertainty by a temporal block bootstrap.
gdpar_dependence_robust(fit, data = df, index = df$t, B = 199, seed = 1)

The robust table reports, per coefficient, the estimate (unchanged), the model_se, the bootstrap robust_se, their ratio se_ratio and percentile ci_lower / ci_upper. A se_ratio > 1 means the model-based SE understates the dependence-robust SE.

The block length. By default block_length = NULL uses the rate-optimal round(n^(1/3)) (it fixes only the rate). You may fix an integer manually, or let the data choose it:

# Data-driven block length: the Politis-White (2004) automatic selector,
# computed from the residuals (no extra refit), with the rate as fallback.
gdpar_dependence_robust(fit, data = df, index = df$t,
                        block_length = "auto", B = 199, seed = 1)

"auto" runs the canonical Politis & White (2004) rule (Patton, Politis & White 2009 correction): it reads the residual autocorrelations, finds the lag beyond which they are negligible, and returns b_opt = (2 ĝ² / D)^(1/3) n^(1/3) with the overlapping-block constant D = (4/3) spec². Stronger serial dependence gives a longer block; white-noise residuals give unit blocks; a degenerate series falls back to the rate. The chosen value and method are reported in block_length and block_length_method. (The selector assumes the fitted parameter count is small relative to n.)

3. The spatial pair

The spatial functions replace exactly two pieces of the temporal machinery — the statistic (lag-1 autocorrelation becomes Moran’s I) and the resampling (1-D contiguous blocks become 2-D spatial blocks) — and reuse everything else.

3.1. Diagnostic: Moran’s I

set.seed(2)
n <- 200
gx <- runif(n); gy <- runif(n)            # spatial coordinates
x <- rnorm(n)
# An omitted smooth spatial trend lands in the residuals.
y <- 1 + 0.5 * x + 3 * (gx + gy) + rnorm(n, sd = 0.3)
df <- data.frame(x = x, y = y)

fit_sp <- gdpar_eb(y ~ x, amm = amm_spec(a = ~ x), data = df,
                   chains = 2, iter_warmup = 300, iter_sampling = 300)

gdpar_spatial_dependence_diagnostic(fit_sp, coords = cbind(gx, gy), seed = 1)

Moran’s I is

\[ I \;=\; \frac{n}{S_0}\, \frac{\sum_i \sum_j w_{ij}\,(r_i - \bar r)(r_j - \bar r)} {\sum_i (r_i - \bar r)^2}, \qquad S_0 = \sum_i \sum_j w_{ij}, \]

with \(r\) the residuals and \(W = (w_{ij})\) a spatial weight matrix. Under the null of spatial exchangeability \(\mathbb{E}[I] = -1/(n-1)\).

Choosing the weights. By default gdpar builds a row-standardized \(k\)-nearest-neighbour graph (so \(S_0 = n\)), with the declared heuristic \(k = \max(4, \min(\lfloor \log n \rceil, n-1))\) — robust to irregular spacing and guaranteeing no isolated point. Alternatives:

Significance. The default is a two-sided permutation test (n_perm = 999): the residuals are relabelled across locations, \(I\) is recomputed, and \(p = (1 + \#\{|I_{\text{perm}} - \mathbb{E}[I]| \ge |I - \mathbb{E}[I]|\}) / (n_{\text{perm}} + 1)\). It assumes neither normal residuals nor a symmetric \(W\), so it is safe with Dunn-Smyth residuals and the asymmetric kNN graph. The analytic Cliff-Ord normal approximation is available via test = "analytic" (cheaper, but it warns under an asymmetric \(W\)).

3.2. Robust SE: spatial block bootstrap

gdpar_spatial_dependence_robust(fit_sp, data = df, coords = cbind(gx, gy),
                                B = 199, seed = 1)

The bounding box of coords is tiled into a g × g grid; non-empty cells are resampled with replacement and concatenated to length n. By default the grid origin is randomized per replicate (random_origin = TRUE, Politis-Romano-Lahiri), which breaks the deterministic cell-boundary artifact. An overlapping scheme = "moving" is also available. The output table has the same columns as the temporal one.

3.3. The default block size, and why n^(1/4)

The block side per axis defaults to g = max(2, round(n^(1/4))). This is the \(d = 2\) case of the rate that minimises the mean-squared error of the block-bootstrap variance estimator. Writing \(M\) for the points per block (so the block has linear extent \(M^{1/d}\) per axis):

so \(\mathrm{MSE}(M) \sim M^{-2/d} + M/n\) is minimised at \(M \sim n^{\,d/(d+2)}\). At \(d = 1\) this gives \(M \sim n^{1/3}\) points per block — exactly the temporal block_length = round(n^(1/3)) default — and at \(d = 2\) it gives \(M \sim n^{1/2}\), i.e. \(g^2 = n/M \sim n^{1/2}\) cells, hence \(g \sim n^{1/4}\) cells per axis. The exponent is therefore the variance-optimal rate that reduces correctly to the canonical temporal rate.

Registered dissent (D100). A decorrelating cross-lineage review argued for the \(n^{1/(d+4)}\) rate (\(n^{1/6}\) at \(d = 2\)). That rate governs a different estimand — the second-order bias / two-sided distribution-function coverage, which gives \(n^{1/5}\) at \(d = 1\) and so does not reduce to the variance default’s \(n^{1/3}\). It is recorded as a dissent, not adopted.

3.4. Data-driven block size (block_size = "auto")

The default above fixes only the rate. Its constant can be chosen from the data — but, unlike the temporal case, Politis & White (2004) has no established spatial plug-in (its flat-top spectral-density-at-zero estimator does not extend cleanly to a field in the plane). gdpar therefore calibrates the cells-per-axis g over a grid of candidates:

gdpar_spatial_dependence_robust(fit_sp, data = df, coords = cbind(gx, gy),
                                block_size = "auto", B = 199, seed = 1)

For each candidate g, cheap (no-refit) spatial block resamples give the bootstrap variance \(V(g)\) of the design-weighted residual functionals \((1/n)\,[1, \tilde{gx}, \tilde{gy}]^\top z\) — these are the influence directions of the coefficient, so their optimal block size matches the coefficient’s, which the residual mean alone would not. The block size minimises an empirical mean-squared error,

\[ g^\ast \;=\; \arg\min_g\;\Big[\underbrace{(\tilde V(g) - \tilde V(g_{\min}))^2} _{\text{squared bias}} \;+\; \underbrace{c\,V(g)^2 / n_{\text{tiles}}(g)} _{\text{variance}}\Big], \]

with the bias anchored at the largest blocks \(g_{\min}\) (the least biased, since the dependence-breaking bias of the variance estimator grows like \(g/\sqrt n\)) and the variance scaling like the inverse number of blocks (Lahiri 2003). Stronger / longer-range spatial dependence is captured by larger blocks (smaller g); a short-range or near-independent field by smaller blocks (larger g); a degenerate calibration falls back to the \(n^{1/4}\) rate. The choice and method are reported in block_size and block_size_method.

Provenance and honest scope (D101). A decorrelating cross-lineage review supplied the empirical-MSE skeleton; two of its concrete choices — the bias anchor and the variance term — were corrected after an audit and empirical validation, because the proposed forms would have made the selector anticonservative (anchoring at the smallest blocks) or non-adaptive (a Monte-Carlo jackknife variance that vanishes with the resample count). A single isotropic g is used; strongly anisotropic residual dependence is a documented limitation (the minimal fix, two coordinate-wise calibrations, is deferred). As everywhere in Axis 2, this only sizes the resampling blocks — it does not model the dependence.

4. The full-Bayes path

Everything above was shown on the Empirical-Bayes path (gdpar_eb). The exact same four functions also accept a scalar full-Bayes fit (gdpar), so the EB/FB asymmetry is closed: diagnose, then — if flagged — re-estimate, with no change of API.

# A full-Bayes fit instead of an Empirical-Bayes one:
fit_fb <- gdpar(y ~ x, amm = amm_spec(a = ~ x), data = df,
                chains = 2, iter_warmup = 300, iter_sampling = 300)

# Same diagnostic, same robust SE -- only the object class changes.
gdpar_dependence_diagnostic(fit_fb, index = df$t)
gdpar_dependence_robust(fit_fb, data = df, index = df$t, B = 199, seed = 1)

What differs is internal, and worth understanding before you read the table:

A bagged / widened posterior (BayesBag) would be a different object — a re-architected estimator, not a robust variance for the same one — and is a deliberately deferred lateral; Axis 2 keeps its honest scope, robust variance, not better estimates, on both paths.

5. Reading the result, and the caveats

References

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.