Reproducible models · Tutorial 10 of 12
Reproducing Geographically Optimal Similarity
A complete walkthrough of GOS — a spatial prediction model built on the geographical similarity principle, which borrows values from the observations whose geographical configuration most resembles the unknown location, and from no others. From the packaged Leonora trace-element data to a manuscript, with every number regenerable by one command.
To cite the GOS model and its R package and codes in publications, please use:
Song Y (2023). Geographically Optimal Similarity. Mathematical Geosciences 55:295–320. doi · PDF
Song Y, Lyu W. geosimilarity: Geographically Optimal Similarity. R package. CRAN · vignette
R/ pipeline scripts · config/project-config.R · run-all.R · tests/ · results/ · tables/ · env/ · a README.md. Unzip and run Rscript run-all.R test from the GOS/ folder root. The input data are not in the archive — both tables ship inside the geosimilarity package and step 1 writes CSV copies of them for you.
Upload a sample CSV (and, optionally, a prediction grid), pick the response and the covariates, and the app runs the same CRAN geosimilarity package compiled to WebAssembly: the κ search, the GOS prediction, the uncertainty maps and an MLR/BCS/GOS accuracy table, all downloadable as PNG, PDF and CSV. Nothing is uploaded to a server — the computation happens on your device.

1 Method Overview & Reproduction Scope
1.1 Core Idea
Most spatial prediction starts from distance. Kriging borrows from nearby samples; a regression borrows from a fitted equation. The geographical similarity principle — the Third Law of Geography — borrows from somewhere else entirely: similar geographical configurations produce similar values, wherever they happen to be. Two sites 60 km apart with the same terrain, vegetation, soil chemistry and distance to mining can inform each other better than two adjacent sites that differ in all of them.
A geographical configuration is just the vector of covariate values at a location. Similarity compares two such vectors directly — there is no regression coefficient anywhere in the model, and no explicit relationship between response and covariates is ever estimated. That is the point: the method describes the comprehensive degree of approximation of a geographical structure instead of an explicit relationship between variables.
The earlier basic configuration similarity (BCS) model applies that principle by averaging over every observation, weighted by similarity. GOS adds one idea and one parameter: at each unknown location, keep only the most similar κ share of observations and discard the rest. Observations with low similarity are not neutral — they are noise dressed up as evidence, and averaging them in drags every prediction towards the study-area mean.
- Research problem: similarity-based prediction uses all samples at every location, so large data sets cost more to compute and predict worse, because unrelated observations dilute the informative ones.
- One-line contribution: a cross-validated threshold λ that selects, at each unknown location, the small share of observations with optimal similarity — plus an uncertainty measure read straight off the retained similarities.
- Why it matters here: on the packaged Zn data, λ = 0.08 — 8 % of 885 observations, about 71 samples per cell — beats using all of them on MAE in 50 of 50 cross-validation splits, and lowers mean prediction uncertainty at ζ = 0.9 by 67 %.

Configuration = the covariate vector at a location → similarity = how closely two configurations match, composed with a minimum operator → BCS averages over all of them → GOS keeps only the best κ share, with κ chosen by cross-validation → and the similarity of what was kept is the uncertainty. Everything on this page serves that line.
R 4.1+ and one CRAN package, geosimilarity. Nothing else: the figures are
base graphics, the tables are written by hand-rolled helpers, and both input tables ship
inside the package, so the pipeline never touches the network. A full run takes
107 seconds on one core — almost all of it cross-validation.
1.2 Method Logic
GOS runs in four stages, and each stage is a small number of equations. Equation numbers below are the paper's, and every one of them is exercised by the pipeline in §3.
Eq. 1 · correlation + VIF
Eq. 2–6 ·
gos(kappa = 1) = BCSEq. 7–10 ·
gos_bestkappa()Eq. 11–12 ·
gos(kappa = λ)Stage 1 — characterising the geographical configuration (Eq. 1)
Two screens, in order. First keep the covariates significantly correlated with the response; then drop the most collinear one repeatedly until every variance inflation factor is under 4, the paper's conservative threshold. What survives is the configuration:
where ei is the value of covariate Xi at a given location. The configuration must be computable at the unsampled locations too, which is what rules out covariates you only measured at the samples.
Stage 2 — assessing similarity (Eq. 2–6)
For an unknown location vβ and an observation uα, each covariate contributes its own Gaussian similarity, and the covariate-scale similarities are composed into one number:
Three details in there decide everything downstream. σ is the standard deviation of the
covariate over the observation and prediction locations together, so the scale is set by
the whole study area, not by the samples alone. δ is a root-mean-square deviation that
makes the Gaussian bandwidth adaptive rather than fixed. And P is the minimum operator —
the paper adopts Zhu et al.'s min, so a single badly matched covariate is enough to
make two locations dissimilar no matter how well the others agree. Similarity is a conjunction,
not an average.
Collecting the similarities of one unknown location against every observation gives a vector (Eq. 5), and the similarity-weighted mean of the observed values is the BCS prediction:
Stage 3 — determining the optimal similarity (Eq. 7–10)
κ is the share of observations retained at each prediction location, defined as one minus the quantile probability τ of the similarity vector:
κ = 1 keeps everything and is exactly BCS. For each candidate κ the observations are split 50/50, the held-out values are predicted with Eq. 6, and the cross-validation error is recorded; repeating the split many times and taking the minimum gives λ:
The similarity vector is then truncated to the observations above the corresponding similarity threshold Sλ (Eq. 10). λ is a property of the data set, not of a location: one number is searched once and used everywhere.
Stage 4 — prediction and uncertainty (Eq. 11–12)
The GOS prediction is the same weighted mean as Eq. 6, taken over the retained observations only:
and the uncertainty is one minus a quantile of the retained similarities:
with ζ ∈ {0.9, 0.95, 0.99, 0.995, 0.999, 1}. This is the model's most elegant move: the uncertainty needs no error model and no second pass, because the quality of the evidence is already in the similarities. At ζ = 1 the expression collapses to 1 − max(similarity) — "how good is the single best match?" — and at that setting GOS and BCS give identical answers, since truncating the vector never removes its maximum. The pipeline reproduces that identity exactly (§3.2, Step 4).

1.3 Reproduction Scope
Everything tagged Demo run regenerates from this folder with
Rscript run-all.R; everything tagged Published reference is a static image
from the article. The demo runs end to end on R 4.6.0 with
geosimilarity 3.9. Three things are worth stating plainly before any number
below is quoted.
- The two geological covariates are missing. The paper's Table 2 selects nine variables for Zn, and the two strongest are distance to Zn-related lithology (R = −0.428) and distance to Zn-related faults (R = −0.350), both derived from the Surface Geology of Australia. Neither ships with the package. The packaged tables carry only the nine natural and social covariates, of which the strongest here is NDVI at R = −0.398.
- n = 894, not 966. The packaged
zntable is a subset of the Zn samples behind the published tables. - Three models, not five. The paper benchmarks GOS against ordinary kriging,
multivariate linear regression, regression kriging and BCS. This tutorial compares
MLR, BCS and GOS — the three that need no variogram, which keeps the whole
pipeline inside
geosimilarityand base R. The two kriging rows of the paper's Table 3 are not reproduced here at all, and no claim on this page rests on them.
So the published Table 3 values do not reproduce digit-for-digit, and this page does not pretend otherwise.
What does reproduce is the whole GOS estimation path and the qualitative findings: the correlation-and-VIF screen (which lands on exactly the covariate set the package vignette uses), the κ search of Eqs. 7–9 and its characteristic curve shape, the Eq. 11 prediction over all 13,132 grid cells, the Eq. 12 uncertainty at all six ζ levels including the exact GOS = BCS identity at ζ = 1, the collapse of BCS predictions towards the mean that GOS avoids, and the accuracy ordering GOS < BCS < MLR on both MAE and RMSE.
| Item | Demo template | Published reference |
|---|---|---|
| Data | 894 Zn samples and a 13,132-cell 1 km grid, 9 covariates each
(data(zn) and data(grid), shipped with the package) |
966 Zn samples and 9 covariates including two geological distances, Leonora mining region, Western Australia |
| Method | gos(), gos_bestkappa(), removeoutlier() from
the author's package |
Song (2023) §2, Eqs. 1–12 |
| Tables | results/response-summary.csv, variable-selection.csv,
kappa-rmse.csv, model-comparison.csv (+ a no-screening
variant) |
paper Tables 1, 2 and 3 (Table 3 partially — MLR, BCS and GOS rows only) |
| Figures | fig01–fig06 from R/10 … R/50 |
paper Figs 1, 3, 4, 7, 9, 10, 11 embedded as reference images |
| Not attempted | — | ordinary and regression kriging (Table 3); the geological variable construction (Fig. 5); the local singularity analysis of Eq. 14 |
What the template regenerates versus what is shown as published reference.
How to use this page
- To reproduce the demo: run
Rscript run-all.R test, thenRscript run-all.R. Every number and figure below regenerates in under two minutes. - To try GOS without installing anything: the online GOS calculator runs the same CRAN package in your browser, on this same example data or on a CSV of your own.
- To use GOS on your own data: §2.3 gives the data contract — a sample table and a prediction table sharing covariate columns — and §4.1 the porting steps.
2 Setup, Structure & Data Contract
2.1 Environment & Dependencies
This is the shortest dependency list in the series. The method is one CRAN package written by the article's author; everything else is base R.
# The method, and both input data sets
install.packages("geosimilarity")
| Package | Role in the pipeline | Required? |
|---|---|---|
geosimilarity 3.9 | gos() for Eqs. 2–6 and 11–12, gos_bestkappa() for Eqs. 7–9, removeoutlier() for the screen, plus the zn and grid data | yes |
| base R graphics | all six figures, as PNG at 196 dpi and matching vector PDF | built in |
car | cross-checks the base-R VIFs in step 2 whenever it is installed | optional — nothing depends on it |
Package roles. VIFs use the identity VIFi = 1/(1 − R²i),
which is exactly what car::vif() returns for continuous main effects. That
equivalence is not taken on trust: the cross-check was run with car installed and
the two agree on all seven selected covariates to within 5 × 10⁻⁵, the rounding
of results/variable-selection.csv, and match the values the package vignette
reports. The verified pipeline session is recorded in env/session-info.txt:
R 4.6.0, aarch64-apple-darwin23, seed 42, one core.
2.2 Project Structure & Configuration
One entry script runs the five 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.
GOS/
├── gos.html # this guide
├── run-all.R # entry point: full run, single steps, tests
├── assets/ # figures shown in this guide (web copies)
├── config/project-config.R # the ONLY file to edit for a new domain
├── R/
│ ├── 00-config.R, 01-helpers.R # paths, IO, CV splits, base-R figure helpers
│ ├── 10-prepare-data.R # load zn + grid, log-transform, outlier screen
│ ├── 20-select-variables.R # correlation + VIF -> the configuration (Eq. 1)
│ ├── 30-best-kappa.R # the kappa search -> lambda (Eqs. 7-9)
│ ├── 40-gos-prediction.R # grid prediction + uncertainty (Eqs. 11-12)
│ ├── 50-model-comparison.R # MLR / BCS / GOS on identical CV splits
│ └── 90-tables.R # LaTeX tables + session info
├── data/ # written by step 10: verbatim CSV copies of the
│ # packaged zn and grid, plus provenance.md
├── tests/ # test-01 method properties; test-02 results regenerate
├── env/ # requirements.md, runtimes.csv, session-info.txt
└── results/ tables/ figs/ # generated output, never hand-edited
There is no download step and no data file to fetch. R/10-prepare-data.R calls
geosimilarity::zn and geosimilarity::grid and writes verbatim CSV
copies into data/ so the inputs can be inspected without R. That is also why the
downloadable archive is 46 KB and contains no data/ folder — the data arrive
with the package.
Main configuration file
config/project-config.R is the single file a reader edits. It is read by
R/00-config.R and used by every step; nothing else in the pipeline hard-codes a
column name, a threshold or a palette.
# -- 2. Input data -----------------------------------------------------------
RESPONSE <- "Zn" # the raw response column (ppm)
RESPONSE_LOG <- "logZn" # name given to the log-transformed response
LOG_TRANSFORM <- TRUE # paper Sect. 3.2, step 1
CANDIDATES <- c("Elevation", "Slope", "Aspect", "Water",
"NDVI", "SOC", "pH", "Road", "Mine")
# -- 3. Outlier screening ----------------------------------------------------
OUTLIER_COEF <- 2.5 # removeoutlier(): coef x IQR of the log response
# -- 4. Variable selection (paper Sect. 2.2.1) -------------------------------
COR_ALPHA <- 0.05 # keep covariates significantly correlated with y
VIF_THRESHOLD <- 4 # the paper's conservative multicollinearity limit
# -- 5. Optimal similarity threshold (Eqs. 7-9) ------------------------------
KAPPA_GRID <- c(seq(0.01, 0.10, 0.01), seq(0.2, 1.0, 0.1))
BESTKAPPA_NREPEAT <- 10 # cross-validation repeats inside gos_bestkappa()
BESTKAPPA_NSPLIT <- 0.5 # training share of each split
# -- 6. Model comparison (paper Table 3) -------------------------------------
CV_REPEATS <- 50 # the paper's 50 repeated 50/50 splits
CV_MODELS <- c("MLR", "BCS", "GOS")
# -- 7. Compute --------------------------------------------------------------
CORES <- 1 # one core: reproducible, and matches the web app
SEED <- 42
| Parameter | Default | Origin / rationale |
|---|---|---|
LOG_TRANSFORM | TRUE | the paper's step 1: trace-element data are right-skewed, so they are log-transformed before anything else |
OUTLIER_COEF | 2.5 | the loose screen the package vignette documents. The paper deliberately keeps high values, so step 5 reports the comparison both ways |
VIF_THRESHOLD | 4 | the paper's conservative threshold (§2.2.1); 10 is the permissive alternative |
KAPPA_GRID | 19 values | fine (0.01 steps) below 0.1 where the minimum sits, coarse (0.1 steps) above it where the curve is nearly flat |
BESTKAPPA_NREPEAT | 10 | the paper's own illustration uses 10 repeated 50/50 splits |
CV_REPEATS | 50 | the paper's Table 3 protocol; this is the knob to turn if the run needs to be faster |
CORES | 1 | gos() can fork, but one core keeps the run reproducible and matches the browser app, which has no multiprocessing |
Free parameters and the published choices they follow.
2.3 Data Contract
GOS needs two plain tables: the sample table, carrying the response and the covariates at the observation locations, and the prediction table, carrying the same covariates wherever you want a value. Neither needs to be a spatial object.
| Column | Config key | In samples | In grid | Type | Meaning |
|---|---|---|---|---|---|
| response | RESPONSE | required | — | numeric, strictly positive if LOG_TRANSFORM | the variable being predicted (demo: Zn, ppm) |
| covariates | CANDIDATES | required | required, same names | numeric | the geographical configuration of Eq. 1; at least two must survive selection |
Lon, Lat | — | optional | optional | numeric | used only to draw maps — never by the model |
GridID | — | — | optional | any | carried through to the output so predictions can be joined back |
Required schema. In the demo the sample table is 894 × 12 and the grid is
13,132 × 12, both written to data/ by step 1. Provenance and units are
documented in data/provenance.md.
Units, and why they matter less than you would think
In the demo, distances (Water, Road, Mine) are in
kilometres, Elevation in metres, Slope and Aspect in
degrees, NDVI and pH dimensionless, and SOC is a mass
fraction. Equation 3 divides every deviation by that covariate's own σ, so a covariate's
units cancel and rescaling one from metres to kilometres changes nothing. What does matter
is that a covariate be measured the same way in both tables — a sample column in metres against a
grid column in kilometres is a silent, catastrophic error the model cannot detect.
- No coordinate reference system is needed, or used. Similarity is computed from covariates alone (Eqs. 2–4) — no distance matrix, no spatial weights, no variogram. Projected or geographic, sorted or shuffled, the predictions are identical. Coordinates are needed only to draw the result. The flip side is real: any spatial structure not carried by a covariate is invisible to the model.
- Standardisation depends on the prediction set. σ in Eq. 3 is computed over
observation and prediction locations together, and δ in Eq. 4 divides by the
number of prediction locations. So predicting a subset of your grid does not
give the same numbers as predicting all of it, and the results are not chunkable.
tests/test-02re-predicts all 13,132 cells for exactly this reason. - NAs are not handled. A missing value in any covariate, in either table, raises an
error from the quantile step inside
gos(). A missing value in the response is worse: it propagates silently and every prediction comes backNA. Complete your cases before you call the model.
| Lon | Lat | Zn | Elevation | Slope | NDVI | SOC | pH | … |
|---|---|---|---|---|---|---|---|---|
| 120.0523 | −28.4551 | 10 | 455.3352 | 0.2360 | 0.1835 | 0.9092 | 5.9487 | … |
| 120.0554 | −28.4145 | 30 | 450.5336 | 0.2073 | 0.2023 | 0.9061 | 6.0522 | … |
The first two rows of data/zn-samples.csv — 894 rows, the Zn response
in ppm and nine covariates (Aspect, Water, Road and
Mine elided here for width); the grid table has the same covariate columns plus
GridID. SOC and pH shown to four decimals; the CSV is verbatim.
3 Reproduction Pipeline, Results & Validation
3.1 Pipeline Execution
| Full run | Rscript run-all.R — 107 s measured |
|---|---|
| Verification | Rscript run-all.R test — 42 checks, ~40 s. Do this first |
| Output folders | results/ tables/ figs/ data/ |
cd GOS # this folder
Rscript run-all.R test # 1. 42 checks on the method and on the committed results
Rscript run-all.R # 2. full pipeline
Rscript run-all.R 30 40 # re-run single steps (10 20 30 40 50 90)
| Step | What it does | Seconds |
|---|---|---|
10-prepare-data.R | load zn + grid, log-transform, outlier screen, Fig. 1 | 0.3 |
20-select-variables.R | correlation and VIF screens (Eq. 1) | 0.0 |
30-best-kappa.R | 19 κ values × 10 repeated 50/50 splits (Eqs. 7–9), Fig. 2 | 31.8 |
40-gos-prediction.R | two full-grid gos() calls (Eqs. 11–12), Figs. 3–5 | 10.0 |
50-model-comparison.R | 3 models × 50 repeats, run twice, plus a second κ search, Fig. 6 | 64.9 |
90-tables.R | LaTeX tables and the session record | 0.0 |
| total | one core, R 4.6.0 on an Apple-silicon laptop | 107.0 |
Measured wall-clock time, from env/runtimes.csv, written by the run
itself. Almost all of it is cross-validation; the two full-grid predictions together take about
ten seconds. CV_REPEATS is the knob if this needs to be faster.
Project : gos-zn
Domain : trace element (Zn) concentration, Leonora mining region, WA
== Step 1/5 Prepare data ======================================
zn: 894 samples x 12 columns
grid: 13132 prediction cells x 12 columns
outlier screen (coef = 2.5): 9 of 894 rows dropped, 885 retained
Shapiro-Wilk W on log Zn (screened): 0.9860
== Step 2/5 Select variables ==================================
VIF screen kept 7 of 7 covariates (max VIF 2.608)
selected: NDVI, SOC, Mine, Water, pH, Slope, Road
vignette: Slope, Water, NDVI, SOC, pH, Road, Mine
the two sets are identical
== Step 3/5 Optimal similarity threshold ======================
kappa grid: 19 values from 0.01 to 1.00, 10 repeats of a 50/50 split
lambda = 0.08 (cross-validation RMSE 0.6668)
RMSE at kappa = 1 (BCS) is 0.6729 — GOS lowers it by 0.91%
only 8% of the 885 observations are used at each prediction location
== Step 4/5 GOS prediction on the 1 km grid ===================
885 observations -> 13132 grid cells, kappa = 0.08
GOS predictions: mean 3.353, sd 0.365, range 2.396-4.302 (log Zn)
BCS predictions: mean 3.403, sd 0.138, range 2.396-4.075 (log Zn)
BCS spread is 38% of the GOS spread — the clustering the paper reports
at zeta = 1 the two models agree to 0.00e+00, as the paper notes
== Step 5/5 Model comparison ==================================
[screened] 885 samples, 50 repeats, 442 training / 443 testing rows each
MLR MAE 0.5456 RMSE 0.6865 GOS reduces MAE +5.8%, RMSE +4.2%
BCS MAE 0.5220 RMSE 0.6653 GOS reduces MAE +1.5%, RMSE +1.1%
GOS MAE 0.5140 RMSE 0.6578
RMSE ranking, screened : GOS < BCS < MLR
RMSE ranking, unscreened: GOS < BCS < MLR
Finished in 107.0 s
3.2 Core Analytical Steps
Six stages: purpose → code → output → result → how to read it. Steps 4 and 5 both come
out of R/40-gos-prediction.R; the rest map one-to-one onto the numbered scripts.
Prepare the response — log-transform, then screen
What this step does
Loads the two packaged tables, writes verbatim CSV copies of them, log-transforms the
response because trace-element data are strongly right-skewed, and applies
removeoutlier() to the transformed values. Both the screened and the
unscreened sample sets are kept, because the paper and the package vignette disagree about
whether to screen at all, and step 5 reports the comparison both ways.
Code
zn <- as.data.frame(geosimilarity::zn) # 894 x 12, no download
grid <- as.data.frame(geosimilarity::grid) # 13,132 x 12
zn$logZn <- log(zn$Zn) # paper Sect. 3.2, step 1
out_idx <- geosimilarity::removeoutlier(zn$logZn, coef = 2.5)
d <- zn[-out_idx, ] # 885 rows retained
Example output
| Series | n | Mean | Min | Median | Max | σ | CV |
|---|---|---|---|---|---|---|---|
| Zn, ppm — all samples | 894 | 42.55 | 2 | 29 | 353.5 | 37.11 | 0.872 |
| log Zn — all samples | 894 | 3.436 | 0.693 | 3.367 | 5.868 | 0.801 | 0.233 |
| Zn, ppm — outliers screened | 885 | 41.64 | 5 | 29 | 181 | 32.95 | 0.791 |
| log Zn — outliers screened | 885 | 3.438 | 1.609 | 3.367 | 5.198 | 0.772 | 0.225 |
results/response-summary.csv, in the shape of the paper's Table 1.
The highlighted row is the sample set every later step uses. The paper reports 966 Zn
samples with mean 43.52, median 30, max 353.5, σ 38.2 and CV 0.88 — the same population
seen through 72 fewer samples, with an identical maximum.

- The transform is doing the work, not the screen. The log takes CV from 0.872 to 0.233; the outlier screen then removes 9 rows and moves CV only from 0.233 to 0.225. Order matters — screening raw ppm values would have thrown away far more.
- The screen is a documented disagreement, not a default. The package vignette
applies
removeoutlier(coef = 2.5); the paper explicitly does not, because high trace-element values "may indicate the clusters of mineral deposits". Rather than pick a side silently, step 5 runs the whole comparison on both sample sets. The ranking is identical either way. - Everything downstream is in log units. The predictions, the MAE and the RMSE
are all log Zn. The back-transformed
pred_zn_ppmcolumn is a plainexp(pred)with no smearing or lognormal bias correction — fine for mapping, not for a mass balance.
Characterise the geographical configuration (Eq. 1)
What this step does
Runs the paper's two-stage screen over all nine candidate covariates: keep those significantly correlated with log Zn, then prune the most collinear one repeatedly until every VIF is below 4. Whatever survives is the geographical configuration that every similarity in Eqs. 2–4 is computed from.
Code
# stage 1 — correlation with the log-transformed response
ct <- stats::cor.test(d$logZn, d[[v]]) # for each candidate v
sig <- cors$variable[cors$p < 0.05]
# stage 2 — iterative VIF pruning at the paper's threshold of 4
pr <- vif_prune(d, sig, VIF_THRESHOLD) # VIF_i = 1 / (1 - R2_i)
selected <- pr$keep
Example output
| Variable | r with log Zn | p | VIF | Selected | Also in vignette |
|---|---|---|---|---|---|
| NDVI | −0.398 | 5.4 × 10⁻³⁵ | 1.460 | yes | yes |
| SOC | −0.317 | 4.7 × 10⁻²² | 1.356 | yes | yes |
| Mine | −0.259 | 5.4 × 10⁻¹⁵ | 2.608 | yes | yes |
| Water | −0.244 | 2.1 × 10⁻¹³ | 1.232 | yes | yes |
| pH | −0.241 | 3.8 × 10⁻¹³ | 1.568 | yes | yes |
| Slope | +0.230 | 4.1 × 10⁻¹² | 1.651 | yes | yes |
| Road | −0.219 | 4.9 × 10⁻¹¹ | 2.273 | yes | yes |
| Aspect | −0.055 | 0.0998 | — | no — not significant | no |
| Elevation | +0.037 | 0.273 | — | no — not significant | no |
results/variable-selection.csv, in the shape of the paper's
Table 2. Seven of nine candidates pass the correlation screen and all seven survive
the VIF screen unchanged — nothing is dropped for collinearity, and the maximum VIF is
2.608 against the paper's 2.97 for Zn.
The seven selected covariates — Slope, Water, NDVI, SOC, pH, Road, Mine — are
exactly the set the geosimilarity vignette uses in its example
formula. Here they were arrived at from the data, by the paper's own screen, rather than
assumed. tests/test-02 asserts that equality, so if a future package version
changed the data the check would fail rather than quietly shifting every number on this
page.
- These are weak correlations, and that is normal for this method. The strongest is NDVI at −0.398; a linear model on all seven explains only a modest share of log Zn. GOS does not need a strong marginal relationship, because it never fits one — it needs covariates that jointly discriminate between locations. Comparing it against MLR in step 6 is precisely a test of whether that distinction is worth anything.
- The screen is where the missing geology shows up. In the paper, the two strongest Zn covariates are distances to Zn-related lithology (−0.428) and faults (−0.350), and neither exists in the packaged data. The overlapping variables agree closely with the published values — NDVI −0.398 vs −0.346, SOC −0.317 vs −0.312, Water −0.244 vs −0.259, pH −0.241 vs −0.232, Slope 0.230 vs 0.233, Road −0.219 vs −0.215 — so the difference is one of coverage, not of computation.
- Aspect fails for a real reason. Aspect is circular: 359° and 1° are neighbours that a Pearson correlation and a Gaussian similarity both read as opposites. It would have needed a sin/cos decomposition to be usable, and the screen drops it before that becomes a problem.
Find the optimal similarity threshold λ (Eqs. 7–9)
What this step does
Scores 19 candidate κ values by repeated 50/50 cross-validation and takes the minimum. This is the paper's actual contribution, reduced to one function call and one number.
Code
f <- logZn ~ NDVI + SOC + Mine + Water + pH + Slope + Road
bk <- geosimilarity::gos_bestkappa(f, data = d,
kappa = c(seq(0.01, 0.10, 0.01), seq(0.2, 1.0, 0.1)),
nrepeat = 10, nsplit = 0.5, cores = 1)
lambda <- bk$bestkappa # 0.08
curve <- bk$cvmean # one mean RMSE per candidate kappa
Example output

| κ | 0.01 | 0.02 | 0.05 | 0.07 | 0.08 | 0.09 | 0.10 | 0.20 | 0.50 | 1.00 |
|---|---|---|---|---|---|---|---|---|---|---|
| CV RMSE | 0.6944 | 0.6797 | 0.6687 | 0.6670 | 0.6668 | 0.6670 | 0.6673 | 0.6691 | 0.6724 | 0.6729 |
Ten of the 19 rows of results/kappa-rmse.csv. λ = 0.08
with RMSE 0.6668; κ = 1, which is BCS, gives 0.6729, so choosing a threshold lowers
cross-validation RMSE by 0.91 %. Only 8 % of the 885 observations —
about 71 samples — contribute at each prediction location.
- The left arm of the curve is the more interesting half. RMSE falls 0.6944 → 0.6668 between κ = 0.01 and 0.08 — nine samples per cell is too few, and the prediction is unstable. The right arm rises only 0.6668 → 0.6729. Under-using the data costs about four times as much as over-using it, which is worth knowing before you pick a coarse κ grid.
- The minimum is shallow, and you should say so. Between κ = 0.05 and 0.10 the RMSE spans 0.6687 to 0.6673 — a 0.2 % range. λ is not sharply identified, and with a coarser grid or a different covariate set it moves easily. That is exactly why the paper's 0.04 and this run's 0.08 are the same finding: only a few per cent of observations are needed.
- λ is deterministic.
gos_bestkappa()seeds each repeat internally (1…nrepeat), so every candidate κ is scored on identical splits and the answer does not depend on the outerset.seed().tests/test-01asserts this by running the search twice under deliberately different outer seeds. - κ = 1 is BCS, exactly. The last point on the curve is not an approximation of the older model — it is the older model. That is what makes the comparison in step 6 fair: one implementation, one code path, one parameter changed.
The online GOS calculator runs this exact search — the same
CRAN geosimilarity package, compiled to WebAssembly — on the same example
data or on a CSV of your own, and draws this same curve with λ marked. No R installation,
nothing uploaded to a server. It is the fastest way to see how the curve reacts to a
different covariate set before committing to a pipeline run.
Predict the grid, and map the uncertainty (Eqs. 11–12)
What this step does
One gos() call predicts log Zn at all 13,132 grid cells at
κ = λ = 0.08 and returns the Eq. 12 uncertainty at all six ζ levels in
the same tibble. A second call at κ = 1 produces the BCS counterpart used in step 5.
Together they take about ten seconds.
Code
g <- geosimilarity::gos(f, data = d, newdata = grid,
kappa = lambda, cores = 1)
# g holds: pred, uncertainty90, uncertainty95, uncertainty99,
# uncertainty99.5, uncertainty99.9, uncertainty100
b <- geosimilarity::gos(f, data = d, newdata = grid, # BCS, for step 5
kappa = 1, cores = 1)
Example output


| ζ | 0.9 | 0.95 | 0.99 | 0.995 | 0.999 | 1 |
|---|---|---|---|---|---|---|
| GOS mean Θ | 0.0724 | 0.0545 | 0.0332 | 0.0295 | 0.0265 | 0.0258 |
| BCS mean Θ | 0.2203 | 0.1656 | 0.0801 | 0.0602 | 0.0351 | 0.0258 |
| Reduction by GOS | 67.1 % | 67.1 % | 58.5 % | 51.0 % | 24.5 % | 0 % |
Mean Eq. 12 uncertainty over the 13,132 cells, computed from
results/grid-prediction.csv and grid-prediction-bcs.csv. The paper
reports a 28.5–74.1 % reduction over ζ = 0.9 to 0.999; this run gives
24.5–67.1 % — same magnitude, same direction.
- ζ = 1 is an exact identity, and it holds to machine precision. At
ζ = 1, Eq. 12 becomes 1 − max(similarity), and truncating a
vector never removes its maximum — so GOS and BCS must agree exactly even though
their predictions differ everywhere. The largest difference across all 13,132 cells
is 0.00e+00. That is a genuine test of the implementation, not a
coincidence, and
tests/test-01checks it on synthetic data too. - The gap between GOS and BCS shrinks as ζ rises, for a mechanical reason. At ζ = 0.9, BCS's quantile falls in the long tail of poor matches it is obliged to keep, so its uncertainty is triple. By ζ = 0.999 both models are reading their few best matches, and the reduction collapses to 24.5 %. Report the ζ you used, or the number means nothing.
- Uncertainty here means "was there anything to learn from?", not a variance. A cell can have a confident prediction with high Θ if it is simply unlike everything observed. This is an extrapolation warning rather than a confidence interval, and it is available at no extra cost because it falls out of the similarities already computed.
σ in Eq. 3 is computed over the observation and prediction locations together, and
δ in Eq. 4 divides by the number of prediction locations. Splitting
newdata into blocks therefore changes both, and the numbers drift. This is a
property of the method, not a bug: tests/test-02 re-predicts all 13,132 cells
from scratch rather than checking a sample of them, and thinning the grid would shift the
values slightly too.
Why the threshold matters — BCS collapses towards the mean, GOS does not
What this step does
Puts the κ = 1 prediction on the same colour scale as the κ = λ one, and draws the distribution of all 13,132 predictions from both. Two views of a single fact.
Example output

| Prediction set | Mean | σ | Range (log Zn) | Range (ppm) | Within ±0.1 of the observed mean |
|---|---|---|---|---|---|
| GOS (κ = 0.08) | 3.353 | 0.365 | 2.396 – 4.302 | 11.0 – 73.9 | 12.8 % |
| BCS (κ = 1) | 3.403 | 0.138 | 2.396 – 4.075 | 11.0 – 58.8 | 70.0 % |
Computed from results/grid-prediction.csv and
grid-prediction-bcs.csv. BCS's spread is 38 % of GOS's, and
70 % of its cells sit within ±0.1 of the observed mean of 3.438 against
12.8 % of GOS cells — a 5.5-fold concentration.

- This is the paper's Fig. 10 finding, reproduced cleanly and with no caveats. Of everything on this page it is the most robust: it needs no cross-validation, no accuracy metric and no held-out sample — it is visible in the predictions themselves, on 13,132 cells.
- Averaging over everything is a shrinkage estimator you did not ask for. Weight 885 observations by similarities that are mostly small and mostly similar to each other, and the weighted mean converges on the unweighted one. The informative observations are outvoted. Discarding 92 % of them is what restores the local signal.
- Both models share a lower bound. Both ranges start at exactly 2.396 — some locations are dominated by the same single best-matching observation under either rule. The divergence is at the top: BCS reaches 4.075 where GOS reaches 4.302, so the high-Zn areas are precisely where BCS's dilution costs the most, and those are the areas a mining study cares about.
- Spread is not accuracy. A model could scatter its predictions wildly and be worse. Step 6 is what closes that loop: the extra contrast comes with lower error, not higher.
Accuracy comparison — MLR, BCS and GOS on identical splits
What this step does
Scores three predictors of log Zn over 50 repeated 50/50 splits, the paper's protocol. Splits are generated once and shared, so every model sees exactly the same training and testing rows in every repeat and the differences between models are paired rather than merely averaged. The whole comparison is then run a second time on all 894 unscreened samples, with λ re-derived for that sample set so GOS is not handicapped by a threshold tuned elsewhere.
Code
one_repeat <- function(d, idx, lam) {
tr <- d[idx, ]; te <- d[-idx, ]
p_mlr <- stats::predict(stats::lm(f, data = tr), newdata = te)
p_bcs <- geosimilarity::gos(f, tr, te, kappa = 1, cores = 1)$pred
p_gos <- geosimilarity::gos(f, tr, te, kappa = lam, cores = 1)$pred
...
}
splits <- cv_splits(nrow(d), CV_REPEATS, CV_SPLIT, SEED) # shared by all three
Example output
| Model | MAE | sd | RMSE | sd | MAE reduction by GOS | RMSE reduction by GOS |
|---|---|---|---|---|---|---|
| MLR — multivariate linear regression | 0.5456 | 0.0124 | 0.6865 | 0.0154 | 5.81 % | 4.18 % |
| BCS — basic configuration similarity (κ = 1) | 0.5220 | 0.0125 | 0.6653 | 0.0174 | 1.54 % | 1.12 % |
| GOS — geographically optimal similarity (κ = 0.08) | 0.5140 | 0.0119 | 0.6578 | 0.0183 | — | — |
results/model-comparison.csv — 885 outlier-screened samples,
λ = 0.08, 50 repeated 50/50 splits, all errors in log Zn units. RMSE ranking
GOS < BCS < MLR. The paper reports, for Zn, GOS reducing MAE by 2.6 % and
RMSE by 0.9 % against BCS, and 6.0 % / 1.2 % against MLR: same direction, same
order of magnitude, with the MAE-against-MLR figure almost exact.

Robustness — the paper's own preprocessing
The vignette screens outliers; the paper explicitly does not. Re-running everything on all 894 samples, with λ re-derived on that sample set (it comes out at 0.08 again), gives:
| Model | MAE | sd | RMSE | sd | MAE reduction by GOS | RMSE reduction by GOS |
|---|---|---|---|---|---|---|
| MLR | 0.5640 | 0.0130 | 0.7184 | 0.0168 | 5.66 % | 3.69 % |
| BCS | 0.5401 | 0.0150 | 0.6994 | 0.0185 | 1.48 % | 1.08 % |
| GOS | 0.5321 | 0.0136 | 0.6919 | 0.0185 | — | — |
results/model-comparison-no-outlier-screening.csv — 894 samples,
no screening, λ re-derived = 0.08, 50 repeats. Every error rises by roughly 0.02–0.03,
because the nine retained extremes are genuinely hard to predict, but the ordering
GOS < BCS < MLR and the size of GOS's advantage are unchanged. The
preprocessing choice does not carry the result.
Averages can hide a model that wins narrowly on most splits and loses badly on a few.
These do not. On the 885 screened samples, GOS has the lower error than MLR on
50/50 splits by MAE and 50/50 by RMSE, and than BCS on 50/50 by MAE
and 47/50 by RMSE. On the 894 unscreened samples: 50/50 and 50/50
against MLR, 49/50 and 47/50 against BCS. Counted from
results/model-comparison-repeats.csv, which holds all 300 per-repeat rows.
- The GOS-vs-BCS gap is small, consistent and the point of the paper. 1.5 % MAE is not dramatic, but it is the same 1.5 % on essentially every split, and it comes with a 92 % cut in the observations used per prediction and the restored map contrast of step 5. Cheaper, sharper and slightly more accurate.
- The GOS-vs-MLR gap is what justifies the similarity framing. Both models see exactly the same seven covariates. MLR insists on an explicit linear relationship; GOS never estimates one, and still predicts better by 5.8 % MAE on all 50 splits. The comparative structure of a configuration carries information that a fitted coefficient does not.
- MAE gaps exceed RMSE gaps throughout. 5.81 vs 4.18 % against MLR, 1.54 vs 1.12 % against BCS. GOS's advantage is in typical accuracy rather than in the worst cases — expected, since the hardest cells are the ones with no similar observation anywhere, which is precisely what its uncertainty map flags.
- What this comparison does not say. It says nothing about kriging, which is not run here (§1.3). On these covariates GOS beats a linear model and beats averaging over everything; whether it beats a geostatistical model on this data set is a question this tutorial does not answer.
3.3 Reading the Results Together
| A threshold exists, and it is small | λ = 0.08: 8 % of 885 observations per cell. Cross-validation RMSE 0.6668 against 0.6729 for κ = 1. |
|---|---|
| It restores the map | GOS σ 0.365 vs BCS 0.138; 12.8 % of GOS cells near the mean against 70.0 % of BCS cells. |
| And it lowers error and uncertainty | MAE 0.5140 vs 0.5220 (BCS) and 0.5456 (MLR); mean Θ down 67.1 % at ζ = 0.9. |
- Mechanism. Similarity weights are strictly positive and mostly small, so a weighted mean over all 885 of them is dominated by the crowd rather than by the evidence. Truncating at λ turns a soft weighting into a hard selection, and only then do the informative observations decide the answer. Everything else on this page — the contrast in fig05, the lower error in fig06, the lower Θ in step 4 — is a consequence of that one move.
- The similar samples need not be nearby. Nothing in Eqs. 2–4 refers to a coordinate. The 71 observations that predict a given cell are chosen for their configuration, and may be scattered across the study area. That is the Third Law working as advertised, and it is also why GOS can predict sensibly in a region with few local samples but a well-represented environment.
- Uncertainty is an extrapolation flag, not an error bar. Θ says "how similar was the best evidence available here", not "how wrong is this number likely to be". Two cells with the same prediction can have very different Θ, and it is the high-Θ ones you would sample next.
- Honest limits. The configuration is everything: any driver you did not measure is invisible, and GOS cannot recover it from geometry the way a kriging model can, because it never looks at geometry. λ sits in a shallow trough here (0.2 % RMSE across κ = 0.05–0.10), so report the curve rather than the single number. The comparison covers three models, not the paper's five. And the back-transform to ppm is naive.
- What would falsify the method on your data. A κ–RMSE curve that decreases monotonically to κ = 1. That would say no threshold helps and BCS is the right model — a perfectly publishable finding, and one this pipeline reports without complaint.
1) results/variable-selection.csv — did anything survive both screens, and is
the maximum VIF under 4. 2) figs/fig02 — does the κ curve have a minimum away
from 1, and how shallow is it. 3) figs/fig05 — does thresholding restore contrast
your BCS map had lost. 4) results/model-comparison.csv together with
model-comparison-repeats.csv — is GOS ahead on average and on most splits.
5) figs/fig03 panel (b) — where is the model extrapolating, and does that match
where your sampling is thin.
3.4 Validation
Rscript run-all.R test runs two scripts and 42 checks in about 40 seconds.
They test different things, and it is worth knowing which is which.
| Script | Checks | What it establishes |
|---|---|---|
tests/test-01-method-properties.R | 18 | Properties the method must have regardless of dataset, on a small synthetic case with no spatial structure at all — which is legitimate precisely because the model never uses coordinates. |
tests/test-02-reproduce-pipeline.R | 24 | That the committed CSVs regenerate. It repeats the expensive parts of steps 30–50
from scratch and compares against what is in results/. |
Together they guard against two different failures: a wrong implementation, and a silently drifted result.
What test-01 pins down
- Eq. 6 is transcribed and matched exactly. The test writes out Eqs. 2–6 by hand —
the Gaussian Ei, the σ over observation and prediction locations together,
the δ normalisation as the package computes it, the
minoperator, the weighted mean — and compares it withgos(kappa = 1). Maximum difference: 0.0e+00. That single check verifies the minimum-operator composition and the Eq. 6 weighted mean at once. - Eq. 12 behaves like a probability. All six uncertainty columns present, all values in [0, 1], non-increasing in ζ, and ζ = 1 the smallest of the six — with GOS and BCS identical there.
- Eq. 9 is deterministic.
gos_bestkappa()is run twice under deliberately different outer seeds and must return the same λ and the same curve to 10⁻¹². - Scale equivariance and range. Shifting and scaling the response shifts and scales every prediction identically, and predictions never leave the observed range — both necessary consequences of a weighted mean with positive weights.
- The helpers. The base-R VIF matches 1/(1 − R²), and the split generator is reproducible, disjoint, correctly sized, and refuses a degenerate split.
What test-02 pins down
- The packaged
znstill holds 894 rows and the screen still retains 885 — so a package update that changed the data would fail here rather than silently rewrite this page. - All nine correlations, the seven selected covariates (asserted equal to the vignette's set), and their VIFs.
- λ reproduces as 0.08 and the whole 19-point κ–RMSE curve matches to 10⁻⁶.
- All 13,132 grid predictions match to 10⁻⁶ and the ζ = 0.99 uncertainties to 10⁻⁷ — re-predicting the entire grid, because a subset would legitimately differ.
- Three cross-validation repeats reproduce for all three models to 10⁻⁸, the summary table averages its own per-repeat file, and GOS beats BCS and MLR on both metrics in both preprocessing variants.
42 checks pass — 18 on properties the method must satisfy on any data, 24 on the committed results regenerating. If those hold, you are running the reference implementation as its author intended, and every number on this page is reachable from your own machine.
4 Adaptation, Writing & Reproducibility
4.1 Port to Your Domain
GOS needs no geometry, no neighbourhood and no variogram, so it travels further than most spatial methods: anywhere you have a response measured at some locations, and covariates measured everywhere. Soil properties, air quality, species abundance, house prices, disease incidence — the contract in §2.3 is the whole requirement.
What to edit
config/project-config.R—RESPONSE, andLOG_TRANSFORMif your response is right-skewed and strictly positive (check fig01 for your own data before deciding).CANDIDATES— every covariate available at both the samples and the prediction locations. Step 2 whittles the list down; you do not have to pre-select.KAPPA_GRID— keep it fine below 0.1, where the minimum usually sits, and coarse above. If your first run puts λ at the smallest grid value, extend the grid downwards before believing it.CV_REPEATS— 50 is the paper's protocol and costs about a minute here. Lower it while iterating, restore it for the run you report.- Nothing else.
Rscript run-all.R test, thenRscript run-all.R.
Choosing covariates
The configuration is the model. Three rules that follow from Eqs. 2–4 rather than from taste:
- Availability at the prediction locations is the binding constraint. A covariate you measured only at your samples is useless to GOS, however predictive. This is why remote sensing, terrain and distance-to-feature layers dominate every published configuration.
- Because P is the minimum, more is not better. Every added covariate can only lower each pairwise similarity, never raise it. A weak covariate does not dilute the signal the way it would in a regression — it actively suppresses matches. Let the VIF and correlation screens do their job, and prefer a compact configuration.
- Prefer covariates that carry the spatial structure you cannot otherwise use. The model never sees coordinates, so anything smooth in space that drives your response — a geological unit, a distance to a river, an elevation band — has to enter through a covariate or not at all. In the paper, the two strongest Zn covariates were distances to related lithology and faults, which are exactly such fields; their absence from the packaged data is the main reason this reproduction differs from the published one.
- Watch for circular and categorical variables. Aspect is dropped by the screen here, and rightly: a Gaussian on degrees treats 359° and 1° as far apart. Decompose it into sin/cos, or leave it out. Unordered categories have no meaningful squared difference at all.
What κ means for your sample size
κ is a share, so the number of observations actually used is κ × m — here 0.08 × 885 ≈ 71 per prediction. That has two consequences worth planning for.
| Sample size m | What to expect of λ | Practical note |
|---|---|---|
| small (m ≲ 100) | large, often approaching 1 | with 100 samples, κ = 0.08 leaves 8 observations — too few to be stable. The left arm of the curve in fig02 is exactly this failure mode. GOS may collapse to BCS, which is a legitimate answer |
| moderate (m ≈ 500–2,000) | a few per cent, as here | the regime the paper studies; the curve has a clear minimum and a shallow trough around it |
| large (m ≫ 5,000) | small, and the computational gain is the point | this is the case the paper opens with: BCS's cost grows with every added sample while its accuracy falls. Thresholding fixes both |
κ is a proportion, so the same κ means very different amounts of evidence at different sample sizes. Read λ together with κ × m.
Run the κ search first and look at the curve. If RMSE decreases monotonically to κ = 1, there is no optimal threshold on your data and BCS is the correct model — report that. If the minimum is real but the trough is very shallow, as it is here (0.2 % across κ = 0.05–0.10), λ is weakly identified: quote the curve, not just the number. And if your sample is small enough that κ × m is under about 20, the threshold is buying you noise. The online GOS calculator is the quickest way to answer this: load your CSV, run the κ search, and look at the shape before writing any pipeline code.
Report λ, the κ grid it was searched over, the number of repeats, and ζ — a Θ value without
its ζ is meaningless, and a λ without its grid cannot be checked. Make sure a covariate is
measured identically in both tables; the model cannot detect a unit mismatch. Do not predict
your grid in chunks or thin it after tuning, because σ and δ depend on the prediction set.
Complete your cases: covariate NAs error out, response NAs return all-NA
predictions in silence. Keep errors in the transformed units you modelled in, and treat any
back-transform as presentational. And do not tune λ on the same split you report accuracy on
— the pipeline derives λ once, then scores on 50 fresh splits.
4.2 Write the Paper
The published study has a clean architecture, and it is worth copying: define the four stages, characterise the configuration, show the threshold search, then predict, quantify uncertainty and benchmark.
| Manuscript section | Template output | Writing job |
|---|---|---|
| Methods | config/project-config.R, Eqs. 1–12 as set out in §1.2 | state the similarity function and that P is the minimum operator; define κ and ζ; say how λ was searched — grid, repeats, split ratio — and that it was derived once and reused |
| Data | results/response-summary.csv, figs/fig01, data/provenance.md | the response and its transform, the candidate covariates and where each comes from, and the fact that every covariate exists at the prediction locations |
| Results — configuration | results/variable-selection.csv, tables/table-variable-selection.tex | the correlation and VIF table, and what was dropped and why |
| Results — threshold | results/kappa-rmse.csv, figs/fig02 | the curve, λ, and the depth of the trough; state κ × m, not only κ |
| Results — prediction | figs/fig03, fig04, fig05, results/grid-prediction.csv | the map, the uncertainty at a named ζ, and the BCS contrast that shows why the threshold matters |
| Results — accuracy | results/model-comparison.csv + -repeats.csv, figs/fig06, tables/table-model-comparison.tex | the table, and the paired win counts — averages alone under-sell a consistent small margin |
| Discussion | the no-screening variant, §3.3 | the mechanism, the limits of a covariate-only model, and one robustness variant of your own preprocessing |
Where each manuscript section draws its material from.
Manuscript skeleton
1 Introduction — spatial prediction from similarity, not distance;
BCS uses every sample; the gap: which samples,
and how uncertain (3 aims)
2 Geographically optimal similarity
— 2.1 Characterising configurations (Eq. 1)
2.2 Assessing similarity (Eqs. 2-6; P = min)
2.3 The optimal threshold (Eqs. 7-10)
2.4 Prediction and uncertainty (Eqs. 11-12)
3 Case & data — study area, response and transform, covariates,
availability at the prediction locations
4 Results — 4.1 the selected configuration (correlation, VIF)
4.2 the kappa curve and lambda
4.3 prediction map
4.4 uncertainty across zeta
4.5 GOS against BCS: spread and accuracy
4.6 accuracy comparison, paired over splits
5 Discussion — why fewer samples predict better; what the
uncertainty does and does not mean; limits of a
covariate-only configuration
6 Conclusions — 6-8 sentences answering the aims
Results-to-manuscript map
| Manuscript element | Source file | Shown in |
|---|---|---|
| Response summary and transform | tables/table-response-summary.tex, figs/fig01 | §3 / §4.1 |
| Configuration table | tables/table-variable-selection.tex | §4.1 |
| Main figure (the κ curve) | figs/fig02, results/kappa-rmse.csv | §4.2 |
| Prediction and uncertainty maps | figs/fig03, figs/fig04 | §4.3 / §4.4 |
| BCS contrast | figs/fig05 | §4.5 |
| Main table (accuracy) | tables/table-model-comparison.tex, figs/fig06 | §4.6 |
| Robustness variant | tables/table-model-comparison-no-screening.tex | §5 / supplement |
Every manuscript element traces to one regenerable file.
4.3 Final Reproducibility Package
| Code | numbered scripts in R/ + run-all.R + config/project-config.R + tests/ |
|---|---|
| Data | data(zn) and data(grid) from geosimilarity 3.9, with data/provenance.md and the CSV copies step 1 writes |
| Results | results/*.csv + figs/fig01–fig06 (PNG + vector PDF) + tables/*.tex + env/ |
What is in the download, and what is not
Included in gos-code-and-data.zip (46 KB) | Deliberately excluded |
|---|---|
run-all.R, config/project-config.R, all eight scripts in
R/, both scripts in tests/, six CSVs in
results/, four LaTeX tables in tables/, all of
env/, and a README.md — 26 files in seven folders |
data/, because both input tables ship inside the
geosimilarity package and step 1 writes CSV copies of them on the first
run; the two grid-prediction CSVs (~1.5 MB each), which step 4 regenerates in ten
seconds; and figs/, since every figure is redrawn by the step that owns
it and the rendered versions are on this page |
The archive is 46 KB because the data are a package dependency rather than a file, which is also what makes this pipeline network-free.
Rscript run-all.R testpasses — 42 checks, includinggos(kappa = 1)against a hand transcription of Eqs. 2–6 at 0.0e+00.- Delete
results/,tables/,figs/anddata/derived/, re-runRscript run-all.R— every number and figure on this page regenerates (~107 s). - λ, the κ grid, the number of repeats, the split ratio and the ζ you report are stated in
the manuscript and match
config/project-config.R. - The exact
geosimilarityversion is named.gos()'s δ normalisation is an implementation detail of the package, so the version is part of the method here. - Accuracy is reported over repeated splits with the per-repeat file deposited, not as a single number from a single split.
env/session-info.txtis included in the deposit.
Is this just inverse-distance weighting in covariate space? → no, in two ways: the
minimum operator makes similarity a conjunction rather than an average, and the threshold
turns a weighting into a selection. Why not just fit a regression? →
results/model-comparison.csv: same covariates, MLR is 5.8 % worse on MAE, on
all 50 splits. Is λ overfitted? → it is derived by cross-validation once and then
scored on 50 independent splits; tests/test-01 shows the search is deterministic.
Is the improvement over BCS real, given how small it is? → paired over shared splits,
50/50 on MAE, and it comes with a 92 % reduction in observations used and the restored
map contrast of figs/fig05. Why no kriging comparison? → out of scope
here, and stated as such in §1.3 rather than glossed. What does the uncertainty mean? →
Eq. 12 at a named ζ: one minus a quantile of the retained similarities, i.e. how good the
available evidence was, not a predictive variance.
References & credits
Demo outputs were generated by the scripts in this folder using the CRAN
geosimilarity package and its bundled data. Reference figures are reproduced from
the article, which is open access, for comparison.
- Song Y (2023). Geographically Optimal Similarity. Mathematical Geosciences 55:295–320. doi:10.1007/s11004-022-10036-8 · PDFopen access (source of the method, of Figs. 1, 3, 4, 7, 8, 9, 10 and 11 reference images, and of the case reproduced here)
- Song Y, Lyu W. geosimilarity: Geographically Optimal Similarity. R package, version 3.9. CRAN · vignette (the reference implementation, and the source of both data sets)
- Zhu A-X, Lu G, Liu J, Qin C-Z, Zhou C (2018). Spatial prediction based on Third Law of Geography. Annals of GIS 24(4):225–240. doi:10.1080/19475683.2018.1534890 (the geographical similarity principle GOS builds on)
- Zhu A-X, Liu J, Du F, et al. (2015). Predictive soil mapping with limited sample data. European Journal of Soil Science 66(3):535–547. doi:10.1111/ejss.12244 (the BCS / iPSM model that κ = 1 reproduces exactly)
- Online GOS calculator — yongzesong.com/app/gos/ (the same package compiled to WebAssembly, running entirely in the browser)



