The fit must have been called with felm(..., keepCX = TRUE) so the
centered design matrix is stored on the object. IV/2SLS fits (the
felm multi-part formula with (endog ~ instruments)) work
out of the box: lfe stores the projected (second-stage) design in
cX and the structural residuals in residuals, which is
exactly the 2SLS sandwich. Weighted fits are supported (the scores carry
the weights and the bread uses \(X'WX\)).
Usage
# S3 method for class 'felm'
vcovSpHAC(
reg,
unit = NULL,
time = NULL,
lat = NULL,
lon = NULL,
kernel = c("bartlett", "uniform"),
dist_fn = c("haversine", "spherical", "chord"),
dist_cutoff = NULL,
lag_cutoff = 0,
verbose = FALSE,
balanced_pnl = FALSE,
ncores = NA,
pixel = 0,
neighbor = c("grid", "band"),
csr_weight = c("double", "float"),
method = c("auto", "pairwise", "grid"),
ssc = TRUE,
psd_fix = TRUE,
maxobsmem = 50000L,
data = NULL,
...
)Arguments
- reg
A fitted object of class "felm", including IV fits.
- unit
Optional name of the panel unit variable.
- time
Optional name of the time variable.
- lat
Name of the latitude variable. If
NULL(default), it is auto-detected from the data's column names ("lat","latitude", case-insensitive); a message reports the pick.- lon
Name of the longitude variable. If
NULL(default), it is auto-detected ("lon","long","longitude","lng", case-insensitive).- kernel
Spatial kernel, either "bartlett" or "uniform".
- dist_fn
Distance function, one of "haversine", "spherical", "chord".
- dist_cutoff
Spatial cutoff in km.
- lag_cutoff
Serial HAC lag cutoff.
- verbose
Print progress messages.
- balanced_pnl
Whether the panel is balanced and unit locations are time-invariant.
- ncores
Number of cores for the C++/RcppParallel spatial and serial routines.
- pixel
Score-pre-aggregation cell size, in kilometres. Default 0 (exact-coordinate dedupe only). If `pixel > 0`, points are snapped to a uniform `pixel`-km grid before the dedupe — a speed/accuracy trade-off that approximates the distance up to roughly `pixel / 2`.
- neighbor
Neighbor-search strategy for the spatial meat: "grid" (default; 3D cell grid, output-sensitive candidate enumeration) or "band" (latitude band scan, the pre-0.5.0 behavior). Both are exact and use identical per-pair accept tests; results agree to floating-point summation order.
- csr_weight
Storage precision for the balanced-path bartlett kernel weights: "double" (default, exact) or "float" (halves the per-pair weight memory; introduces at most ~6e-8 relative error per weight). Ignored for
kernel = "uniform", which stores no weights.- method
Spatial meat engine. "pairwise" enumerates neighbor pairs (the default engine; works for any data). "grid" uses the exact grid-native meat — requires observations on a regular lat/lon lattice (e.g. raster data); cost is independent of the pair count, so it is dramatically faster on dense grids with large cutoffs. The uniform kernel uses sliding-window prefix sums; the bartlett kernel uses per-ring-pair FFT convolutions. Lattices spanning the full longitude circle wrap correctly across the dateline. "auto" (default) picks "grid" when it detects a lattice and a flop-balance estimate says it wins; both engines are exact, so the choice only affects speed (results agree to FP summation order, plus ~1e-12 acos conditioning for the bartlett spherical/chord weights). Pass
verbose = TRUEto see which engine ran; if you expect gridded data to use the grid engine and it does not, run once withmethod = "grid"— it errors with the specific reason instead of falling back.- ssc
Small-sample correction. If
TRUE(default), the variance matrix is scaled byn / (n - K)whereKcounts all estimated parameters including absorbed fixed-effect levels (taken from the fit's residual degrees of freedom). This matchesfixest's default Conley correction (its cluster adjustment is a no-op for Conley vcovs). PassFALSEfor no correction — that reproducesrbluhm/conley, fastconley versions before 0.9.0, andfixestwithssc(adj = FALSE, cluster.adj = FALSE).- psd_fix
The spatial kernels do not guarantee a positive semi-definite variance matrix. If
TRUE(default), negative eigenvalues are clamped (to 1e-16, asfixest'svcov_fixdoes) and a warning reports when the fix noticeably changed the matrix. IfFALSE, the matrix is returned as computed, with a warning when it is not positive semi-definite.- maxobsmem
Ignored by the fast spatial path. Kept for backward compatibility.
- data
Optional. The data frame to draw
lat/lonfrom. IfNULL(default), the data is recovered from the fit's call and aligned via a model-frame re-evaluation. Pass it explicitly if the original data has gone out of scope — and when no rows were dropped at fit time (no NAs, nosubset), the coordinates are then taken by direct column access with no model-frame rebuild at all.- ...
Currently unused.
Examples
if (requireNamespace("lfe", quietly = TRUE)) {
## Cross-section on a regular 0.5-degree raster with holes. method =
## "grid" forces the exact grid engine; the default method = "auto"
## picks it automatically when the raster is large enough to win
## (on a toy example this small, pairwise is just as fast).
set.seed(1)
cells <- expand.grid(lat = seq(40, 49.5, by = 0.5),
lon = seq(-10, 9.5, by = 0.5))
cells <- cells[sample(nrow(cells), 600), ] # irregular occupancy
cells$x <- rnorm(nrow(cells))
cells$y <- 0.5 * cells$x + rnorm(nrow(cells))
fit <- lfe::felm(y ~ x, data = cells, keepCX = TRUE)
V <- vcovSpHAC(fit, lat = "lat", lon = "lon",
kernel = "bartlett", dist_fn = "spherical",
dist_cutoff = 200, ncores = 2, method = "grid",
data = cells)
sqrt(diag(V))
## Panel with spatial + serial HAC (scattered points: pairwise engine)
pnl <- data.frame(unit = rep(1:200, each = 5),
time = rep(1:5, times = 200),
lat = rep(runif(200, 40, 50), each = 5),
lon = rep(runif(200, -10, 10), each = 5))
pnl$x <- rnorm(nrow(pnl))
pnl$y <- 0.5 * pnl$x + rnorm(nrow(pnl))
fit2 <- lfe::felm(y ~ x | unit + time, data = pnl, keepCX = TRUE)
V2 <- vcovSpHAC(fit2, unit = "unit", time = "time",
lat = "lat", lon = "lon", kernel = "bartlett",
dist_fn = "haversine", dist_cutoff = 300,
lag_cutoff = 2, balanced_pnl = TRUE, ncores = 2,
data = pnl)
sqrt(diag(V2))
}
#> x
#> 0.03549569