Soil Sampling Design Methods Including Spatial Coverage, Maxvol Optimal Design, Simple Random, and Stratified Sampling
The soilsampling package provides methods for designing soil sampling schemes. It implements:
- Simple Random Sampling: Uniform random selection of sampling locations
- Stratified Random Sampling: Random sampling within compact geographical strata
- Spatial Coverage Sampling: Purposive sampling at stratum centroids for optimal coverage
- Maxvol Optimal Design Sampling: Feature-based sampling using D-optimal experimental design
- Composite Sampling: Sampling from equal-area strata for combined samples
- Validation Metrics: Assess stratification quality through coverage efficiency and distance statistics
- KL Sample Size Optimization: Determine optimal cLHS sample size via Kullback-Leibler divergence
- cLHS + Random Forest Optimization: Refine a cLHS design with simulated annealing to minimize RF prediction error
- Alternative Site Selection: Find environmentally similar replacement sites for inaccessible sampling locations
# Using pak (fastest)
pak::pak("ccarbajal16/soilsampling")
# Or using devtools
devtools::install_github("ccarbajal16/soilsampling")# Install from local directory
install.packages(".", repos = NULL, type = "source")
# Or using devtools
devtools::install_local("path/to/soilsampling")- R (>= 4.1.0)
- sf (>= 1.0-12)
- ggplot2 (>= 3.3.0)
- terra
- clhs
- dplyr
- minpack.lm
- randomForest
- gridExtra
library(soilsampling)
library(sf)
# Create a study area
poly <- st_polygon(list(rbind(
c(0, 0), c(100, 0), c(100, 50), c(0, 50), c(0, 0)
)))
study_area <- st_sf(geometry = st_sfc(poly))
# Set seed for reproducibility
set.seed(42)
# Spatial coverage sampling
samples <- ss_coverage(study_area, n_strata = 25, n_try = 5)
ss_plot(samples)
# Get coordinates
coords <- ss_to_data_frame(samples)
head(coords)Select locations uniformly at random:
samples <- ss_random(study_area, n = 30)
ss_plot_samples(samples)Create strata first, then sample randomly within each:
# Create 25 compact strata
strata <- ss_stratify(study_area, n_strata = 25, n_try = 5)
# Take 1 random sample per stratum
samples <- ss_stratified(strata, n_per_stratum = 1)
ss_plot(strata, samples = samples)Place samples at stratum centroids for optimal spatial coverage:
# Direct approach
samples <- ss_coverage(study_area, n_strata = 25, n_try = 5)
# Or use pre-computed strata
strata <- ss_stratify(study_area, n_strata = 25, n_try = 5)
samples <- ss_coverage(strata)
ss_plot(samples)Incorporate existing sampling locations:
# Existing sample locations
prior_pts <- st_as_sf(
data.frame(x = c(25, 75), y = c(25, 25)),
coords = c("x", "y")
)
# Create stratification around prior points
strata <- ss_stratify(
study_area,
n_strata = 20,
prior_points = prior_pts,
n_try = 5
)
samples <- ss_coverage(strata)
ss_plot(samples) # Prior points shown with different symbolCreate strata of equal size:
samples <- ss_coverage_equal_area(study_area, n_strata = 25, n_try = 5)For combining samples from multiple locations:
# Create 3 composite samples from 20 equal-area strata
samples <- ss_composite(study_area, n_strata = 20, n_composites = 3)
ss_plot(samples) # Different symbols for each compositeFeature-based sampling using D-optimal experimental design to maximize diversity:
# Create a grid with terrain features
strata <- ss_stratify(study_area, n_strata = 100, n_try = 5)
cells_sf <- strata$cells
# Add terrain features (in practice, extract from DEM)
coords <- st_coordinates(cells_sf)
cells_sf$elevation <- coords[,2] + rnorm(nrow(coords), 0, 5)
cells_sf$slope <- abs(rnorm(nrow(coords), 5, 2))
cells_sf$twi <- rnorm(nrow(coords), 8, 1.5)
# Select 20 optimal samples based on features
samples <- ss_maxvol(
cells_sf,
n = 20,
features = c("elevation", "slope", "twi"),
min_dist = 5, # Minimum distance between samples
normalize = TRUE, # Normalize features to equal scales
add_coords = TRUE # Include coordinates as features
)
ss_plot_samples(samples)The maxvol algorithm selects locations that maximize the determinant (volume) of the feature submatrix, ensuring maximum diversity in feature space. This is particularly effective when:
- Features explain soil-forming factors
- You need deterministic (non-random) point selection
- You want optimal coverage with few samples
Determine the cLHS sample size that best represents the population distribution, based on Kullback-Leibler divergence (Malone et al. 2019):
library(terra)
predictors <- rast("data/predictors.tif")
result <- ss_kl_size(
predictors,
min_samples = 10,
max_samples = 200,
step_size = 10,
n_replicates = 10,
output_dir = "outputs" # optional: writes CSVs and plots
)
result$optimal_sample_size
result$plot_cdfss_kl_size() accepts a raster path, an already-loaded SpatRaster, or a population data frame directly. Use ss_kl_optimize() for the underlying optimization without raster I/O, or ss_kl_divergence() to compute divergence between an arbitrary population and sample.
Refine a cLHS baseline design using simulated annealing to minimize cross-validated Random Forest prediction error (Wadoux et al. 2019):
predictors <- rast("data/predictors.tif")
result <- ss_rf_size(
predictors,
n_samples = 100,
n_iterations = 500,
seed = 123,
output_dir = "outputs" # optional: writes CSVs and comparison plot
)
result$comparison_table
result$improvement # % MSE reduction vs. the cLHS baseliness_clhs_sample() generates the cLHS baseline alone; ss_rf_optimize() runs the simulated-annealing refinement; ss_rf_mse() exposes the cross-validated MSE objective function directly.
Find environmentally similar alternative sites when original sampling locations become inaccessible:
predictors <- rast("data/predictors.tif")
inaccessible <- read.csv("data/inaccessible_sites.csv") # needs x, y columns
result <- ss_alt_sites(
predictors,
inaccessible,
method = "mahalanobis", # or "euclidean", "gower"
n_alternatives = 3,
min_distance_buffer = 300, # exclude candidates within 300m of any target
seed = 123,
output_dir = "outputs" # optional: writes CSVs
)
result$alternatives # top alternatives for every inaccessible siteLower-level building blocks are also exported: ss_alt_candidates() (candidate pool generation), ss_alt_filter_buffer() (distance exclusion), ss_alt_similarity() (similarity scoring), ss_alt_rank() (top-N selection), and ss_alt_standardize_sites() (normalize raw CSV coordinates to the package's site layout).
The package provides validation metrics to assess the quality of your stratification:
Evaluate how uniformly cells are distributed across strata:
# Create stratification
strata <- ss_stratify(study_area, n_strata = 25, n_try = 5)
# Assess coverage efficiency
efficiency <- ss_coverage_efficiency(strata)
print(efficiency)Output metrics:
- CV (Coefficient of Variation): Lower values indicate more uniform distribution
- Min/Max Ratio: Ratio of smallest to largest stratum (1.0 = perfect equality)
- Gini Coefficient: Standard inequality measure (0 = perfect equality)
- Efficiency Score: Qualitative assessment (Excellent/Good/Fair/Poor)
Comparing stratification methods:
# Regular stratification
strata_regular <- ss_stratify(study_area, n_strata = 20, n_try = 5)
# Equal-area stratification
strata_equal <- ss_stratify(study_area, n_strata = 20,
equal_area = TRUE, n_try = 5)
# Compare efficiency
eff_regular <- ss_coverage_efficiency(strata_regular)
eff_equal <- ss_coverage_efficiency(strata_equal)
cat("Regular CV:", eff_regular$cv, "\n")
cat("Equal-area CV:", eff_equal$cv, "\n")
# Equal-area should have much lower CV (< 0.05 vs 0.15-0.30)Compute distance from cells or samples to stratum centroids:
# For stratification
dist_summary <- ss_distance_summary(strata)
print(dist_summary)
# For samples (works with coverage or stratified samples)
samples <- ss_coverage(strata)
dist_samples <- ss_distance_summary(samples, actual_distance = TRUE)
print(dist_samples)Output (per stratum):
- Mean, SD, min, max, median distances to centroid
- Helps identify dispersed or compact strata
- Default: squared distances (consistent with MSSD metric)
Use cases:
- Compare different stratification runs (
n_tryvalues) - Verify equal-area maintains compactness
- Identify strata with unusual geometry
# Read study area
study_area <- st_read("path/to/study_area.shp")
# Create sampling design
set.seed(42)
samples <- ss_coverage(study_area, n_strata = 50, n_try = 10)
# Export as CSV
coords <- ss_to_data_frame(samples)
write.csv(coords, "sampling_points.csv", row.names = FALSE)
# Export as GeoPackage
samples_sf <- ss_to_sf(samples)
st_write(samples_sf, "sampling_points.gpkg")| Function | Description |
|---|---|
ss_stratify() |
Create compact geographical strata using k-means |
| Function | Description |
|---|---|
ss_random() |
Simple random sampling |
ss_stratified() |
Stratified random sampling |
ss_coverage() |
Spatial coverage sampling (centroids) |
ss_coverage_equal_area() |
Coverage sampling with equal-area strata |
ss_maxvol() |
Maxvol optimal design sampling (feature-based) |
ss_composite() |
Composite sampling |
| Function | Description |
|---|---|
ss_plot() |
Plot stratification and/or samples |
ss_plot_samples() |
Plot sampling points only |
| Function | Description |
|---|---|
ss_coverage_efficiency() |
Assess uniformity of cell distribution across strata |
ss_distance_summary() |
Compute distance statistics from cells/samples to centroids |
| Function | Description |
|---|---|
ss_to_sf() |
Convert to sf object |
ss_to_data_frame() |
Convert to data frame with coordinates |
ss_summary() |
Get summary statistics |
ss_get_samples() |
Extract samples sf object |
ss_n_strata() |
Get number of strata |
ss_n_samples() |
Get number of samples |
ss_area() |
Get stratum areas |
ss_relative_area() |
Get relative stratum areas |
| Function | Description |
|---|---|
ss_load_rasters() |
Load a multi-layer raster or a directory of single-layer rasters |
ss_kl_divergence() |
KL divergence between population and sample distributions |
ss_kl_optimize() |
Optimize cLHS sample size via KL divergence |
ss_kl_size() |
End-to-end workflow from raster/data frame to optimal sample size |
ss_kl_save_plots() |
Save KL divergence and CDF plots independently |
| Function | Description |
|---|---|
ss_clhs_sample() |
Conditioned Latin hypercube baseline sampling |
ss_rf_mse() |
Cross-validated Random Forest MSE objective function |
ss_rf_optimize() |
Simulated annealing refinement to minimize RF MSE |
ss_rf_size() |
End-to-end workflow with comparison plots |
| Function | Description |
|---|---|
ss_alt_similarity() |
Environmental similarity (Mahalanobis, Euclidean, or Gower distance) |
ss_alt_candidates() |
Generate a candidate site pool (random or systematic grid) |
ss_alt_filter_buffer() |
Exclude candidates within a minimum distance of target sites |
ss_alt_rank() |
Rank and select the top similar alternatives |
ss_alt_standardize_sites() |
Standardize site coordinate data to the package's layout |
ss_alt_sites() |
End-to-end alternative site selection workflow |
| Method | Best For | Inference Type |
|---|---|---|
ss_coverage() |
Interpolation (kriging) | Model-based |
ss_maxvol() |
Feature diversity, D-optimal design | Model-based |
ss_stratified() |
Estimating means/totals | Design-based |
ss_random() |
Simple design-based analysis | Design-based |
ss_composite() |
Reducing laboratory costs | Design-based |
ss_kl_size() |
Choosing how many cLHS samples to collect | Model-based |
ss_rf_size() |
Maximizing predictive accuracy of a fixed-size design | Model-based |
ss_alt_sites() |
Replacing inaccessible sites with similar alternatives | Model-based |
The package uses pure R implementations of k-means algorithms:
-
Transfer Algorithm (default): Standard k-means that iteratively transfers cells to nearer cluster centers. Creates compact but potentially unequal-sized strata.
-
Swop Algorithm (
equal_area = TRUE): Modified k-means that swaps cells between clusters to maintain equal sizes while optimizing compactness.
Both algorithms minimize the Mean Squared Shortest Distance (MSSD) between cells and their assigned cluster centroids.
- Use multiple tries: Set
n_try = 10or higher to avoid local optima - Set a seed: Use
set.seed()for reproducible results - Adjust resolution: Use
n_cellsparameter to control grid density - Check convergence: The output includes a
convergedflag - Validate your stratification: Use
ss_coverage_efficiency()to ensure uniform strata - Compare alternatives: Test different
n_stratavalues and assess efficiency
-
de Gruijter, J.J., Brus, D.J., Bierkens, M.F.P., and Knotters, M. (2006). Sampling for Natural Resource Monitoring. Springer, Berlin.
-
Walvoort, D.J.J., Brus, D.J., and de Gruijter, J.J. (2010). An R package for spatial coverage sampling and random sampling from compact geographical strata by k-means. Computers & Geosciences 36, 1261-1267. DOI: 10.1016/j.cageo.2010.04.005
-
Petrovskaia, N., Korveh, K., and Maas, E. (2021). Optimal soil sampling design based on the maxvol algorithm. Geoderma 381, 114733. DOI: 10.1016/j.geoderma.2020.114733
-
Malone, B.P., Minasny, B., and Brungard, C. (2019). Some methods to improve the utility of conditioned Latin hypercube sampling. PeerJ 7, e6451. DOI: 10.7717/peerj.6451
-
Wadoux, A.M.J-C., Brus, D.J., and Heuvelink, G.B.M. (2019). Sampling design optimization for soil mapping with random forest. Geoderma 355, 113913. DOI: 10.1016/j.geoderma.2019.113913
GPL (>= 3)
