--- title: "Respecting groups and monitoring domains in SOM validation" author: "Shaowen Ye" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Respecting groups and monitoring domains in SOM validation} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") ``` ## Why the resampling unit matters Environmental and ecological tables often contain repeated observations from the same site, individual, transect, campaign or watershed. Treating those rows as independent can place closely related observations in both the analysis and assessment subsets. A model may then appear reproducible because it repeatedly sees the same sampling unit, not because the pattern transfers to a new unit. `SOMevidence` stores the sampling hierarchy outside the predictor matrix and requires the resampling design to be stated. The example below creates a known three-class system with strong site-level dependence. Class labels remain external to training. ```{r grouped-data} library(SOMevidence) grouped <- simulate_som_scenario( "grouped_pseudoreplication", n = 120, p = 6, n_groups = 20, group_icc = 0.7, seed = 101 ) grouped ``` Row resampling and site resampling answer different questions. The latter asks whether a partition can be reproduced after entire sites are withheld. ```{r resampling-designs} row_splits <- som_resamples( grouped, method = "subsample", repeats = 3, prop = 0.8, seed = 102 ) site_splits <- som_resamples( grouped, method = "group_subsample", repeats = 3, prop = 0.8, seed = 102 ) spec <- som_spec( grids = list(c(3, 2), c(4, 3)), seeds = c(103, 104), rlen = 40, k = 2:4 ) ``` Both workflows use the same SOM budget and preprocessing. Only the resampling unit changes. ```{r grouped-workflows} row_result <- run_som_workflow( grouped, spec, row_splits, cross_models = c("kmeans", "ward") ) site_result <- run_som_workflow( grouped, spec, site_splits, cross_models = c("kmeans", "ward") ) row_result$partitions$stability site_result$partitions$stability ``` These outputs should be compared as design sensitivities, not as an algorithm ranking. A difference shows that the inferential unit matters. It does not by itself identify which design matches a particular field study; that decision comes from how the observations were collected. ## Mapping to a held-out monitoring domain Leave-domain-out analysis addresses another question: does a map trained in some domains receive observations from a held-out domain at comparable distances and on units occupied during training? ```{r transfer-data} shifted <- simulate_som_scenario( "gradient", n = 120, p = 6, n_domains = 3, domain_shift = 1, seed = 110 ) domain_splits <- som_resamples( shifted, method = "leave_domain_out", domain = "domain" ) domain_ensemble <- fit_som_ensemble( shifted, som_spec(c(4, 3), seeds = c(111, 112), rlen = 40, k = 2:4), domain_splits ) transfer <- audit_transfer(domain_ensemble) transfer ``` ```{r transfer-plot, fig.width = 6, fig.height = 4} plot(transfer) ``` The distance ratio and unoccupied-unit rate diagnose domain shift. They are not accuracy statistics, because an unsupervised map has no response label to predict. They also do not establish a causal explanation for the shift. ## Map a later monitoring batch without refitting An operational monitoring workflow may freeze a trained ensemble and map a later batch. The new data must have the same named layers and variables, but it may contain one or many observations. Each map applies its own training-derived preprocessing. ```{r new-batch} training_rows <- shifted$metadata$domain != "domain_03" training <- som_data( layers = lapply( shifted$layers, function(x) x[training_rows, , drop = FALSE] ), id = shifted$metadata$id[training_rows] ) new_batch <- som_data( layers = lapply( shifted$layers, function(x) x[!training_rows, , drop = FALSE] ), id = shifted$metadata$id[!training_rows], domain = shifted$metadata$domain[!training_rows] ) frozen_ensemble <- fit_som_ensemble( training, som_spec(c(4, 3), seeds = c(120, 121), rlen = 40, k = 2:4), keep_models = TRUE ) new_mapping <- map_som_ensemble(frozen_ensemble, new_batch) new_mapping new_mapping$summary ``` ```{r new-batch-plot, fig.width = 6, fig.height = 4} plot(new_mapping) ``` Best-matching unit identifiers stay specific to each map. The package does not align nodes across different grids or turn distance shift into a hard class prediction. ## Predefined temporal or spatial blocks `block_subsample` expects `unit` to identify blocks already defined from the study design. The package does not guess a block length from row order. This keeps the scientific definition of a season, campaign, reach or time window under analyst control and prevents accidental dependence on file ordering.