Reproducible models · Tutorial 12 of 12

Reproducing the Generalized Covariate Field

A complete walkthrough of GCF — a spatial prediction method that expands the data rather than the model: each covariate is expanded into spatial-pattern and neighbourhood-distribution features around every location, a stable subset is selected with spatial-block resampling, and any learner then predicts from the enriched representation. From the packaged simulation and species-richness data to the paper's tables, with every re-run number regenerable by one command.

Cite this method

To cite the GCF method and its R package and codes in publications, please use:

Song Y (2026). Generalized covariate field (GCF): spatial-pattern and neighbourhood-distribution feature expansion improves geospatial prediction. International Journal of Geographical Information Science 40:1–29. doi · PDF · GitHub

Song Y. gcf: Generalized Covariate Field. R package, version 0.1.0. CRAN submission in preparation — until it is accepted, install from the released tarball.

Download code & data (zip, 660 KB)

R/ pipeline scripts · config/project-config.R · run-all.R · tests/ · results/ · tables/ · env/ · data/ (incl. the paper's case_final.rds result object and the published table CSVs used as ground truth) · a README.md. Unzip and run Rscript run-all.R test from the GCF/ folder root. The two input tables ship inside the gcf package and steps 1 and 4 write CSV copies of them for you.

Open the online GCF calculator — no installation, runs in your browser

Upload a CSV of spatial variables with projected coordinates, assign the coordinate, variable and (optionally) response columns, and the app runs the same gcf feature engine compiled to WebAssembly: the ψ spatial-pattern features, the Zx neighbourhood quantiles, the functional reduction and the spatial-block stability selection, with variable maps, the candidate table and CSV/PDF downloads (the browser build uses a randomForest importance kernel in place of ranger; see the app's method notes). Nothing is uploaded to a server — the computation happens on your device.

Expansion of the twelve covariates of the biodiversity case study into spatial pattern features and neighbourhood distribution features, with the nine derived features retained by the full-sample variable selection
The method in one figure, from the published article (Song 2026, Figure 5): the twelve original predictors of the biodiversity case study are expanded into two derived feature categories (left), and the nine derived features retained by the full-sample variable selection are mapped — (a) spatial pattern features, (b) neighbourhood distribution features. ψV = log local variance; ψP = positive z-outlier strength; Zτ = neighbourhood τ-quantile; IQR Z = Z0.75 − Z0.25. Step 4 of this tutorial runs the same feature construction on the observed cells.

1 Method Overview & Reproduction Scope

1.1 Core Idea

Most attempts to improve spatial prediction add model: a deeper forest, a cleverer kernel, another layer. GCF starts from the opposite observation — the covariates themselves carry spatial information that point values throw away. A location is not just its covariate vector: it sits inside a neighbourhood of covariate values, with local dependence, heterogeneity, multiscale variation and outlyingness of its own. The generalized covariate field (GCF) turns that surrounding structure into explanatory variables.

GCF is a feature transform, not a learner — there is no fitting inside it. Each covariate is expanded into two families of features at every location, sampled and unsampled: spatial pattern features ψ (eleven local operators over a series of buffer radii) and neighbourhood distribution features Zx (quantiles of the covariate over multiscale neighbourhoods). The sweeps are collinear by construction, so a functional reduction compresses them into interpretable candidates, and a stability selection keeps the few that matter. Any learner then fits on raw covariates + selected features exactly as it would have fit on raw covariates alone.

  • Research problem: prediction accuracy is constrained by spatial heterogeneity and sparse, uneven sampling; existing methods respond by increasing algorithmic complexity while underusing the spatial information embedded in the covariates themselves.
  • One-line contribution: expand the covariate space — pattern operators + neighbourhood quantiles + leakage-free reduction + spatial-block stability selection — so that a fixed learner sees the spatial structure it could not extract from point values.
  • Why it matters here: in the simulation, GCF lifts the spatial-block CV R² of every one of seven learners — RF from 0.419 to 0.657 (+56.5 %) — and the reproduction of the paper's Table 2 below is byte-identical. In the case study it lifts RF spatial R² from 0.296 to 0.346 (+16.7 %) with a 3.0 % RMSE reduction.
Reader anchor

x → ψ (what the covariate's local spatial pattern looks like) → Zx (what values surround this location, summarised as quantiles) → functional reduction (band-average the collinear sweeps into a compact candidate field) → stability selection (keep the covariates, vote in the few derived features that fire reliably) → hand the selected set to any learner. Everything on this page serves that line.

What you need

R 4.1+ and the gcf package (the method engine, validated bit-identical to the paper's analysis code), plus the seven learner packages for the model comparison. Both input tables ship inside gcf, so the pipeline never touches the network. A full run takes about 140 seconds; the verification tests take 16.

1.2 Method Logic

GCF runs in four steps, followed by the paper's validation design. The notation follows the paper's Section 2; every step below is exercised by the pipeline in §3.

Step 1Spatial pattern ψ
11 operators × buffers
Step 2Neighbourhood distribution Zx
quantiles × buffers
Step 3aFunctional reduction
band-averaged P and D candidates
Step 3bStability selection
rf_imp + blocks + group voting
Validationdual five-fold CV
random + spatial-block

Step 1 — spatial pattern features ψ

For each covariate and each buffer radius b, eleven local operators describe the covariate's spatial behaviour around location v: LISA on the local z-score (spatial dependence), local Geary's c (local contrast), log local variance (heterogeneity), rank-binned quantile entropy (local diversity), geocomplexity (single scale), log scale-variance across the scale series (single scale), the local variogram exponent (roughness of local spatial structure), and positive/negative z-score and MAD outlier strengths (local outlyingness). The simulation uses buffers {2, 4, 6} on the unit grid → 87 ψ columns from 3 covariates; the case study uses {20, …, 100} km → 12 × 83 = 996.

Step 2 — neighbourhood distribution features Zx

For each location v, covariate x and buffer b, summarise the covariate's distribution over the support points inside the buffer by quantile levels τ:

(Zx)Zx(vb, τ) = Qτx(u) : u ∈ N(vb) }

Sim: 3 covariates × 3 buffers × 11 quantile levels = 99 columns. Case: 12 × 9 × 21 = 2,268. Together with ψ this is what "the field" in the method's name means: every covariate becomes a field of spatial descriptors around every location, computable at unsampled locations too.

Step 3a — functional reduction (leakage-free)

The quantile/buffer sweeps are strongly collinear, so they are reduced to interpretable functionals — a per-row transform computed once, with no response involved. Two scale bands are fixed a priori: fine ({20, 30} km in the case, {2} in the sim) and broad ({90, 100} km / {6}). For each covariate × band, the band-averaged quantile curve yields five D (context) functionals:

(D)med = q0.50, iqr = q0.75 − q0.25, lotail = q0.10, hitail = q0.90, skew = q0.90 + q0.10 − 2q0.50

and each buffered ψ operator is band-averaged into P (pattern) candidates (the two single-scale operators pass through). With the raw covariates X forced in, the reduced candidate field is 93 columns in the sim (X 3, P 60, D 30) and 372 in the case (X 12, P 240, D 120) — the paper's Table 4, which step 4 rebuilds byte-for-byte.

Step 3b — selection: rf_imp kernel + spatial-block stability + group voting

Selection runs on training rows only (inside each CV fold, so it is leakage-safe). Three moves:

  1. Per-subsample selector. On a subsample, fit a random forest (ranger, 200 trees) and keep the top K = 20 derived features by impurity importance: sb,j = 1{rank(−impj) ≤ 20}. The raw covariates are never candidates for removal.
  2. Spatial-block stability. The training rows fall in spatial blocks; for b = 1…B (B = 80) draw ⌊0.7 M⌋ of the M blocks, run the selector, and record the selection frequency freqj = (1/B) Σb sb,j.
  3. Medium group voting. Group the derived features by (covariate × category). A group qualifies if any member is kept in at least π = 0.6 of the resamples — fireg = (1/B) Σb 1{Σj∈Gg sb,j > 0} ≥ π — and contributes its highest-frequency member. Voting de-dilutes collinear blocks: sibling features split freq so none clears π alone, while the block as a whole fires reliably.

The output S = covariates ∪ group representatives is typically ~20–25 features in the case study and 8 in the simulation.

The validation design — dual five-fold cross-validation

Two partitions of the same samples: ordinary random five-fold (interpolation), and spatial-block five-fold (transfer): square blocks of side L = 2 × the residual variogram range (132 km in the case; 6 in the sim) are assigned whole to folds by shuffled round-robin (seed 36). Metrics are per-fold mean ± sd:

(metrics)R² = 1 − Σ(y−ŷ)²/Σ(y−ȳfold)², RMSE = √[mean(y−ŷ)²], R²imp% = 100·(R²gcf−R²base)/R²base, RMSEimp% = 100·(RMSEbase−RMSEgcf)/RMSEbase

Seven learners are compared, each with the paper's fixed settings — RF (randomForest, 500 trees), Cubist, GLMNET (ridge), LM, XGBoost, SVM (radial), KNN — first on the raw covariates, then on the per-fold re-selected GCF sets. Because both arms share folds and learners, any difference is attributable to the representation, not to the algorithm.

1.3 Reproduction Scope

Everything tagged Demo run regenerates from this folder with Rscript run-all.R on R 4.6.0 with gcf 0.1.0. The scope follows a deliberate split — simulation: full re-run; case study: lightweight rebuild — and three things are worth stating plainly before any number below is quoted.

What is re-run and what is rebuilt
  • The simulation per-model validation (paper Table 2) is re-run end to end — feature field, ten B = 80 per-fold selections, seven learners under both partitions — and the finished table is byte-identical to the published CSV. Nothing about it is copied.
  • The case-study validation (paper Tables 5–7) is rebuilt, not re-run. The full case pipeline — per-fold selections over 372 candidates, seven learners, the feature-set and contribution analyses, three impact analyses, full-grid prediction — runs for hours on 12 cores (the paper's run_case.R). This tutorial ships the paper's computed result object data/case_final.rds unchanged and rebuilds Tables 5, 6 and 7 and the prediction figure from it with the paper's own formatting code. All three tables come out byte-identical to the published CSVs — a check that the shipped object, the formatting code and the published tables are one consistent chain.
  • The case feature demo uses observed-cell support. The paper computes features over the full 6,229-cell grid; step 4 demonstrates the identical gcf_field() call on the 958 observed cells (about a minute instead of much longer). The candidate structure — 996 ψ, 2,268 Zx, 372 candidates — rebuilds exactly; the feature values differ because the neighbourhood support differs, and the demo selection consequently overlaps the paper's full-support selection in 19 of 21 variables rather than 21 of 21 (step 4 shows exactly which).
ItemDemo templatePublished reference
Data data(sim_grid) (900 cells) and data(bio_grid) (6,229 cells, 958 observed), shipped with the gcf package; data/case_final.rds, the paper's computed case result object the same two datasets — the packaged tables are value-identical to the paper's raw CSVs
Method gcf_field(), gcf_select(), gcf_blocks() from the gcf package; learners + CV ported verbatim from the paper's analysis code into R/01-helpers.R the paper's Section 2 and its analysis engine (which the package reproduces to machine precision)
Tables results/table02…07*.csv — Table 2 re-run, Table 4 rebuilt from the demo field, Tables 5–7 rebuilt from case_final.rds; every one byte-identical to data/paper-tables/ paper Tables 2, 4, 5, 6, 7 (Tables 1 and 3 are descriptive and authored, not computed)
Figures fig01–fig06 from R/10 … R/60 the paper's figures are their own high-resolution versions of the same content; none are embedded here
Not attempted the case full-grid feature build and four-part validation re-run (hours on 12 cores); the three impact analyses; the paper's display projection (EPSG:3857) — maps here use plain Albers km axes

What the template regenerates versus what is rebuilt from the shipped result object.

How to use this page

  • To reproduce the demo: run Rscript run-all.R test, then Rscript run-all.R. Every re-run number and figure below regenerates in about 140 seconds.
  • To use GCF on your own data: §2.3 gives the data contract — one table with projected coordinates, a response at the sampled rows and covariates everywhere — and §4.1 the porting steps.
  • To check rather than trust: §3.4 lists the 28 verification checks, including the byte-identity of every rebuilt table against data/paper-tables/.

2 Setup, Structure & Data Contract

2.1 Environment & Dependencies

The method is one package written for the paper and validated bit-identical against its analysis engine; the learners are the seven the paper compares.

R console
# The method, and both input data sets (CRAN submission in preparation;
# until acceptance, install gcf from the released tarball)
install.packages("gcf")
# The seven learners of the per-model comparison
install.packages(c("ranger", "randomForest", "glmnet", "Cubist",
                   "xgboost", "e1071", "kknn"))
PackageRole in the pipelineRequired?
gcf 0.1.0gcf_field() for ψ + Zx + reduction (Steps 1–3a), gcf_select() for the stability selection (Step 3b), gcf_blocks() for the spatial blocks, plus the sim_grid and bio_grid datayes
ranger 0.18.0the importance kernel inside gcf_select() (200 trees, single-threaded, fixed seed)yes (a gcf dependency)
randomForest, Cubist, glmnet, xgboost, e1071, kknnthe seven learners of Table 2 / Table 5, with the paper's fixed settings in gcf_fit_pred()yes, for step 3
base R graphicsall six figures, as PNG at 196 dpi and matching vector PDFbuilt in

Package roles. The byte-exact reproduction of Table 2 depends on the learner package versions (randomForest 4.7-1.2, glmnet 5.0, xgboost 3.2.1.1, e1071 1.7-17, kknn 1.4.1, Cubist 0.6.0) — a future release could move third decimals, and results/sim-permodel-check.csv would show exactly which cells moved. The verified session is recorded in env/session-info.txt: R 4.6.0, aarch64-apple-darwin23.

2.2 Project Structure & Configuration

One entry script runs the six numbered steps, the table builder, or the tests. Each step writes to results/, tables/, figs/ or data/derived/ and reads what the previous step wrote, so any step can be re-run in isolation after a configuration change.

folder tree
GCF/
├── gcf.html                       # this guide
├── run-all.R                      # entry point: full run, single steps, tests
├── config/project-config.R        # the ONLY file to edit for a new domain
├── R/
│   ├── 00-config.R, 01-helpers.R  # paths, IO, folds, the 7 learners, base-R maps
│   ├── 10-sim-field.R             # sim: gcf_field -> 93 candidates + full-data selection
│   ├── 20-sim-cv.R                # sim: dual five-fold design + 10 per-fold selections
│   ├── 30-sim-permodel.R          # sim: 7 learners x base/GCF  ->  Table 2 (byte-check)
│   ├── 40-case-field.R            # case: gcf_field on the 958 observed cells -> Table 4
│   ├── 50-case-tables.R           # case: Tables 5-7 from case_final.rds (byte-check)
│   ├── 60-case-prediction.R       # case: both prediction surfaces, mapped
│   └── 90-tables.R                # LaTeX tables + session info
├── data/                          # sim-grid.csv + bio-grid.csv (written by steps 1 and 4),
│                                  #   case_final.rds, paper-tables/, provenance.md
├── tests/                         # test-01 method properties; test-02 reproduction
├── env/                           # requirements.md, runtimes.csv, session-info.txt
└── results/  tables/  figs/       # generated output, never hand-edited
Where the data come from

There is no download step. Steps 1 and 4 call gcf::sim_grid and gcf::bio_grid and write verbatim CSV copies into data/ so the inputs can be inspected without R. Two things ship in the archive because they cannot be regenerated quickly: data/case_final.rds (the paper's computed case result object — hours of compute) and data/paper-tables/ (the published table CSVs the checks compare against). data/provenance.md documents all of it.

Main configuration file

config/project-config.R is the single file a reader edits. Every value in it is the paper's setting; nothing else in the pipeline hard-codes a column name, a buffer or a seed.

config/project-config.R (key lines)
# -- Simulation --------------------------------------------------------------
SIM_VARS    <- c("x1", "x2", "x3");  SIM_Y <- "y1";  SIM_COORDS <- c("x", "y")
SIM_BUFFERS <- c(2, 4, 6)          # buffer radii, grid units
SIM_PROBS   <- seq(0, 1, 0.1)      # 11 quantile levels for the Zx sweep
SIM_D_NORM  <- 4                   # normalisation distance (middle buffer)
SIM_FINE    <- 2;  SIM_BROAD <- 6  # reduction bands {2} / {6}
SIM_L       <- 6                   # spatial-block side

# -- Case study ---------------------------------------------------------------
CASE_BUFFERS <- seq(20, 100, 10)   # 9 buffer radii, km
CASE_PROBS   <- seq(0, 1, 0.05)    # 21 quantile levels
CASE_D_NORM  <- 100                # km
CASE_FINE    <- c(20, 30);  CASE_BROAD <- c(90, 100)
CASE_L       <- 132                # block side, km (2 x variogram range 66)

# -- Selection (rf_imp + spatial-block stability + group voting) --------------
SEL_B    <- 80L;   SEL_FRAC  <- 0.7;  SEL_PI   <- 0.6
SEL_KTOP <- 20L;   SEL_TREES <- 200L; SEL_SEED <- 1L

# -- Cross-validation ---------------------------------------------------------
K_FOLDS <- 5L;  SEED_SHUF <- 36L;  SEED_RANDOM <- 1L

# -- Learners -----------------------------------------------------------------
LEARNERS <- c("RF", "Cubist", "GLMNET", "LM", "XGBoost", "SVM", "KNN")
RF_TREES <- 500L                   # trees of the RF learner (not the selector)

# -- Compute ------------------------------------------------------------------
CORES <- 5L   # wall clock only: every selection re-seeds its own RNG
ParameterDefaultOrigin / rationale
*_BUFFERS{2,4,6} / 20–100 kmthe multiscale neighbourhood series; in the case study it spans roughly one to five grid-cell diameters up to beyond the variogram range
*_PROBS11 / 21 levelsthe quantile sweep of the Zx operator; the reduction compresses it, so the exact count is not delicate
*_FINE, *_BROAD{2}/{6}, {20,30}/{90,100}the two a-priori scale bands of the functional reduction — local structure vs regional context
SEL_B, SEL_FRAC80, 0.7stability resamples: 80 draws of 70 % of the spatial blocks
SEL_PI0.6group fire-frequency threshold: a (covariate × category) group must fire in 60 % of resamples
SEL_KTOP, SEL_TREES20, 200the rf_imp kernel: top-20 impurity features from a 200-tree ranger forest
*_L6 / 132 kmspatial-block side = 2 × the residual variogram range (66 km in the case study)
SEED_SHUF36the paper's fold-shuffle seed; with SEED_RANDOM = 1 and SEL_SEED = 1 it makes the whole validation deterministic
RF_TREES500the RF learner of the comparison — distinct from the 200-tree selection kernel
CORES5parallelises the per-fold selections; every selection re-seeds its own RNG, so this changes the wall clock, never a number

Free parameters and the published choices they follow.

2.3 Data Contract

GCF needs one plain table: projected coordinates, a response at the sampled rows, and covariates at every row where you want features. Unlike a purely tabular method, the coordinates are load-bearing — buffers, blocks and folds are all defined on them.

ColumnConfig keyTypeMeaning
coordinates*_COORDSnumeric, projected, in the buffer unitsthe case uses xkm/ykm (EPSG:3577 Australian Albers ÷ 1,000, so buffers are in km); the sim uses unit grid coordinates
response*_Ynumeric, at sampled rowsused only by the selection and the learners — gcf_field() never sees it
covariates*_VARSnumericthe variables to expand; a light cleaning (median-impute non-finite values, drop zero-variance columns) runs before ψ/Zx, and the raw values enter the X columns

Required schema. In the demo the sim table is 900 × 6 and the case table 6,229 × 19 (958 observed), both written to data/ by the pipeline. Provenance and units are documented in data/provenance.md.

lonlatxkmykmrichnessobservedElevationSlopePrecipitation
116.525−34.951−1405.95−3901.9247.6TRUE200.264411192.7
116.615−34.951−1397.83−3900.9337.2TRUE610.520810785.8

The first two rows of data/bio-grid.csv — 6,229 cells at 10 km over the Southwest Australian Floristic Region, vascular plant species richness aggregated from 4,989 plots, observed flagging the 958 cells with samples, and 12 covariates (geography, climate/environment, soil; nine elided for width). Observed richness: mean 36.1, range 1.4–107.5.

The shipped result object

Element of data/case_final.rdsContentUsed by
p1, bestper-model dual five-fold CV (7 learners × 2 partitions); adopted learner "RF"step 5 → Table 5
p2feature-set comparison, contribution attribution, Shapley decompositionstep 5 → Tables 6–7
predictionboth full-grid surfaces (6,229 cells: pred_x, pred_gcf)step 6 → fig06
gcf_cols, S_spthe 21-variable full-data selection and the five per-fold spatial selectionsstep 4's overlap check
regAregC, configthe three impact analyses (not rebuilt here) and the run configuration (seed 36, L = 132 km)reference

The paper's case result object, carried over unchanged. Rebuilding it is the paper's run_case.R — hours on 12 cores — which is exactly what this tutorial does not ask of you.

Three contract rules that bite
  • The support is part of the definition. ψ and Zx at location v are computed from the rows you pass in — the neighbourhood is whatever the table contains within the buffer. Features built on the observed cells only are different variables from features built on the full grid (step 4 measures the consequence: 19/21 selection overlap). Build the field once on every location you will ever predict, then subset rows for training — and never build features chunk by chunk, because a chunk boundary truncates neighbourhoods.
  • Coordinates must be projected, in the buffer units. Buffers, block sides and d_norm are Euclidean distances on the coordinate columns. Degrees of longitude are not kilometres; project first (the case data ship with Albers km for exactly this reason).
  • Keep the buffer series integer-valued. An implementation quirk inherited from the paper's engine: buffer series with mixed decimal widths (e.g. c(1.5, 3)) break the reduction's name matching, and gcf_reduce() fails early with a clear message. All paper settings are integer buffers.

3 Reproduction Pipeline, Results & Validation

3.1 Pipeline Execution

Full runRscript run-all.R — ~140 s measured
VerificationRscript run-all.R test — 28 checks, ~16 s. Do this first
Output foldersresults/ tables/ figs/ data/
terminal
cd GCF                     # this folder
Rscript run-all.R test     # 1. 28 checks on the method and on the committed results
Rscript run-all.R          # 2. full pipeline

Rscript run-all.R 30 50    # re-run single steps (10 20 30 40 50 60 90)
StepWhat it doesSeconds
10-sim-field.Rsim feature field (ψ + Zx + reduction, 93 candidates) + one B = 80 full-data selection, Figs. 1–218.9
20-sim-cv.Rdual five-fold design + ten B = 80 per-fold selections, Fig. 320.9
30-sim-permodel.R7 learners × base/GCF × 2 partitions → Table 2 + byte-check, Fig. 49.5
40-case-field.Rcase feature generation on 958 cells (996 ψ / 2,268 Zx / 372 candidates) → Table 4 + demo selection, Fig. 582.8
50-case-tables.RTables 5–7 from case_final.rds + byte-checks0.0
60-case-prediction.Rboth prediction surfaces + cross-section, Fig. 60.1
90-tables.RLaTeX tables and the session record0.1
totalR 4.6.0 on an Apple-silicon laptop, CORES = 5 for the selections132.3

Measured wall-clock time, from env/runtimes.csv, written by the run itself. The two big items are the case feature generation (~60 s) plus its B = 80 demo selection (~26 s), and the sim's ten per-fold selections. Every selection re-seeds its own RNG, so CORES is a wall-clock knob only.

Console output
Project : gcf-reproduce
Domain  : sim: 30 x 30 synthetic grid | case: SWAFR plant species richness
== Step 10  Simulation: GCF candidate field ==============
   sim_grid: 900 cells, response y1, covariates x1, x2, x3
   gcf_field (psi + Zx + reduction) took 5.3 s
   candidates: 93  [X (raw) 3 | P (pattern) 60 | D (context) 30]
   gcf_select on all 900 cells (B = 80) took 12.3 s
   selected 8 variables: x1, x2, x3, x1_D_fine_med, x2_D_fine_med,
                         x2_P_gc, x3_D_fine_med, x3_P_gc
== Step 20  Simulation: CV folds + per-fold selection ====
   random folds: 180/180/180/180/180
   spatial folds (25 blocks): 180/180/180/180/180
   selected set sizes — spatial: 8/8/8/8/8 | random: 8/8/8/8/8
== Step 30  Simulation: per-model dual five-fold CV ======
   RF 5.8 s · Cubist 1.4 s · GLMNET 0.2 s · LM 0.0 s
   XGBoost 0.9 s · SVM 0.2 s · KNN 0.1 s
   best learner by spatial GCF RMSE: LM
   Table 2 vs paper: IDENTICAL byte-for-byte (max |diff| = 0)
== Step 40  Case study: GCF feature generation ===========
   bio_grid: 6229 cells, 958 observed (richness > 0)
   gcf_field on 958 cells (9 buffers, 21 quantiles) took 59.4 s
   candidates: 372  [X (raw) 12 | P (pattern) 240 | D (context) 120]
   Table 4 vs paper: IDENTICAL byte-for-byte
   demo selection: 21 variables (19 also in the paper's 21-variable set)
== Step 50  Case study: rebuild Tables 5-7 ===============
   table05-case-permodel vs paper: IDENTICAL byte-for-byte
   table06-case-featuresets vs paper: IDENTICAL byte-for-byte
   table07-case-shapley vs paper: IDENTICAL byte-for-byte
== Step 60  Case study: prediction surfaces ==============
   cross-section: 93 cells along lat -33.01 (ykm = -3654)
Finished in 132.3 s

3.2 Core Analytical Steps

Six steps: purpose → code → output → result → how to read it. Steps 1–3 are the simulation (fully re-run); steps 4–6 are the case study (feature demo + rebuild). Each maps one-to-one onto a numbered script.

Step 1 · R/10-sim-field.R

Simulation — build the GCF candidate field

What this step does

Loads the packaged 30 × 30 simulation grid (response y1 with smooth spatial structure; three covariates that are nearly unstructured at point level), writes a verbatim CSV copy, and runs the whole feature transform in one call: gcf_field() computes the eleven ψ operators over buffers {2, 4, 6}, the Zx quantiles at 11 levels, and the functional reduction with bands {2}/{6} — 93 candidates from 3 covariates. It then runs one full-data B = 80 selection as a first look at what GCF keeps.

Code

R — from R/10-sim-field.R
field <- gcf::gcf_field(sim, coords = c("x", "y"), vars = c("x1", "x2", "x3"),
                        buffers = c(2, 4, 6), probs = seq(0, 1, 0.1),
                        d_norm = 4, include_vario_exp = TRUE,
                        vario_buffers = c(2, 4, 6),
                        fine_band = 2, broad_band = 6)
# 900 x 93 candidates: field$candidates, field$meta, field$psi, field$zx

bid <- gcf::gcf_blocks(sim[, c("x", "y")], 6)      # 25 blocks of 36 cells
sel <- gcf::gcf_select(field, y = sim$y1, blocks = bid, B = 80)

Example output

Selected variableCategoryForcedSelection frequency
x1, x2, x3X (raw)yes
x1_D_fine_medD (context)no1.000
x2_D_fine_medD (context)no1.000
x2_P_gcP (pattern)no1.000
x3_D_fine_medD (context)no1.000
x3_P_gcP (pattern)no1.000

results/sim-selected-features.csv — the full-data selection keeps 8 of 93 candidates: the three covariates (forced) plus five derived features, every one kept in all 80 stability resamples. The fine-band context median of each covariate is selected — exactly the feature family panel (e) of fig02 shows recovering the response's spatial structure.

Maps of the simulated response y1 and covariates x1 to x3 on the 30 by 30 grid, demo run
Demo run fig01 — the simulation design: the response (a) carries smooth spatial structure; the covariates (b–d) look close to noise at point level. Point values of x explain y only partly, and the missing part is spatial — the designed challenge GCF is built for.
Covariate x1 traced through psi, Zx and the reduced candidates, demo run
Demo run fig02 — x1 traced through the method: raw (a), ψ LISA and log local variance at the finest buffer (b, c), the buffer-6 neighbourhood median (d), and two reduced candidates (e, f). Compare (d)–(e) with fig01(a): the response's structure emerges from the covariate's neighbourhood distribution.
How to read this result
  • Feature generation is deterministic arithmetic. No RNG anywhere in ψ/Zx/reduction (asserted in test-01), which is half of why this page can promise byte-identical tables.
  • The reduction is the honesty step. 3 covariates → 87 ψ + 99 Zx = 186 raw features would invite overfitting and collinearity; band-averaging to 93 interpretable candidates happens before any response is seen, so nothing about it can leak.
  • The selection is unanimous here. Frequency 1.0 on all five derived features means every one of 80 block-subsamples ranked them top-20 — the simulation's signal is strong and stable. The case study (step 4) shows the marginal-group behaviour instead.
Step 2 · R/20-sim-cv.R

Simulation — the dual five-fold design and per-fold selection

What this step does

Builds both partitions of the 900 cells — random five-fold (seed 1) and spatial-block five-fold (25 blocks of side 6, whole blocks round-robined to folds after a seed-36 shuffle) — then re-runs the B = 80 selection inside every training fold of both partitions: ten selections in all. The GCF arm of step 3 uses these per-fold sets, so no validation cell ever influences the features it is predicted with.

Code

R — from R/20-sim-cv.R and R/01-helpers.R
fold_sp <- gcf_folds_rr(bid, K = 5L, seed = 36L)   # whole blocks -> folds
fold_rd <- gcf_folds_random(nrow(sim), K = 5L, seed = 1L)

# leakage-safe: one selection per training fold (x2 partitions = 10)
S_sp <- lapply(1:5, function(k)
  gcf::gcf_select(field, y = sim$y1, blocks = bid,
                  train = which(fold_sp != k), B = 80)$selected)

Example output

Random and spatial-block five-fold maps of the simulation grid, demo run
Demo run fig03 — the two partitions. Random assignment (a) measures interpolation: every validation cell has training neighbours. Spatial-block assignment (b) measures transfer: whole 6 × 6 blocks are held out, so a validation cell's entire neighbourhood is unseen. The paper reports both, and the gap between them is itself informative.
How to read this result
  • All ten selections return 8 variables — the same three covariates plus five derived features per fold (results/sim-selected-by-fold.csv). Stability under refolding is the selection design doing its job.
  • Per-fold selection is the leakage discipline. Selecting once on all data and then cross-validating would let validation rows vote on the features. The cost is honest: ten B = 80 selections are ~80 % of this step's 21 seconds.
  • Blocks respect geography, folds respect blocks. test-01 asserts every block lands entirely inside one fold — the property that makes spatial-block CV a transfer test rather than a leaky variant of random CV.
Step 3 · R/30-sim-permodel.R

Simulation — seven learners, base vs GCF (paper Table 2)

What this step does

Runs the paper's per-model comparison: every learner fits twice per fold — once on the three raw covariates, once on that fold's re-selected GCF set — under both partitions. Learner settings are ported verbatim from the paper's engine, and every fit starts from set.seed(1), so each fit is a pure function of its inputs. The finished wide table is then compared byte-for-byte against the published Table 2 shipped in data/paper-tables/.

Code

R — from R/30-sim-permodel.R
X <- as.matrix(field$candidates)     # the paper's engine used a matrix
x_cols <- field$meta$feature[field$meta$category == "X"]

for (ln in LEARNERS) for (pt in c("random", "spatial")) {
  fold <- fold_of[[pt]]; Sb <- S_of[[pt]]
  b <- gcf_cv(X, y, fold, function(k) x_cols, ln)                 # baseline
  g <- gcf_cv(X, y, fold, function(k) Sb[[as.character(k)]], ln)  # GCF
  # per-fold mean/sd of R2 and RMSE + relative improvements -> p1
}
t2 <- gcf_permodel_wide(p1, best)     # the paper's Table-2 shape
compare_with_paper(F_TABLE02, "data/paper-tables/table02-sim-permodel.csv")

Result — Table 2, reproduced byte-for-byte

LearnerBest Random CV — R²Random CV — RMSE Spatial CV — R²Spatial CV — RMSE
baseGCFimp.% baseGCFimp.% baseGCFimp.% baseGCFimp.%
LM*0.5140.70036.00.7000.55021.40.4690.66842.40.7060.55122.0
GLMNET0.5140.70036.20.7010.55021.40.4700.66842.10.7070.55222.0
RF0.4570.69752.60.7400.55225.40.4190.65756.50.7410.56124.3
Cubist0.4760.67842.30.7270.57021.60.4380.63745.60.7280.57521.0
KNN0.4670.68446.50.7340.56523.00.4290.63748.50.7360.57821.5
XGBoost0.4450.67752.10.7480.57123.70.3990.61955.20.7540.59021.8
SVM0.4720.64536.70.7300.59818.00.4330.59136.60.7340.62415.0

results/table02-sim-permodel.csvbyte-identical to the published Table 2 (results/sim-permodel-check.csv lists all 84 numeric cells side by side; every difference is 0). Learners sorted by spatial GCF RMSE; the asterisk marks the adopted learner. Improvements are relative percentages.

Dumbbell chart of cross-validation RMSE, base versus GCF, for seven learners under both partitions, demo run
Demo run fig04 — Table 2 as a picture: RMSE of the covariate baseline (grey) and GCF (green) per learner, spatial-block (a) and random (b) CV. Every learner moves left; the linear models move furthest left of all.
Why the match is exact, not approximate

Four links in a chain: (1) the gcf package is a verbatim port of the paper's engine, validated to max|diff| = 0 on ψ, Zx, candidates and selections; (2) folds use the paper's seeds (36 / 1); (3) every selection re-seeds seed = 1; (4) every learner fit starts from set.seed(1) with the paper's exact settings. Same inputs, same code, same package versions — same bytes. If a future learner-package release moves a third decimal, results/sim-permodel-check.csv will name the exact cells.

How to read this result
  • All seven learners improve, under both partitions. That pattern — not any single number — is the paper's claim: the gain is attributable to the representation, not to a lucky algorithm.
  • GCF-based LM wins. Spatial RMSE 0.551 against 0.706 for baseline LM. The features encode the spatial structure before fitting, so a simple linear map on top suffices — and the baseline's own ordering (LM 0.470 spatial R² vs RF 0.419) shows there was little residual nonlinearity for trees to exploit.
  • The spatial gain exceeds the random gain (RF +56.5 % vs +52.6 % R²): neighbourhood features transfer to unseen blocks better than point values do — the property that matters for mapping.
Step 4 · R/40-case-field.R

Case study — real feature generation on the 958 observed cells

What this step does

Runs the identical gcf_field() call the paper uses — 9 buffers from 20 to 100 km, 21 quantile levels, bands {20,30}/{90,100} km, d_norm = 100 — on the 958 observed cells of the SWAFR grid (support = the observed cells; the paper's production run uses the full 6,229-cell grid as support). It rebuilds the paper's Table 4 from the resulting field and then runs one B = 80 selection with 132 km blocks, comparing the outcome with the paper's full-support selection.

Code

R — from R/40-case-field.R
obs <- bio_grid[bio_grid$observed, ]           # 958 sampled cells
field <- gcf::gcf_field(obs, coords = c("xkm", "ykm"), vars = CASE_VARS,
                        buffers = seq(20, 100, 10), probs = seq(0, 1, 0.05),
                        d_norm = 100, include_vario_exp = TRUE,
                        vario_buffers = seq(20, 100, 10),
                        fine_band = c(20, 30), broad_band = c(90, 100))
# 958 x 372 candidates; psi 958 x 996; Zx 958 x 2268

bid <- gcf::gcf_blocks(obs[, c("xkm", "ykm")], 132)   # 38 occupied blocks
sel <- gcf::gcf_select(field, y = obs$richness, blocks = bid, B = 80)

Result — Table 4, rebuilt byte-for-byte

CategorySymbolRaw countReduced countConstruction
Covariates (x)x1212raw covariates (forced)
Spatial pattern (ψ)psi99624011 operators, band-averaged
Neighbourhood distribution (Zx)Zx2,2681205 quantile functionals × 2 scale bands
Total3,276372

results/table04-case-predictor-categories.csvbyte-identical to the published Table 4. The counts depend only on the settings (12 covariates × 83 ψ columns; 12 × 9 buffers × 21 quantiles; 12 × (20 P + 10 D) + 12), so they rebuild exactly even on the observed-cell support.

The demo selection against the paper's full-support selection

AgreementVariables
Selected in both (19)the 12 covariates (forced), plus Slope_P_lvar_broad, Precipitation_D_broad_hitail, DistBuilt_D_broad_med, SoilC_D_broad_iqr, SoilClay_D_broad_lotail, SoilDepth_D_broad_hitail, SoilpH_D_broad_med
Demo only (2)Slope_D_broad_skew, Precipitation_P_lvar_fine
Paper only (2)Precipitation_P_lvar_broad, DistBuilt_P_poutlier_z_broad

results/case-selected-observed-demo.csv — 21 variables selected on the observed-cell field, 19 shared with the paper's 21-variable full-support set. Note what the two disagreements are: the Precipitation local-variance group fires in both runs but contributes a different band's representative, and two genuinely marginal groups swap. The stable core — the seven broad-band context features — survives the support change untouched.

Observed richness, precipitation, and four GCF features mapped over the 958 observed SWAFR cells, demo run
Demo run fig05 — the demo field over the observed cells (10 km cells drawn at 14 km for visibility; axes are EPSG:3577 Albers km): observed richness (a), the strongest covariate precipitation (b), its ψ log local variance at the 20 km buffer (c), the fine-band pattern candidate built from it (d), and two context candidates the paper's selection also keeps — the fine-band median of distance to built areas (e) and the fine-band 10th quantile of soil clay (f).
The support caveat, quantified

Feature values here are not the paper's: Zx at an observed cell summarises only the observed neighbours, not the full grid. That is why this step's selection is a demonstration, not a reproduction — and the 19/21 overlap is a fair measure of how much the support choice moves the outcome on these data. For production use, build the field on every location you will ever predict (§2.3, rule 1); the numbers the paper reports all come from the full-support field inside case_final.rds.

Step 5 · R/50-case-tables.R

Case study — Tables 5, 6 and 7, rebuilt from the result object

What this step does

Reads data/case_final.rds — the paper's computed four-part validation, carried over unchanged — and turns it into the paper's three case tables with the paper's own formatting code (the same gcf_permodel_wide(), renames and rounding). Nothing is refit; the step takes under a second, and each CSV is compared byte-for-byte against the published table.

Result — Table 5 (per-model, case)

LearnerBest Random CV — R²Random CV — RMSE Spatial CV — R²Spatial CV — RMSE
baseGCFimp.% baseGCFimp.% baseGCFimp.% baseGCFimp.%
RF*0.4410.4440.717.11417.0430.40.2960.34616.717.96617.4283.0
Cubist0.4310.4350.917.23017.1810.30.2830.34221.118.06517.5063.1
KNN0.4270.4321.317.32117.2320.50.2960.3208.218.03917.7431.6
XGBoost0.4010.4286.917.70417.2912.30.2610.31520.318.30417.7952.8
SVM0.3980.4195.417.75017.4181.90.2750.32718.618.48917.8493.5
GLMNET0.3500.39312.318.45117.8223.40.2590.30216.918.57518.0542.8
LM0.3490.39513.218.46417.8013.60.2530.28613.018.64718.2792.0

results/table05-case-permodel.csvbyte-identical to the published Table 5. All seven learners improve under both partitions; RF is adopted (smallest spatial GCF RMSE). Spatial-block R² improvements run from 8.2 % (KNN) to 21.1 % (Cubist).

Result — Tables 6 and 7 (feature sets and Shapley)

Feature setsdRMSEsdΔR²%ΔRMSE%
x0.2960.14217.9662.13800
x+ψ0.3370.11217.5002.18713.72.6
x+Zx0.3290.11917.6132.41511.22.0
GCF (x+ψ+Zx)0.3460.10817.4282.49716.73.0

Table 6 (results/table06-case-featuresets.csv, byte-identical) — RF, spatial-block CV, per-fold selected sets decomposed by category.

CategoryShapley φShareTotal R²
x (covariates)0.1580.4560.346
ψ (spatial pattern)0.0860.248
Zx (neighbourhood distribution)0.1020.296

Table 7 (results/table07-case-shapley.csv, byte-identical) — exact three-player Shapley decomposition of the spatial-block R²; the three φ values sum to the GCF total by construction.

How to read this result
  • The two families are complementary. GCF (0.346) beats both x+ψ (0.337) and x+Zx (0.329) — neither family alone accounts for the full gain. The attribution file (results/case-attribution.csv: gain_P 2.594, gain_D 1.965, gain_full 2.999, shared −1.560) says the same in RMSE terms: the families overlap partly, and the joint gain exceeds either alone.
  • Together the derived features carry 54 % of the explained variance (Shapley shares 0.248 + 0.296) — on a study where the 12 covariates were chosen by experts precisely for their predictive value.
  • Random-CV gains are small here, spatial gains are not (RF +0.7 % vs +16.7 % R²). With 958 samples on a 10 km grid, interpolation is nearly saturated; the neighbourhood features earn their keep where it is hard — predicting unseen blocks.
Step 6 · R/60-case-prediction.R

Case study — the two prediction surfaces

What this step does

Maps the paper's Part-4 result: RF trained on all 958 observed cells — once on the covariates, once on the 21-variable full-support GCF set — predicting all 6,229 grid cells. Both finished surfaces live in case_final$prediction; this step draws them, the difference, and the paper's west–east cross-section, without refitting.

Example output

GCF and covariate RF prediction surfaces of species richness, their difference, and the cross-section at y equals minus 3654 km, demo run
Demo run fig06 — predicted species richness from GCF-RF (a) and covariate-RF (b) on a shared scale; their difference (c; blue = GCF higher, scale clipped at the 98th percentile of |difference|); and the cross-section at y = −3654 km (d), the paper's transect. Axes are EPSG:3577 Albers km (the paper's own maps use a Web-Mercator display projection; the values are identical).
SurfaceMeansdMinMaxNote
GCF prediction33.21115.3625.16490.002
covariate (RF) prediction33.56614.6036.68988.508r(GCF, base) = 0.953
difference (GCF − base)−0.3554.649−21.71416.607GCF higher in 44.4 % of cells

results/case-prediction-summary.csv. The two surfaces agree on the broad gradient (r = 0.953) and disagree exactly where neighbourhood information matters — the difference map is spatially organised, not noise.

How to read this result
  • Follow the transect. Along y = −3654 km the GCF surface keeps the high peaks (up to ~80) and the deep troughs that the covariate-only surface dampens — richer features let the learner commit to local extremes instead of hedging towards the regional mean.
  • The GCF surface has slightly larger spread (sd 15.36 vs 14.60) with the same mean level — consistent with preserved contrasts rather than added noise, given its lower cross-validated error in Table 5.
  • The difference map is the sampling map's shadow. The largest disagreements sit in the sparsely observed east and interior — where a validation block has few nearby samples and the neighbourhood features carry the most extra information.

3.3 Reading the Results Together

The expansion is disciplined12 covariates → 3,276 raw features → 372 candidates → 21 selected. Each compression step is leakage-free or fold-internal.
The gain is representation, not algorithm all 7 learners improve, in both experiments, under both partitions — 28 of 28 comparisons.
And it is a transfer gainsim RF spatial R² +56.5 %; case RF +16.7 % spatial vs +0.7 % random — the improvement concentrates where prediction is hard.
How to interpret — several angles
  • Mechanism. A learner fed point values must infer each location's spatial context from nothing; GCF hands it that context as columns — what the covariate's local pattern looks like (ψ) and what values surround the location (Zx). The simulation isolates the mechanism: covariates that look like noise at point level (fig01) carry the response's whole structure in their neighbourhood distributions (fig02 d–e).
  • Why the simple learners jump most. Once the representation is spatial, the mapping from predictors to response is close to linear — LM and GLMNET overtake RF in the sim, and close most of their gap in the case study. Expanding the data substitutes for expanding the model; this is the paper's primacy-of-data point.
  • The two families answer different questions. Context features (Zx → D) say where you are in the covariate's distribution — broad-band medians and tails dominate the case selection. Pattern features (ψ → P) say how organised the covariate is locally — local variance survives selection in both experiments. Shapley (Table 7) credits both, and Table 6 shows the union beating either alone.
  • Honest limits. The case gain at full sampling density is modest (3.0 % spatial RMSE) — the paper's own impact analyses locate the larger gains under covariate scarcity. Feature values depend on the support (19/21 selection overlap when it changes). And the improvement percentages are relative: RF's +16.7 % R² is 0.296 → 0.346, worth stating in absolute terms too.
  • What would falsify the method on your data. A per-model table where the GCF columns match the baseline within fold-to-fold noise, or a selection that keeps only the forced covariates — both would say your covariates' neighbourhoods add nothing, a perfectly publishable finding this pipeline reports without complaint.
Reading order for your own run

1) results/*-field-summary.csv — did the expansion produce the expected candidate counts. 2) results/*-selected-*.csv — what survived selection, at what frequency, and are the forced covariates all there. 3) results/table02… / table05… — is the GCF column ahead of base for most learners, especially under spatial blocks. 4) the feature maps — do the selected features look like spatial structure, not artefacts. 5) the prediction surfaces — does the difference map organise where your sampling is thin.

3.4 Validation

Rscript run-all.R test runs two scripts and 28 checks in about 16 seconds. They test different things, and it is worth knowing which is which.

ScriptChecksWhat it establishes
tests/test-01-method-properties.R17 Properties the method must have regardless of dataset, on a fast 100-cell sub-grid: determinism, quantile ordering, fold discipline, selection invariants.
tests/test-02-reproduce-pipeline.R11 That the committed results regenerate and match the paper: it rebuilds the sim field and one fold selection from scratch and re-checks every byte-identity.

Together they guard against two different failures: a wrong implementation, and a silently drifted result.

What test-01 pins down

  • Feature generation is deterministicgcf_field() twice under a deliberately hostile ambient seed returns identical candidates, and the X columns are the raw covariates unchanged.
  • The D functionals behave like quantiles — q0.10 ≤ median ≤ q0.90 at every location and the interquartile range is non-negative; and the band defaults (min/max buffer) equal the explicit sim bands.
  • The spatial design is exact — the 30 × 30 grid at L = 6 gives 25 blocks of 36 cells; round-robin folds keep whole blocks together, are deterministic for a fixed seed, and random folds are balanced to within one cell.
  • Selection invariants — every raw covariate is kept, the result is reproducible for a fixed seed, and every selected variable exists in the candidate field.
  • The CV helper is honest — a fold's RMSE from gcf_cv() equals a hand-fitted LM on the same split to 10⁻⁹.

What test-02 pins down

  • The sim field rebuilds from scratch to the committed structure (93 candidates; ψ 87, Zx 99) and matches the stored pipeline field exactly.
  • The fold-1 spatial selection re-derives to the committed set.
  • Table 2 is byte-identical to the published CSV, and Tables 5, 6, 7 rebuilt from case_final.rds are byte-identical too (checked as data frames to 10⁻¹² and as raw bytes).
  • Table 4's counts (12 / 996 / 2,268 / 3,276 raw; 12 / 240 / 120 / 372 reduced) hold, and the shipped prediction grid is intact (6,229 cells, both surfaces finite).
Checkpoint

28 checks pass — 17 on properties the method must satisfy on any data, 11 on the committed results regenerating and matching the paper byte-for-byte. If those hold, you are running the same engine that produced the paper's tables, and every re-run number on this page is reachable from your own machine.

4 Adaptation, Writing & Reproducibility

4.1 Port to Your Domain

GCF applies wherever you have a response measured at some locations and covariates measured over a grid or a dense point set with projected coordinates: soil properties, air quality, species richness, urban indicators, mineral prospectivity. The whole method is two calls:

R — minimal port
library(gcf)
field <- gcf_field(mydata, coords = c("xkm", "ykm"),
                   vars = my_covariates,
                   buffers = my_buffers,          # e.g. 1-5 cell diameters
                   fine_band = head(my_buffers, 2),
                   broad_band = tail(my_buffers, 2))
blocks <- gcf_blocks(mydata[, c("xkm", "ykm")], size = 2 * variogram_range)
sel <- gcf_select(field, y = mydata$y[train], blocks = blocks, train = train)
X <- as.matrix(field$candidates)[, sel$selected]   # -> any learner

What to edit

  1. config/project-config.R — coordinates, response, covariates (CASE_* block), then the spatial settings below. Nothing else.
  2. *_BUFFERS — a series from roughly one cell diameter up to at or beyond the response's variogram range. Integer values (§2.3, rule 3).
  3. *_FINE / *_BROAD — the two ends of your buffer series; the defaults (min / max buffer) are a sensible start.
  4. *_L — spatial-block side ≈ 2 × the residual variogram range, so validation blocks sit beyond short-range dependence. Fit a variogram to the covariate-detrended response to get the range, as the paper does.
  5. Selection settings (SEL_*) — the paper's B = 80, 0.7, π = 0.6, K = 20 travel well; lower B while iterating, restore it for the run you report.
  6. Nothing else. Rscript run-all.R test, then Rscript run-all.R.

Choosing what to expand

  • Every covariate must exist at the prediction locations — features are computed there too. Remote-sensing, terrain, climate and distance-to-feature layers are ideal; sample-only lab measurements cannot enter.
  • Give the field the full support once. Build features on observed + prediction locations together, then subset rows for training. Features built per subset are different variables (step 4 measured the drift), and chunked builds truncate neighbourhoods at chunk boundaries.
  • Let the selection do the pruning. The candidate field is deliberately generous (31 candidates per covariate); the stability selection with group voting is the part designed to cope with that. Pre-screening candidates by hand mostly re-implements it, worse.
  • Watch scale mismatches. If your buffers are all far below the process scale, the Zx features approach the point values and add little; if far above, they approach global constants. The buffer series should straddle the variogram range.
Common pitfalls

Report the buffer series, the bands, B, π, K, the block side L and every seed — a GCF result without its settings cannot be checked. Keep coordinates projected in the buffer units (degrees are not kilometres). Re-select inside every training fold rather than once on all data — the pipeline's gcf_S_by_fold() exists so that leakage is structurally impossible, and the difference is not cosmetic. Do not compare base and GCF arms on different folds or different learner settings; the paper's design shares both. And quote per-fold mean ± sd, not a single pooled number — the spatial-block sd is large by design, and hiding it overstates certainty.

4.2 Write the Paper

The published study has a clean architecture worth copying: define the feature families, prove the mechanism on a simulation, then validate on the real case with a per-model table, a feature-set decomposition, and prediction surfaces.

Manuscript sectionTemplate outputWriting job
Methodsconfig/project-config.R, §1.2define ψ and Zx, the reduction functionals, the selection (B, π, K, group voting) and the dual-CV design with L = 2 × range; state that selection runs inside folds
Datadata/*.csv, data/provenance.mdthe response, the covariates and their sources, the grid resolution, and the support the features were computed on
Results — the fieldresults/*-field-summary.csv, Table 4 shape, feature mapsraw vs reduced counts per category, and two or three mapped features that make the expansion concrete
Results — selectionresults/*-selected-*.csvwhat survived, with selection frequencies; name the stable core and the marginal groups
Main table (per-model)results/table02…/table05…, tables/*.tex, fig04all learners × both partitions, base vs GCF, sorted by spatial GCF RMSE; the claim is the pattern, not one cell
Results — decompositionTables 6–7, results/case-attribution.csvfeature-set comparison and Shapley shares; state complementarity (GCF > either family alone)
Results — predictionfig06, case_final$predictionboth surfaces, the difference map, and a cross-section that shows preserved contrasts
Discussion§3.3the mechanism, where the gain concentrates (transfer, scarcity), and the support caveat

Where each manuscript element draws its material from.

Manuscript skeleton

manuscript outline
1  Introduction        — spatial prediction constrained by heterogeneity and
                          sparse sampling; methods grow model complexity while
                          underusing the covariates' own spatial structure (aims)
2  Generalized covariate field
                        — 2.1 spatial pattern features (11 operators x buffers)
                          2.2 neighbourhood distribution features (quantiles)
                          2.3 functional reduction (bands; D and P candidates)
                          2.4 selection: rf_imp + block stability + group voting
                          2.5 validation: dual five-fold CV, L = 2 x range
3  Simulation          — design; per-model table; why linear learners win
4  Case study          — data; the field (Table-4 shape); selection;
                          per-model table; feature sets + Shapley; prediction
5  Discussion          — representation vs model complexity; transfer gains;
                          support and scale choices; limits
6  Conclusions         — 6-8 sentences answering the aims

4.3 Final Reproducibility Package

Codenumbered scripts in R/ + run-all.R + config/project-config.R + tests/
Datadata(sim_grid) and data(bio_grid) from gcf 0.1.0, plus case_final.rds and the published-table CSVs, with data/provenance.md
Resultsresults/*.csv + figs/fig01–fig06 (PNG + vector PDF) + tables/*.tex + env/

What is in the download, and what is not

Included in gcf-code-and-data.zipDeliberately excluded
run-all.R, config/project-config.R, all nine scripts in R/, both scripts in tests/, every CSV in results/, five LaTeX tables in tables/, all of env/, data/ with the two input CSV copies, case_final.rds, case-coords.csv, paper-tables/ and provenance.md, and a README.md data/derived/ (intermediates the run regenerates) and figs/, since every figure is redrawn by the step that owns it and the rendered versions are on this page

The two input CSVs are included for inspection even though the same tables ship inside the gcf package — the package is the authoritative source, and the pipeline reads from it, not from the CSVs.

Before claiming reproducible
  • Rscript run-all.R test passes — 28 checks, including the byte-identity of Tables 2, 5, 6 and 7 against the published CSVs.
  • Delete results/, tables/, figs/ and data/derived/, re-run Rscript run-all.R — every re-run number and figure on this page regenerates (~140 s).
  • The buffer series, bands, B, π, K, L and all three seeds you report match config/project-config.R.
  • The exact package versions are named. The byte-exact learner comparison depends on them, and results/sim-permodel-check.csv is the drift detector.
  • Per-fold mean ± sd is reported, with the per-fold construction stated (fold-internal selection, shared folds across arms).
  • env/session-info.txt is included in the deposit.
Anticipate the reviewer

Isn't this just feature engineering? → it is feature engineering made systematic, spatial and leakage-free: a fixed operator catalogue, an a-priori reduction, and a selection that runs inside every fold — all specified before any response is seen, all reproducible to the byte. Why not let a deep model learn the neighbourhoods? → the per-model table is the answer: the same gain appears in seven learners including plain LM, so the information was in the representation, not the capacity — and 958 samples rarely feed a spatial network. Is the selection stable? → selection frequencies are reported per feature (1.0 across the board in the sim), the group-voting design targets exactly the collinear-dilution failure, and refolding leaves the sets unchanged. Does it survive spatial validation? → the gains are larger under spatial blocks than under random folds, in both experiments. Why is the case gain modest? → at full sampling density interpolation is nearly saturated; the paper's impact analyses show the gain growing under covariate scarcity, and the honest headline is the transfer setting (+16.7 % spatial R² for RF).

References & credits

Demo outputs were generated by the scripts in this folder using the gcf package and its bundled data; the case-study validation tables and surfaces come from the paper's computed result object, shipped here unchanged.

  1. Song Y (2026). Generalized covariate field (GCF): spatial-pattern and neighbourhood-distribution feature expansion improves geospatial prediction. International Journal of Geographical Information Science 40:1–29. doi:10.1080/13658816.2026.2729719 · PDF (source of the method and of every published number cross-checked on this page)
  2. Song Y. gcf: Generalized Covariate Field. R package, version 0.1.0 CRAN submission in preparation (the reference implementation — a verbatim, validated port of the paper's analysis engine — and the source of both data sets)
  3. GCF code, data and examples — github.com/yongzesong/gcf (the standalone R implementation gcf.R, the simulation data and a worked example, as published with the paper)
  4. Mokany K, McCarthy JK, Falster DS, Gallagher RV, Harwood TD, Kooyman R, Westoby M (2022). Patterns and drivers of plant diversity across Australia. Ecography 2022(11):e06426. doi:10.1111/ecog.06426 (the vascular-plant richness observations behind the case study)
  5. Online GCF calculator — yongzesong.com/app/gcf/ (the same gcf feature engine compiled to WebAssembly, running entirely in the browser)