--- title: "Using Year-Specific Rasters in One Model" author: "Bill Peterman" output: rmarkdown::html_vignette: number_sections: true toc: true vignette: > %\VignetteIndexEntry{Using Year-Specific Rasters in One Model} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width = 6, fig.height = 4.25) library(multiScaleR) library(terra) ``` When habitat changes between survey years, each observation should use the map from its own year. `kernel_prep_by_group()` prepares one pooled analysis object from a named list of maps and a year label for every observation. The model can then estimate one landscape coefficient and one scale across both years. Read `vignette("quickstart", package = "multiScaleR")` first for the basic preparation and optimization workflow. ## Build aligned annual maps and observations The following small maps represent the proportion of habitat in a neighborhood. The second map has a managed patch. Both maps use the same projected grid and the same layer name. If your maps contain numeric land-cover class codes, turn the class of interest into a binary layer before calculating its mean. Averaging the class codes themselves has no meaningful ecological interpretation. ```{r annual-data} set.seed(93) habitat_1 <- rast(nrows = 35, ncols = 40, xmin = 0, xmax = 800, ymin = 0, ymax = 700, crs = "EPSG:26915") xy <- xyFromCell(habitat_1, seq_len(ncell(habitat_1))) values(habitat_1) <- as.integer( sin(xy[, 1] / 80) + cos(xy[, 2] / 105) > 0 ) names(habitat_1) <- "habitat" habitat_2 <- habitat_1 habitat_2[xy[, 1] > 350 & xy[, 1] < 500 & xy[, 2] > 250 & xy[, 2] < 440] <- 0 observations <- data.frame( year = factor(rep(c("year1", "year2"), each = 30)), x = runif(60, 120, 680), y = runif(60, 120, 580) ) rownames(observations) <- paste0("site_", seq_len(nrow(observations))) points <- sf::st_as_sf(observations, coords = c("x", "y"), crs = 26915) ``` Each row is one observation. A nest sampled repeatedly can have the same coordinates in several rows, but each row needs a unique ID and its own year label. The response model must account for dependence among repeated observations when the study design requires it. ## Prepare one pooled set of covariates ```{r grouped-preparation} prepared <- kernel_prep_by_group( pts = points, raster_stacks = list(year1 = habitat_1, year2 = habitat_2), group = observations$year, max_D = 100, kernel = "gaussian", bin = TRUE, store_cell_data = FALSE, verbose = FALSE ) head(prepared$kernel_dat) table(prepared$raster_group) ``` **How to read the output:** `kernel_dat` has one row per observation. Its `habitat` column is the initial kernel-weighted habitat proportion, centered and divided by the standard deviation across *all 60 observations*. These standardized values are inputs for the starting model. `raster_group` records which map each row used. A year-2 observation uses the year-2 map even if its location lies outside the managed patch, because its surrounding buffer may include changed cells. `max_D` and the estimated scale use map units, meters here. Set `max_D` beyond the plausible scale of effect. Binning summarizes values by distance so the optimizer can evaluate many scales efficiently. `store_cell_data = FALSE` works here because the only spatial covariate is a kernel-weighted mean. For landscape configuration or surface metrics, use `scale_vars` with the same source layer names in every annual map and retain cell data. ## Fit one relationship across years The response below is simulated only to show the connection between the prepared object and a model. Its Bernoulli likelihood represents known survival over the same observation interval for every row. Nest or juvenile survival data with unequal exposure periods, censoring, repeated measurements, or imperfect detection require a model that addresses those features. ```{r grouped-fit} observations$survived <- rbinom( nrow(observations), 1, plogis(-0.4 + 0.9 * prepared$kernel_dat$habitat + 0.3 * (observations$year == "year2")) ) model_data <- cbind(observations, prepared$kernel_dat) initial_model <- glm(survived ~ habitat + year, family = binomial(), data = model_data, na.action = na.fail) fit <- multiScale_optim(initial_model, prepared, n_cores = 1, verbose = FALSE) fit$scale_est coef(fit$opt_mod) diagnostics(fit)$sample_size ``` **How to read the output:** `scale_est["habitat", "Mean"]` is one Gaussian scale in meters, estimated from both years. The `habitat` coefficient is on the log-odds scale per one pooled standard deviation of the kernel-weighted habitat proportion. Exponentiating it gives an odds ratio for that contrast. The year coefficient changes the baseline log odds for year 2 relative to year 1. The `sample_size` diagnostic shows how many prepared rows entered the fitted model. Check the scale-boundary and precision diagnostics as well. This formula assumes the habitat slope and scale are shared across years. A year term alone does not test that assumption. Interpret the model in light of the number of years, study sites, and response design. For a scale interval, use `summary(fit, profile = TRUE)` when the profiling refit can be evaluated. ## Project the fitted relationship onto each annual map Project each map separately with the same fitted scales and pooled centering parameters. The year term is categorical, so specify its level for each prediction scenario. `kernel_scale.raster()` warns that it cannot create a categorical placeholder automatically; the prediction function below supplies the correct level explicitly. ```{r annual-projection, eval=FALSE} surface_year1 <- kernel_scale.raster(habitat_1, multiScaleR = fit, scale_center = TRUE, verbose = FALSE) surface_year2 <- kernel_scale.raster(habitat_2, multiScaleR = fit, scale_center = TRUE, verbose = FALSE) predict_year <- function(surface, year_value) { terra::predict(surface, fit$opt_mod, type = "response", fun = function(model, data, ...) { data$year <- factor(year_value, levels = levels(observations$year)) predict(model, newdata = data, ...) }) } pred_year1 <- predict_year(surface_year1, "year1") pred_year2 <- predict_year(surface_year2, "year2") ``` These surfaces show model predictions for each annual landscape and its year term. They do not represent a causal effect of management; other conditions may differ between years. ## Quick reference ```{r quick-reference, eval=FALSE} # 1. Align the annual maps on one projected grid with matching layer names. maps <- list(year1 = habitat_1, year2 = habitat_2) # 2. Assign each observation to its map; bins and scaling are pooled. prepared <- kernel_prep_by_group(points, maps, observations$year, max_D = 100, bin = TRUE, store_cell_data = FALSE) # 3. Fit the response model appropriate for the sampling design. model_data <- cbind(observations, prepared$kernel_dat) initial_model <- glm(survived ~ habitat + year, family = binomial(), data = model_data) fit <- multiScale_optim(initial_model, prepared) summary(fit) # shared habitat scale and model coefficients diagnostics(fit)$sample_size ``` | Function | Purpose | |:--|:--| | `kernel_prep_by_group()` | Assign observations to maps and prepare pooled inputs. | | `multiScale_optim()` | Fit shared spatial scales and model effects. | | `diagnostics()` | Check sample size and scale warnings. | See `vignette("quickstart", package = "multiScaleR")` for the single-map workflow and `vignette("landscape_metric_covariates", package = "multiScaleR")` for configuration metrics.