Skip to content

Commit 5d89be6

Browse files
elkronosclaude
andcommitted
Lead with the boundary-drawing purpose in README and DESCRIPTION
Both opened on cross-validation, which is the trust mechanism rather than the reason to reach for the package. The point is that you can build analysis regions from the distribution of your own observations instead of aggregating onto ZIP codes, tracts or territories that were drawn for mail routing, enumeration or sales and know nothing about the process being studied -- the modifiable areal unit problem, where the partition itself moves the answer. README now opens on that, names the four functions that do it (get_voronoi_seeds, determine_optimal_levels, build_tessellation, summarize_by_cell), and keeps the cross-validation argument and its random-vs-blocked table as the second half: redrawing boundaries is easy, knowing whether the result means anything is not. DESCRIPTION leads with the same sentence so CRAN, the repo blurb and the README tell one story. man/spatialkit-package.Rd regenerated from DESCRIPTION. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W1BQ34CMur2xHrqbbvFUmw
1 parent b79c601 commit 5d89be6

3 files changed

Lines changed: 56 additions & 28 deletions

File tree

DESCRIPTION

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ Version: 1.0.0.9000
44
Authors@R:
55
person("Justin", "Chase", email = "jchase.msu@gmail.com",
66
role = c("aut", "cre", "cph"))
7-
Description: A modular toolkit for spatial analysis workflows including
8-
coordinate reference system management, Voronoi/Delaunay/grid
9-
tessellation, feature-to-polygon assignment and cell-level
10-
aggregation. Model backends cover geographically weighted regression
7+
Description: Constructs analysis regions from the distribution of the data
8+
itself, as an alternative to aggregating onto administrative boundaries
9+
that were drawn for unrelated purposes. Seeds and builds Voronoi,
10+
Delaunay, hexagonal and square tessellations with reproducible
11+
identifiers, selects a cell count from the spatial structure of the
12+
observations, assigns features to cells, and aggregates to cell level
13+
with optional design-effect corrections so that standard errors account
14+
for within-cell autocorrelation. Also manages coordinate reference
15+
systems. Model backends cover geographically weighted regression
1116
(GWR, via 'GWmodel'), Bayesian spatial Gaussian process regression
1217
(via 'brms') and random forests (via 'ranger'), each behind a common
1318
S3 class ('spatial_fit') with consistent predict, fitted, residuals

README.md

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,52 @@
88

99
## The problem this solves
1010

11-
You have point observations — sites, sensors, parcels, plots — and you want a
12-
model that predicts well at locations you have not sampled. You fit something
13-
flexible, cross-validate it, and get an R² of 0.8. Then you predict onto a grid
14-
and the map is wrong in ways the score never hinted at.
15-
16-
The score was wrong because the folds were wrong. Random k-fold puts a test
17-
point's nearest neighbours in the training set, and under spatial
18-
autocorrelation those neighbours carry most of its signal. The model is scored
19-
on interpolation between known points while the actual task is extrapolation
20-
away from them. The same thing happens one level up, in variable selection: a
21-
predictor chosen by random inner folds can be chosen for leaking rather than
22-
for predicting.
23-
24-
`spatialkit` is built around fixing that. It provides folds that hold out whole
25-
regions sized to the data's own autocorrelation range; three model backends —
26-
geographically weighted regression (`GWmodel`), a Bayesian spatial Gaussian
27-
process (`brms`), and random forests (`ranger`) — behind one `spatial_fit` S3
28-
class so they can be scored on identical folds; and an area-of-applicability
29-
estimate that marks where the resulting score actually applies. Around all of
30-
that sits a tessellation and aggregation pipeline built on
31-
[`sf`](https://r-spatial.github.io/sf/): CRS management, Voronoi, hex, square
32-
and Delaunay cells with stable reproducible IDs, and cell-level summaries with
33-
optional design-effect-corrected standard errors.
11+
You have point observations — sites, sensors, parcels, plots — and you need to
12+
summarise or model them over areas. The path of least resistance is to borrow
13+
boundaries that already exist: ZIP codes, census tracts, counties, sales
14+
territories. Every one of those was drawn for mail routing, enumeration or
15+
politics, and none of them knows anything about the process you are studying.
16+
Aggregate to them and part of your answer is an artefact of the partition —
17+
the modifiable areal unit problem, and it is not a rounding error: the same
18+
observations can support materially different conclusions under different
19+
boundaries.
20+
21+
`spatialkit` lets the data draw the boundaries instead:
22+
23+
- **Seed cells where the observations actually are**`get_voronoi_seeds()`
24+
places seeds by k-means on the point cloud, so cell density follows sampling
25+
density rather than an inherited grid.
26+
- **Let the spatial structure choose how many**`determine_optimal_levels()`
27+
ranks candidate cell counts by the elbow of within-cluster variance, and,
28+
once you hand it a response, also by how much spatial autocorrelation each
29+
partition leaves in the residuals. Better than picking a round number.
30+
- **Build them, reproducibly**`build_tessellation()` produces Voronoi, hex,
31+
square or Delaunay cells, clipped to your study area, with stable IDs that do
32+
not shift when the input row order does.
33+
- **Aggregate honestly**`summarize_by_cell(deff = "kish")` corrects
34+
cell-level standard errors for within-cell autocorrelation, rather than
35+
treating clustered observations as independent and reporting intervals that
36+
are too narrow.
37+
38+
Redrawing boundaries is easy; knowing whether the result means anything is not.
39+
So the second half of the package exists to keep you honest: three model
40+
backends — geographically weighted regression (`GWmodel`), a Bayesian spatial
41+
Gaussian process (`brms`), and random forests (`ranger`) — behind one
42+
`spatial_fit` S3 class so they can be scored on identical folds; cross-validation
43+
that holds out whole regions sized to the data's own autocorrelation range; and
44+
an area-of-applicability estimate that marks where the resulting score actually
45+
applies. All of it is built on [`sf`](https://r-spatial.github.io/sf/), with CRS
46+
management that will not silently hand metres to a function expecting degrees.
47+
48+
That validation half matters more than it sounds. Fit something flexible,
49+
cross-validate it, get an R² of 0.8, predict onto a grid — and the map is wrong
50+
in ways the score never hinted at. The score was wrong because the folds were
51+
wrong. Random k-fold puts a test point's nearest neighbours in the training set,
52+
and under spatial autocorrelation those neighbours carry most of its signal. The
53+
model is scored on interpolation between known points while the actual task is
54+
extrapolation away from them. The same thing happens one level up, in variable
55+
selection: a predictor chosen by random inner folds can be chosen for leaking
56+
rather than for predicting.
3457

3558
Here is the gap, on 600 synthetic points with a smooth spatial field the
3659
predictor does not explain. Same data, same model, same settings — only the

man/spatialkit-package.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)