diff --git a/NEWS.md b/NEWS.md index e6db5b0..6ffba27 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,17 @@ # fireSense_dataPrepPredict (development version) +- `fireSense_EscapePredict` no longer exists (`fireSense_ignitionPredict` predicts ignition and escape): it is removed from the + `whichModulesToPrepare` default (now `fireSense_ignitionPredict` and `fireSense_spreadPredict`) and naming it stops with a message. + +- With one fitted ELF, the covariates (and that ELF's `landcoverDT`) are built from that ELF's groups in `nonForestedLCCGroupsList` and + `missingLCCgroupList` whenever those are present, as with several ELFs. Before, a predict-only run with one ELF used the + module default `nonForestedLCCGroups` (`nf`) and the spread prediction failed on the fitted `nfLCC_*` terms. + +- `loadOrder` and the `whichModulesToPrepare` default and comparisons use the renamed `fireSense_ignitionFit`, `fireSense_spreadFit`, + `fireSense_ignitionPredict` and `fireSense_spreadPredict` (formerly `fireSense_IgnitionFit`, `fireSense_SpreadFit`, + `fireSense_IgnitionPredict`, `fireSense_SpreadPredict`). A project setting `whichModulesToPrepare` must use the new names. + + - `forestedLCC`, `cutoffForYoungAge`, `nonForestCanBeYoungAge`, `flammabilityThreshold`, `fuelClassCol` and `igAggFactor` now default to `fireSenseUtils`'s shared constants (`fireSenseForestedLCC`, `fireSenseYoungAgeCutoff`, `fireSenseNonForestCanBeYoungAge`, diff --git a/fireSense_dataPrepPredict.R b/fireSense_dataPrepPredict.R index 3099c65..f032469 100644 --- a/fireSense_dataPrepPredict.R +++ b/fireSense_dataPrepPredict.R @@ -1,8 +1,8 @@ defineModule(sim, list( name = "fireSense_dataPrepPredict", description = paste( - "Prepares, each year, the covariate tables used by fireSense_IgnitionPredict,", - "fireSense_EscapePredict and fireSense_SpreadPredict."), + "Prepares, each year, the covariate tables used by fireSense_ignitionPredict", + "(ignition and escape) and fireSense_spreadPredict."), keywords = "", authors = c( person("Ian", "Eddy", role = c("aut", "cre"), email = "ian.eddy@nrcan-rncan.gc.ca"), @@ -10,13 +10,13 @@ defineModule(sim, list( person("Alex M", "Chubaty", role = "ctb", email = "achubaty@for-cast.ca") ), childModules = character(0), - version = list(fireSense_dataPrepPredict = "1.0.4.9006"), + version = list(fireSense_dataPrepPredict = "1.0.4.9009"), timeframe = as.POSIXlt(c(NA, NA)), timeunit = "year", citation = list("citation.bib"), documentation = deparse(list("README.txt", "fireSense_dataPrepPredict.Rmd")), loadOrder = list(after = c("Biomass_borealDataPrep", "fireSense_dataPrepFit", - "fireSense_IgnitionFit", "fireSense_SpreadFit")), + "fireSense_ignitionFit", "fireSense_spreadFit")), reqdPkgs = list( "data.table", "PredictiveEcology/fireSenseUtils@development (>= 0.2.3.9062)", @@ -60,11 +60,11 @@ defineModule(sim, list( defineParameter("sppEquivCol", "character", "LandR", NA, NA, desc = "Column of `sppEquiv` with the species names used in `cohortData`."), defineParameter("whichModulesToPrepare", "character", - default = c("fireSense_SpreadPredict", "fireSense_IgnitionPredict", "fireSense_EscapePredict"), + default = c("fireSense_spreadPredict", "fireSense_ignitionPredict"), NA, NA, - desc = paste("Predict modules to prepare covariates for: `fireSense_IgnitionPredict` or", - "`fireSense_EscapePredict` for the ignition/escape table,", - "`fireSense_SpreadPredict` for the spread table. Defaults to all three.")), + desc = paste("Predict modules to prepare covariates for: `fireSense_ignitionPredict` for the", + "ignition/escape table, `fireSense_spreadPredict` for the spread table.", + "Defaults to both.")), defineParameter( ".runInitialTime", "numeric", start(sim), NA, NA, "Time of the first climate and covariate preparation events." ), @@ -139,7 +139,7 @@ defineModule(sim, list( desc = "Table of LandR species equivalencies; must have columns `sppEquivCol` and `fuelClassCol`."), expectsInput("studyAreaWithSpreadParams", "sf", sourceURL = NA, desc = paste("The fitted SpreadFit ledger rows (from `fireSense_ELFs`; also read, undeclared, by", - "`fireSense_SpreadPredict`), one row per fitted ELF, in the order of `sppEquivs`. Each", + "`fireSense_spreadPredict`), one row per fitted ELF, in the order of `sppEquivs`. Each", "row's `params[[1]]` column names are the fitted formula's terms: an ELF whose terms", "include `dom_agb_`/`sec_agb_` predicts with those classes' AGB columns,", "matching what that ELF was fitted with; otherwise (an older, per-species fit) with the", @@ -174,18 +174,20 @@ defineModule(sim, list( doEvent.fireSense_dataPrepPredict <- function(sim, eventTime, eventType) { switch(eventType, init = { + if ("fireSense_EscapePredict" %in% P(sim)$whichModulesToPrepare) + stop("fireSense_EscapePredict no longer exists as a module; escape covariates are prepared with ", + "fireSense_ignitionPredict. Remove it from parameter whichModulesToPrepare.") sim <- Init(sim) sim <- scheduleEvent(sim, time(sim) + 1, "fireSense_dataPrepPredict", "ageNonForest") sim <- scheduleEvent(sim, P(sim)$.runInitialTime, "fireSense_dataPrepPredict", "getClimateRasters") - if ("fireSense_IgnitionPredict" %in% P(sim)$whichModulesToPrepare | - "fireSense_EscapePredict" %in% P(sim)$whichModulesToPrepare) { + if ("fireSense_ignitionPredict" %in% P(sim)$whichModulesToPrepare) { sim <- scheduleEvent(sim, P(sim)$.runInitialTime, "fireSense_dataPrepPredict", "prepIgAndEscPredictData" ) } - if ("fireSense_SpreadPredict" %in% P(sim)$whichModulesToPrepare) { + if ("fireSense_spreadPredict" %in% P(sim)$whichModulesToPrepare) { sim <- scheduleEvent(sim, P(sim)$.runInitialTime, "fireSense_dataPrepPredict", "prepSpreadPredictData" ) } @@ -406,7 +408,7 @@ prepare_IgnitionAndEscapePredict <- function(sim) { # Coming out of the CacheGeo, this is unreliably a data.frame instead of a data.table if (!data.table::is.data.table(sim$sppEquiv)) data.table::setDT(sim$sppEquiv) ## one fuel set per fitted ELF (one, as before, when there is one ELF); the covariate tables are merged, - ## each ELF's columns alongside the others', for fireSense_IgnitionPredict to pick its own + ## each ELF's columns alongside the others', for fireSense_ignitionPredict to pick its own fuelSets <- ELFfuelSets(sim) fuelCovsCoarse <- mergeCovariateTables(lapply(fuelSets, function(fs) prepare_FuelCovsCoarse( cohortData = sim$cohortData, @@ -462,7 +464,7 @@ prepare_SpreadPredict <- function(sim) { stop("spreadClimate is NULL; there is a problem to debug") ## one fuel set per fitted ELF (one, as before, when there is one ELF). Every ELF's covariates are made for - ## every pixel and the tables merged, so fireSense_SpreadPredict can apply each ELF's model wherever it + ## every pixel and the tables merged, so fireSense_spreadPredict can apply each ELF's model wherever it ## predicts, including the blend zone around its own pixels. Column names say what they hold (fuel class, ## non-forest LCC codes), so a column two ELFs share means the same thing in both. fuelSets <- ELFfuelSets(sim) @@ -536,7 +538,9 @@ prepare_SpreadPredict <- function(sim) { #' #' With several fitted ELFs, `fireSense_dataPrepFit` supplies one species table, non-forest grouping and #' missing-LCC group per ELF (`sppEquivs`, `nonForestedLCCGroupsList`, `missingLCCgroupList`); each gets its -#' own `landcoverDT`, made once and kept in `mod`. With one ELF, the single set of objects, as before. +#' own `landcoverDT`, made once and kept in `mod`. This holds for one ELF too whenever the per-ELF lists are +#' present (a predict-only run has only the module defaults in `nonForestedLCCGroups`); the single set of +#' objects is used only when they are absent. #' #' @param sim A `simList`. #' @return list of lists, each with `sppEquiv`, `nonForestedLCCGroups`, `missingLCCgroup`, `landcoverDT`, @@ -565,7 +569,7 @@ fuelClassRolesFromTermNames <- function(termNames) { #' #' @param sim A `simList`. #' @param i integer, the row (ELF), in the order of `sppEquivs` -- the same order -#' `fireSense_SpreadPredict::spreadPredictRun()` indexes `sa$params[[i]]` by. +#' `fireSense_spreadPredict::spreadPredictRun()` indexes `sa$params[[i]]` by. #' @return `list(domClass =, secClass =)`, from [fuelClassRolesFromTermNames()]; both `NA` when #' `studyAreaWithSpreadParams` is absent, too short, or that ELF has no fitted parameters yet. fuelClassRolesForELF <- function(sim, i = 1L) { @@ -579,7 +583,7 @@ fuelClassRolesForELF <- function(sim, i = 1L) { ELFfuelSets <- function(sim) { fcc <- P(sim)$fuelClassCol - if (length(sim$sppEquivs) > 1L) { + if (length(sim$sppEquivs) > 1L || length(sim$nonForestedLCCGroupsList)) { n <- length(sim$sppEquivs) if (length(sim$nonForestedLCCGroupsList) != n || length(sim$missingLCCgroupList) != n) stop("fireSense_dataPrepPredict: sppEquivs, nonForestedLCCGroupsList and missingLCCgroupList must have one ", diff --git a/fireSense_dataPrepPredict.Rmd b/fireSense_dataPrepPredict.Rmd index c61e8af..6c6f3c1 100644 --- a/fireSense_dataPrepPredict.Rmd +++ b/fireSense_dataPrepPredict.Rmd @@ -47,8 +47,8 @@ download.file(url = "https://img.shields.io/badge/Made%20with-Markdown-1f425f.pn Prepares, each year, the covariate tables that the fireSense [@Marchal:2017a; @Marchal:2017b; @Marchal:2019] predict modules use: -- `fireSense_igAndEscapePred_Covariates` for *fireSense_IgnitionPredict* and *fireSense_EscapePredict*: fuel classes, non-forest landcover, `youngAge`, ignition climate and lightning days, aggregated by `igAggFactor`. -- `fireSense_SpreadCovariates` for *fireSense_SpreadPredict*: the same fuel, landcover and `youngAge` columns plus spread climate, at the resolution of `flammableRTM`. +- `fireSense_igAndEscapePred_Covariates` for *fireSense_ignitionPredict* (ignition and escape): fuel classes, non-forest landcover, `youngAge`, ignition climate and lightning days, aggregated by `igAggFactor`. +- `fireSense_SpreadCovariates` for *fireSense_spreadPredict*: the same fuel, landcover and `youngAge` columns plus spread climate, at the resolution of `flammableRTM`. Fuel classes come from `cohortData` and `pixelGroupMap`, grouped by the `fuelClassCol` column of `sppEquiv`. The covariates are built by the same *fireSenseUtils* functions that *fireSense_dataPrepFit* uses, so they match the fitted models. @@ -84,8 +84,8 @@ All events after `init`, except `save`, repeat every `fireTimeStep` years. - `init`: aligns `standAgeMap` and `rstLCC_RTM` to `rasterToMatch`; builds `landcoverDT` if absent; builds `nonForest_timeSinceDisturbance` if absent, from the fire polygons of the `cutoffForYoungAge` years up to `dataYear`. - `getClimateRasters` (from `.runInitialTime`): a supplied `currentClimateRasters` (e.g. from the `climateYear` module) is left alone. If it is absent, or this module built it for another year, takes layer `year` of each element of `projectedClimateRasters`, where `Y` is `climateYear` if supplied, else `time(sim)`. Stops if it does not match `pixelGroupMap`. -- `prepIgAndEscPredictData` (from `.runInitialTime`): builds `fireSense_igAndEscapePred_Covariates`. Scheduled if `whichModulesToPrepare` has `fireSense_IgnitionPredict` or `fireSense_EscapePredict`. -- `prepSpreadPredictData` (from `.runInitialTime`): builds `fireSense_SpreadCovariates`. Scheduled if `whichModulesToPrepare` has `fireSense_SpreadPredict`. +- `prepIgAndEscPredictData` (from `.runInitialTime`): builds `fireSense_igAndEscapePred_Covariates`. Scheduled if `whichModulesToPrepare` has `fireSense_ignitionPredict`; the module `fireSense_EscapePredict` no longer exists and is rejected. +- `prepSpreadPredictData` (from `.runInitialTime`): builds `fireSense_SpreadCovariates`. Scheduled if `whichModulesToPrepare` has `fireSense_spreadPredict`. - `ageNonForest` (from `time(sim) + 1`): adds 1 to `nonForest_timeSinceDisturbance` and resets pixels burned in `rstCurrentBurn` to 0. - `save`: does nothing except emit a message. The module never schedules it. @@ -103,7 +103,7 @@ knitr::kable(df_outputs, caption = "List of (ref:fireSense-dataPrepPredict) outp ### Links to other modules -Runs after *Biomass_borealDataPrep*, *fireSense_dataPrepFit*, *fireSense_IgnitionFit* and *fireSense_SpreadFit*, and supplies *fireSense_IgnitionPredict*, *fireSense_EscapePredict* and *fireSense_SpreadPredict*. +Runs after *Biomass_borealDataPrep*, *fireSense_dataPrepFit*, *fireSense_ignitionFit* and *fireSense_spreadFit*, and supplies *fireSense_ignitionPredict* (ignition and escape) and *fireSense_spreadPredict*. It is normally run as part of the [fireSense](https://github.com/PredictiveEcology/fireSense) module group. ### Getting help diff --git a/fireSense_dataPrepPredict.md b/fireSense_dataPrepPredict.md index d6b2902..caff9e9 100644 --- a/fireSense_dataPrepPredict.md +++ b/fireSense_dataPrepPredict.md @@ -39,8 +39,8 @@ Ian Eddy [aut, cre], Eliot McIntire studyAreaWithSpreadParams sf - The fitted SpreadFit ledger rows (from `fireSense_ELFs`; also read, undeclared, by `fireSense_SpreadPredict`), one row per fitted ELF, in the order of `sppEquivs`. Each row's `params[[1]]` column names are the fitted formula's terms: an ELF whose terms include `dom_agb_<class>`/`sec_agb_<class>` predicts with those classes' AGB columns, matching what that ELF was fitted with; otherwise (an older, per-species fit) with the previous one-column-per-fuel-class covariates. Unsupplied: every ELF predicts per-fuel-class, as before this was read. + The fitted SpreadFit ledger rows (from `fireSense_ELFs`; also read, undeclared, by `fireSense_spreadPredict`), one row per fitted ELF, in the order of `sppEquivs`. Each row's `params[[1]]` column names are the fitted formula's terms: an ELF whose terms include `dom_agb_<class>`/`sec_agb_<class>` predicts with those classes' AGB columns, matching what that ELF was fitted with; otherwise (an older, per-species fit) with the previous one-column-per-fuel-class covariates. Unsupplied: every ELF predicts per-fuel-class, as before this was read. NA @@ -310,7 +310,7 @@ Summary of user-visible parameters (Table \@ref(tab:moduleParams-fireSense-dataP fireSens.... NA NA - Predict modules to prepare covariates for: `fireSense_IgnitionPredict` or `fireSense_EscapePredict` for the ignition/escape table, `fireSense_SpreadPredict` for the spread table. Defaults to all three. + Predict modules to prepare covariates for: `fireSense_ignitionPredict` for the ignition/escape table, `fireSense_spreadPredict` for the spread table. Defaults to both. .runInitialTime @@ -345,8 +345,8 @@ All events after `init`, except `save`, repeat every `fireTimeStep` years. - `init`: aligns `standAgeMap` and `rstLCC_RTM` to `rasterToMatch`; builds `landcoverDT` if absent; builds `nonForest_timeSinceDisturbance` if absent, from the fire polygons of the `cutoffForYoungAge` years up to `dataYear`. - `getClimateRasters` (from `.runInitialTime`): a supplied `currentClimateRasters` (e.g. from the `climateYear` module) is left alone. If it is absent, or this module built it for another year, takes layer `year` of each element of `projectedClimateRasters`, where `Y` is `climateYear` if supplied, else `time(sim)`. Stops if it does not match `pixelGroupMap`. -- `prepIgAndEscPredictData` (from `.runInitialTime`): builds `fireSense_igAndEscapePred_Covariates`. Scheduled if `whichModulesToPrepare` has `fireSense_IgnitionPredict` or `fireSense_EscapePredict`. -- `prepSpreadPredictData` (from `.runInitialTime`): builds `fireSense_SpreadCovariates`. Scheduled if `whichModulesToPrepare` has `fireSense_SpreadPredict`. +- `prepIgAndEscPredictData` (from `.runInitialTime`): builds `fireSense_igAndEscapePred_Covariates`. Scheduled if `whichModulesToPrepare` has `fireSense_ignitionPredict`; the module `fireSense_EscapePredict` no longer exists and is rejected. +- `prepSpreadPredictData` (from `.runInitialTime`): builds `fireSense_SpreadCovariates`. Scheduled if `whichModulesToPrepare` has `fireSense_spreadPredict`. - `ageNonForest` (from `time(sim) + 1`): adds 1 to `nonForest_timeSinceDisturbance` and resets pixels burned in `rstCurrentBurn` to 0. - `save`: does nothing except emit a message. The module never schedules it. @@ -391,7 +391,7 @@ Description of the module outputs (Table \@ref(tab:moduleOutputs-fireSense-dataP ### Links to other modules -Runs after *Biomass_borealDataPrep*, *fireSense_dataPrepFit*, *fireSense_IgnitionFit* and *fireSense_SpreadFit*, and supplies *fireSense_IgnitionPredict*, *fireSense_EscapePredict* and *fireSense_SpreadPredict*. +Runs after *Biomass_borealDataPrep*, *fireSense_dataPrepFit*, *fireSense_ignitionFit* and *fireSense_spreadFit*, and supplies *fireSense_ignitionPredict* (ignition and escape) and *fireSense_spreadPredict*. It is normally run as part of the [fireSense](https://github.com/PredictiveEcology/fireSense) module group. ### Getting help diff --git a/tests/testthat/test-init.R b/tests/testthat/test-init.R index 1ebaaf8..9c94a51 100644 --- a/tests/testthat/test-init.R +++ b/tests/testthat/test-init.R @@ -71,26 +71,21 @@ test_that("init schedules exactly the events implied by whichModulesToPrepare", sort(as.data.frame(SpaDES.core::events(sim))$eventType) } ## ageNonForest and getClimateRasters are unconditional; the two prep events are not - expect_identical(evs("fireSense_SpreadPredict"), + expect_identical(evs("fireSense_spreadPredict"), c("ageNonForest", "getClimateRasters", "prepSpreadPredictData")) - expect_identical(evs("fireSense_IgnitionPredict"), + expect_identical(evs("fireSense_ignitionPredict"), c("ageNonForest", "getClimateRasters", "prepIgAndEscPredictData")) - ## the ignition/escape table is shared: EscapePredict alone schedules it too - expect_identical(evs("fireSense_EscapePredict"), - c("ageNonForest", "getClimateRasters", "prepIgAndEscPredictData")) - expect_identical(evs(c("fireSense_SpreadPredict", "fireSense_IgnitionPredict")), + expect_identical(evs(c("fireSense_spreadPredict", "fireSense_ignitionPredict")), c("ageNonForest", "getClimateRasters", "prepIgAndEscPredictData", "prepSpreadPredictData")) ## a Fit module name is not a Predict module name: no covariate table is prepared - expect_identical(evs("fireSense_EscapeFit"), c("ageNonForest", "getClimateRasters")) + expect_identical(evs("fireSense_ignitionFit"), c("ageNonForest", "getClimateRasters")) }) -test_that("the default whichModulesToPrepare is the three Predict modules doEvent tests for", { - ## The default used to include `fireSense_EscapeFit`, which `doEvent` never tests for. +test_that("the default whichModulesToPrepare is the two Predict modules", { md <- SpaDES.core::moduleMetadata(module = moduleName, path = modulePath) default <- md$parameters$default[[which(md$parameters$paramName == "whichModulesToPrepare")]] - expect_setequal(default, c("fireSense_IgnitionPredict", "fireSense_EscapePredict", - "fireSense_SpreadPredict")) + expect_setequal(default, c("fireSense_ignitionPredict", "fireSense_spreadPredict")) ## with the default, init schedules both covariate events sim <- SpaDES.core::spades(toyPrepSim(), events = "init", debug = FALSE) @@ -99,6 +94,14 @@ test_that("the default whichModulesToPrepare is the three Predict modules doEven "prepSpreadPredictData")) }) +test_that("the retired fireSense_EscapePredict stops with a message naming fireSense_ignitionPredict", { + expect_error( + SpaDES.core::spades( + toyPrepSim(params = list(whichModulesToPrepare = c("fireSense_ignitionPredict", "fireSense_EscapePredict"))), + events = "init", debug = FALSE), + "no longer exists.*fireSense_ignitionPredict") +}) + test_that("the save event does nothing", { sim <- SpaDES.core::spades(toyPrepSim(), events = "init", debug = FALSE) before <- sapply(ls(sim), function(nm) reproducible::.robustDigest(sim[[nm]])) diff --git a/tests/testthat/test-metadata.R b/tests/testthat/test-metadata.R index fb916d1..875f67d 100644 --- a/tests/testthat/test-metadata.R +++ b/tests/testthat/test-metadata.R @@ -64,3 +64,11 @@ test_that("parameters are the expected names", { "sppEquivCol", "whichModulesToPrepare")) ) }) + +test_that("loadOrder names the renamed fit modules", { + ## moduleMetadata() does not return `loadOrder`, so read it from the parsed defineModule() call + parsed <- parse(file.path(moduleRoot, paste0(moduleName, ".R")), keep.source = TRUE) + dm <- Filter(function(e) grepl("^defineModule", paste(deparse(e), collapse = "")), as.list(parsed)) + lo <- eval(dm[[1]][[3]]$loadOrder) + expect_true(all(c("fireSense_ignitionFit", "fireSense_spreadFit") %in% lo$after)) +}) diff --git a/tests/testthat/test-multiELF.R b/tests/testthat/test-multiELF.R index eed653c..8803a2d 100644 --- a/tests/testthat/test-multiELF.R +++ b/tests/testthat/test-multiELF.R @@ -1,5 +1,5 @@ ## Several fitted ELFs in one study area (the 2-ELF Mackenzie forecast, 2026-09). Each ELF's model predicts -## over its own pixels and a blend zone around them (fireSense_SpreadPredict), so every pixel needs every +## over its own pixels and a blend zone around them (fireSense_spreadPredict), so every pixel needs every ## ELF's covariates: each ELF's fuel classes and non-forest groups, made from its own sppEquiv. Columns two ## ELFs share have the same name and so the same content. ## @@ -55,3 +55,24 @@ test_that("mismatched per-ELF lists stop with a message", { o$sppEquivs <- list(sppA(), sppB()); o$nonForestedLCCGroupsList <- list(nfA); o$missingLCCgroupList <- list("grass", "wetgrs") expect_error(toyPrepRun(o), "one element per ELF") }) + +## A predict-only run (no fireSense_dataPrepFit; parameters from the ledger) with ONE fitted ELF still +## gets that ELF's groups in `nonForestedLCCGroupsList`; `nonForestedLCCGroups` is then only the module +## default. The covariates must be built with the ELF's groups (fireCarbon run 4.2.2, 2026-09-29: the fit +## had nfLCC_* terms, the covariates had `nf`, and the spread prediction failed). +test_that("with one ELF and per-ELF lists, the list's groups build the covariates", { + o <- toyObjects() + o$landcoverDT <- NULL + o$nonForestedLCCGroups <- list(nf = c(16L, 19L)); o$missingLCCgroup <- "nf" # the module defaults + o$sppEquivs <- list(sppA()) + o$nonForestedLCCGroupsList <- list(nfA) + o$missingLCCgroupList <- list("grass") + one <- toyPrepRun(o) + ref <- oneELF(sppA, nfA, "grass") + + sp <- covDF(one$fireSense_SpreadCovariates); spRef <- covDF(ref$fireSense_SpreadCovariates) + expect_true(all(c("wetland", "grass") %in% names(sp))) + expect_false("nf" %in% names(sp)) + expect_setequal(names(sp), names(spRef)) + for (cn in setdiff(names(spRef), "pixelID")) expect_equal(sp[[cn]], spRef[[cn]], info = cn) +})