Status: draft. Nothing is being submitted. This file describes the working tree so it does not drift out of date while development continues. Every "" below must be replaced with a real result before this is used for an actual submission.
This is an update to spatialkit 1.0.0, published on CRAN 2026-08-07.
The version being submitted is 2.0.0. A major bump is the honest choice:
three exported functions are removed, and several defaults change the result of
a fit or a comparison — the same script gets a different block size, a
different projection and different standard errors than it did under 1.0.0.
See "Breaking changes" in NEWS.md.
The CRAN page for 1.0.0 lists no reverse depends, imports or suggests, so there
is nothing to break. Confirm with revdepcheck::revdep_check() before
submitting rather than relying on this note. (This container has no network
access, so nothing here was checked against CRAN.)
spatialkit builds tessellations over point data, aggregates observations to
cells, and cross-validates spatial models with folds that respect the data's
autocorrelation structure. Three model backends (GWR via GWmodel, a Bayesian
Gaussian process via brms, random forests via ranger) sit behind one S3
class so they can be scored on identical folds.
Ubuntu 24.04.4 LTS, R 4.3.3, x86_64-pc-linux-gnu — the environment the results below were produced on. Installed: sf 1.0.15, dplyr 1.1.4, logger 0.2.2, digest 0.6.34, testthat 3.2.1, sp 2.1.2, ranger 0.16.0, tibble 3.2.1, geometry 0.4.7, gstat 2.1.1, ggplot2 3.4.4, patchwork 1.2.0, FNN 1.1.4, Matrix 1.6.5, roxygen2 7.3.1, knitr 1.45, rmarkdown 2.25, pkgload 1.3.4, spdep 1.3.1, brms 2.20.4.
GWmodel was present only as a faithful API stub built from CRAN sources --
argument names and order, return shapes, SDF column naming and the GWR.df
column order, with real weighted least squares -- because the real package would
not install in this container. And although brms itself is installed, Stan
could not compile here, so no posterior was ever sampled: the brms findings
were established with make_stancode(), make_standata(), get_prior() and
validate_prior(), which need no compilation. Every GWR and brms result
recorded below must be re-confirmed on a machine with the real packages before
submission. Not installed: cmdstanr, loo.
Still to run before any submission:
- local: macOS (aarch64-apple-darwin), R release —
- win-builder: R-devel —
- win-builder: R-release —
- GitHub Actions
R-CMD-check.yaml, five jobs (not a cross-product): macOS-latest R release; windows-latest R release; ubuntu-latest R devel; ubuntu-latest R release; ubuntu-latest R oldrel-1. R-devel and oldrel-1 are tested on Linux only. — - GitHub Actions
backendsjob (ubuntu-latest, R release) withsp,GWmodel,gstat,FNN,Matrix,geometry,rangerandtibbleinstalled, so the optional code paths actually execute rather than skip. — - GitHub Actions
check-brms.yaml(weekly, ubuntu-latest, R release) withbrmsand the Stan toolchain, which is the only job that setsSPATIALKIT_TEST_BRMSand therefore the only one that runs the five Stan smoke tests. —
<fill in: R CMD check --as-cran has not been run against the current tree on a
release-R machine, and neither has the manual (no LaTeX here).>
On the environment above, R CMD check --no-manual on the built tarball --
with the vignette built, the GWmodel stub and brms installed, and
_R_CHECK_CRAN_INCOMING_=false because --as-cran needs network access to
CRAN that this container does not have -- reports 1 NOTE and nothing else.
- NOTE -- "Package suggested but not available for checking: 'cmdstanr'".
Environmental, and expected:
cmdstanris not on CRAN. It is reached throughAdditional_repositories, is used strictly conditionally viarequireNamespace(), and thecheck-brmsCI job installs it.
Everything previously recorded here as a blocker is resolved:
-
The ERROR in "checking tests" is gone. Those 15 failures were tests asserting the pre-fix behaviour of
.morans_i_for_k(). They have been rewritten against the corrected contract, along with the tests that encoded the pre-fix GP domain measure, GWR criterion column, Kish standard error and subsampled design effect.checking testspasses. -
The non-ASCII WARNING is gone:
checking R files for non-ASCII characterspasses. The literal U+2014 EM DASH that sat inside a string literal inensure_projected()'starget_crserror message has been replaced with--. Em dashes elsewhere inR/sit in comments and roxygen blocks, which the check tolerates. -
The syntax-error WARNING is gone:
checking R files for syntax errorspasses with no output. It previously carried only a failedSys.setlocale("LC_CTYPE", "en_US.UTF-8")from the check's own locale switch.
Two artefacts of this container are worth recognising if they appear again.
checking package dependencies emits
Warning: unable to access index for repository ... lines, because there is no
outbound network access here; it is not a check condition and does not affect
the status line. And building with --no-build-vignettes adds two WARNINGs
("Files in the 'vignettes' directory but no files in 'inst/doc'" and
"Directory 'inst/doc' does not exist") that a normal R CMD build does not
produce.
R CMD build produces a 994 KB tarball, of which the built vignette HTML is the
bulk. checking running R code from vignettes passes.
testthat reports 3479 passing, 0 failures, 0 errors, 0 warnings, 9 skips
with NOT_CRAN=true and both backends present, and no test runs with zero
assertions. The nine skips are:
- 5 Stan smoke tests in
test-bayes-smoke.R, skipped becauseSPATIALKIT_TEST_BRMSis unset --skip_if_not_installed("brms")alone was not enough, since these compile Stan models and would otherwise run in any matrix job that happened to havebrms - 1 Windows-only fallback path that cannot run on Linux
- 3 that skip because an optional backend is installed: they assert the
behaviour seen when
GWmodelorbrmsis absent
Under R CMD check, where NOT_CRAN is unset, four further tests skip on
purpose -- the parallel::mclapply() fork tests in test-cv-parallel.R, which
are skip_on_cran().
README.md does not restate these counts, precisely so the two cannot drift
apart.
Three exported functions have been removed: evaluate_models(),
evaluate_models_cv() and phi_prior_bounds(). All three were thin wrappers
the package's own documentation described as legacy, and each has a documented
replacement (compare_models(), compare_models_cv() and
gp_lengthscale_bounds()). NAMESPACE now exports 40 objects.
Default results from fit_bayesian_spatial_model() also change, as a
consequence of three corrections to the Gaussian process path (the basis count,
the coordinate scaling, and the length-scale prior). The previous behaviour
remains reachable by passing gp_k, gp_c and gp_iso explicitly.
Three further corrections change results without being deliberate default changes, so they are recorded under "Bug fixes" rather than "Breaking changes", but a user upgrading should know about them:
create_grid_polygons()supplied with bothcellsizeandnused to return a grid truncated ton[1]xn[2]cells anchored at the bounding-box corner, silently covering only part of the boundary.cellsizenow wins andnis dropped with a logged warning.fit_gwr_model()used to refuse any response with exactly two distinct finite values as "binary". A censored or saturated continuous measurement has two distinct values and is a well-defined Gaussian GWR problem; the hard stop is now gated on the response also being integer-like, and the non-integer case warns and fits. Fits that used to error now run.determine_optimal_levels()no longer reports a Moran's I that is fixed by arithmetic. Below a nine-cell floor the criterion returnsNAand the call falls back to the geometric ranking, which changes which cell counts it returns at the defaultmax_levels.
NEWS.md is the full record: 20 breaking changes, 62 bug fixes, 13 new
features and 19 documentation entries relative to 1.0.0.
In descending order of user impact:
-
residual_morans_i()failed on its own documented fast path. WithFNNandMatrixinstalled the weights are a sparseMatrix, andbase::crossprod()does not S4-dispatch on thedgeMatrixthatW %*% residproduces, so the call errored — takingcompare_models(), which invokes it automatically, down with it. This is a live failure on the recommended configuration, not a corner case. -
The automatic length-scale prior for the Gaussian process was expressed in the wrong units.
brms::gp()defaults toscale = TRUE, which rescales its covariates so the maximum pairwise distance is 1 and reportslscalein that space. This package standardises the coordinates itself and derived the prior in those units, so the two normalisations differed by roughly the maximum pairwise distance and the prior was about five times too diffuse. In practice that produced rejected initial values ("Gradient evaluated at the initial value is not finite") on most chains. The GP term now setsscale = FALSE. -
The number of GP basis functions was chosen from the number of observations rather than from the spatial structure of the data.
brms::gp()builds a tensor grid, sogp(x, y, k = k)carriesk^2basis functions, and the previous rule reduced tomax(15, floor(sqrt(n)))for any n above 45 — making the basis count identically n. A model at n = 10,000 carried 10,000 basis functions. The count is now derived from the length-scale-to-domain ratio following Riutort-Mayol et al. (2023, Statistics and Computing 33:1). Measured ondev/baseline-structural.rds: at n = 2,000,gp_k44 to 24 and the basis count 1,936 to 576 on the elongated layout (44 to 22 and 1,936 to 484 on the clustered one); at n = 10,000, 100 to 23 and 10,000 to 529; at n = 200 the basis is larger than before (225 to 529), which is the expected consequence of a correction rather than an optimisation. -
ensure_projected()forced data of any extent into a single UTM zone. Transverse Mercator scale error grows quadratically with distance from the central meridian, so continental-extent data carried distance errors of several percent, propagating silently into variogram ranges, spatial block sizes, GWR bandwidth selection and GP length-scales. Wide extents now receive an equal-area projection centred on the data. -
make_folds(method = "nndm")calledset.seed(seed)unconditionally, andseeddefaults toNULL.set.seed(NULL)re-initialises the RNG from the clock and process ID, so an ordinary call destroyed the caller's random number stream. Every seeded path is now guarded by an internal.with_seed()helper that is a no-op whenseedisNULL. -
Cross-validation under
parallel = TRUEwas not reproducible; forked workers seeded themselves from the current time and process ID.
A third audit pass found a further group of defects that returned a plausible
wrong number rather than failing, which is why they are called out here as
well as in NEWS.md. In each case the measurement is recorded there.
-
gwr_model_selection()ranked models on AIC while labelling the answer AICc: GWmodel's diagnostic table is built byrbind()over unnamed vectors so it never carries column names, making the positional read the normal path rather than a fallback, and column 2 is the uncorrected AIC. Executed, the old column selected a model containing a pure-noise predictor that AICc drops. -
The calibrated GP length-scale prior never reached Stan. A
class = "lscale"prior with nocoefis a global prior, which brms applies only to coefficients that lack an individual prior — and everylscalecoefficient has one. Confirmed withmake_stancode(). -
The GP basis was sized against the per-axis half-range while
brms::gp(c = )multiplies the full pooled range, so the boundary was twice as wide asgp_kwas sized for and the diagnostic meant to catch under-resolution was twice too lenient. -
fitted()on agwr_fitreturned a local coefficient surface when a predictor was namedfit,pred,prediction,fittedoryhat; executed in-sample R^2 was −1.18 against a true 0.981. -
residual_morans_i()put weight on a point's own residual whenever coordinates were duplicated, and applied an exchangeable null to model residuals. Both inflate significance; the corrected moments agree withspdep::lm.morantest()to machine precision. -
summarize_by_cell()'s design-effect standard errors were too small (95% coverage 0.63 at rho = 0.8), and a subsampled variogram design effect answered for a cell ofdeff_max_npoints rather than the cell's own size. -
estimate_sac_range()swept only two azimuths at ±22.5°, leaving half of all directions covered by neither, and itsn_maxsubsample was unseeded — so the range was irreproducible aboven_maxand the caller's RNG was advanced.
A fourth, adversarial pass then set three reviewers the task of breaking the
package on valid input. The corrections it produced are listed under "Fourth
audit pass" in NEWS.md; the ones that changed results were the predictor ICC
pooled under shared cell labels (under-estimated by ~1/m), the variogram design
effect evaluated at degree distances on lon/lat input (SEs out by a factor of
354), CRS-less coordinates interpreted one way at fit time and another at
predict time (predictions at the training rows off by one response SD), and
NNDM folds built by an approximation that left the realised distance
distribution up to 0.17 above the target — now the paper's own deterministic
procedure, verified removal for removal against a transcription of it.
None listed on the CRAN page for 1.0.0. Re-confirm with
revdepcheck::revdep_check() before submitting.
-
Words flagged by the spell checker in DESCRIPTION (Voronoi, Delaunay, GWR, backends) are correctly spelled domain terms or standard abbreviations.
-
cmdstanr(Suggests) is not on CRAN; it is available from the repository declared inAdditional_repositories(https://stan-dev.r-universe.dev). It is an optional backend forbrms, used only conditionally viarequireNamespace(); therstanbackend that ships withbrmsis used otherwise. -
Optional model backends (
GWmodel,brms,ranger) and other heavy dependencies live in Suggests and are used strictly conditionally. All package code, examples, tests and the vignette guard their use withrequireNamespace()and skip or degrade gracefully when the package is absent. The vignette resolves every optional backend in its setup chunk and gates the relevant chunks on the result; theggplot2gate is global, since every chunk in it either draws something or feeds something that does, so on a machine withoutggplot2the vignette builds as code without output rather than failingR CMD build. The vignette built and ran here withranger,gstat,geometryandpatchworkpresent andGWmodelabsent. -
Exactly two examples are wrapped in
\dontrun{}:fit_bayesian_spatial_model()andcv_bayes(), both of which run full MCMC viabrmsand require Stan compilation. Every other example runs, using\donttest{}plus arequireNamespace()guard where it needs an optional backend.checking examplespasses. -
Logging writes INFO+ to a session
tempdir()file and WARN+ to the console (see.onLoadinR/zzz.R), all within a package-specificloggernamespace so the user's globalloggerconfiguration is never modified. Nothing is written outsidetempdir(). The demo script ininst/scripts/also writes its PNGs totempdir()unless the user sets an environment variable naming somewhere else. -
RNG state is saved and restored around seeded operations via an internal
.with_seed()helper — the per-fold streams used by the parallel cross-validation path, the k-means seeding functions, andfit_gwr_model()'s local-collinearity spot-check. WhereseedisNULL, nothing is seeded and nothing is restored: unseeded functions advance the caller's stream the way any other unseeded R function does, rather than re-initialising it. That distinction is the subject of fix 5 above. -
NEWS.mdis long. The package changed substantially since the 1.0.0 tag, and several of the changes alter results that users may have already reported, so each is recorded with enough detail to tell whether it affects a given analysis.