--- title: "Getting Started with soReta" output: rmarkdown::html_vignette: md_extensions: -smart vignette: > %\VignetteIndexEntry{Getting Started with soReta} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ## What soReta does `soReta` takes your camera-trap dataset and returns datasets ready for statistical analysis: GLMM, GAMM, occupancy, kernel density and circular activity analysis, temporal interactions between species, capture-mark-recapture -- without you having to write the aggregation code yourself. To do this, it needs a recordTable featuring a station (Station), a date/time (DateTimeOriginal), and a species (Species) column formatted in `camtrapR`'s standard style (Niedballa et al. 2016), along with a matching `camOp` effort matrix. Additionally, some functions require an individual-count column; unlike the core variables, this column does not need to follow camtrapR formatting. This vignette walks through the main families of functions using `soReta`'s own bundled example data: `camOp_soReta` (the effort matrix, already built via `camtrapR::cameraOperation()` from `camtraps_soReta`, a station table -- see `?camtraps_soReta` for its own format), `recordTable_soReta` (species detections), and `recordTableIndividuals_soReta` (individually-identifiable detections, for the capture-mark-recapture section) -- all entirely synthetic; see `?camOp_soReta`, `?recordTable_soReta`, and `?recordTableIndividuals_soReta` for details on how they were built. ```{r setup} library(soReta) head(camOp_soReta[, 1:6]) # only the first 6 of 120 days, or the table gets too wide head(recordTable_soReta) head(recordTableIndividuals_soReta) ``` ## 1. GLMM/GAMM-ready datasets: the `build_site_*` and `build_day_total` family These functions aggregate independent events into counts (N events) and Relative Abundance Index (RAI, O'Brien et al. 2003), at whatever temporal grain you need. Thay all use a configurable threshold, in minutes (`threshold_min`), to determine which events count as independent. All of them recompute event independence from the raw timestamps (never relying on a pre-set threshold baked into the `recordTable`). ```{r} # 1 row = 1 site x 1 day (no RAI column here: with n_days_active always # equal to 1, RAI would just be N x 100 -- no extra information over N) ds_day <- build_site_day(recordTable_soReta, camOp_soReta, threshold_min = 30) head(ds_day[ds_day$N_sp > 0, ]) ``` ### Choosing how events are grouped: `independence_method` and `require_uninterrupted` Before going further, it's worth understanding exactly how `threshold_min` turns raw timestamps into "independent events" -- because two further arguments (`independence_method`, `require_uninterrupted`), present on `build_site_day()` and on every other function in this package that takes a `threshold_min`, let you control that precisely. `"chain"/FALSE` are set as the defaults. This choice is usually unsettled in the published literature, though, I encourage you to report which `independence_method`/`require_uninterrupted` combination you used if you publish results built with soReta, the same way you'd report `threshold_min` itself. **`independence_method`** (`"chain"`, default, or `"window"`): `"chain"` compares each photo only to the one immediately before it -- a close-together run of photos can, in principle, span far longer than `threshold_min`, as long as no single gap between consecutive photos exceeds it. `"window"` instead compares each photo to the start of the current bout: a bout can never last longer than `threshold_min`. The two methods only disagree on bouts long enough to contain more than one sub-threshold gap in a row -- illustrated below on a real sequence from `recordTable_soReta` itself (a wolf pack at station S_02, three photos 16 and 25 minutes apart -- "chain" counts this as one event, "window" as two, because the first 16-minute gap already used up most of the 30-minute budget before the second photo arrived): ```{r} wolf_burst <- recordTable_soReta[ recordTable_soReta$Station == "S_02" & recordTable_soReta$Species == "wolf" & format(recordTable_soReta$DateTimeOriginal, "%Y-%m-%d") == "2026-02-14", ] wolf_burst wolf_burst_camOp <- camOp_soReta["S_02", "2026-02-14", drop = FALSE] build_site_day(wolf_burst, wolf_burst_camOp, threshold_min = 30, independence_method = "chain")$wolf_N build_site_day(wolf_burst, wolf_burst_camOp, threshold_min = 30, independence_method = "window")$wolf_N ``` **`require_uninterrupted`** (`FALSE`, default, or `TRUE`): implements the third independence criterion of O'Brien et al. (2003) -- the paper most commonly cited for the whole convention. Two photos of the same species are only subject to the time threshold at all if they are literally consecutive in the station's raw, all-species timeline: if a photo of any other species falls chronologically between them, the two are independent regardless of elapsed time. Another real sequence from `recordTable_soReta` shows this directly (station S_04: a wolf, then a red fox 10 minutes later, then a wolf again 10 minutes after that): ```{r} interrupted_burst <- recordTable_soReta[ recordTable_soReta$Station == "S_04" & format(recordTable_soReta$DateTimeOriginal, "%Y-%m-%d") == "2026-03-10", ] interrupted_burst interrupted_burst_camOp <- camOp_soReta["S_04", "2026-03-10", drop = FALSE] build_site_day(interrupted_burst, interrupted_burst_camOp, threshold_min = 30, require_uninterrupted = FALSE)$wolf_N build_site_day(interrupted_burst, interrupted_burst_camOp, threshold_min = 30, require_uninterrupted = TRUE)$wolf_N ``` With `require_uninterrupted = FALSE` the two wolf photos are 20 minutes apart (below the 30-minute threshold) and collapse into one event. With `require_uninterrupted = TRUE` the red fox in between breaks the run, so both wolf photos count as independent regardless of the 20-minute gap. `independence_method` and `require_uninterrupted` combine freely, giving four possible conventions in total. Every function below that takes a `threshold_min` supports both -- from here on, this vignette just points back to this section rather than re-explaining them each time. O'Brien et al. (2003) actually define three criteria for independence: (1) consecutive photos of different individuals of the same or different species, (2) consecutive photos of the same species more than 0.5 hours apart, and (3) non-consecutive photos of the same species. `independence_method` implements criterion (2) (as "chain" or "window"), and `require_uninterrupted` implements criterion (3). Criterion (1) is deliberately not implemented: individual recognition is only feasible for some species (e.g. those with strong coat-pattern variation or sexual dimorphism) and not others, and applying it selectively -- crediting extra independent events only where individuals happen to be distinguishable -- would bias N and RAI inconsistently across species within the same study. Since most studies now monitor several species at once rather than a single one, this package does not implement criterion (1) for any species. ### The rest of the `build_site_*` family The other functions in the `build_site_*` family perform the same task as `build_site_day()`, but aggregate events at a different temporal grain: - `build_site_block()` -- a fixed-size window, in days, set via `block_days` - `build_site_month()` -- calendar months - `build_site_period()` -- named periods you define yourself: `period_names` sets their labels, `period_starts` their start dates All three also take `min_days` (default `NULL`, no filtering): set it to drop any site x interval combination where the station was active for fewer than that many days -- useful for excluding a short, unreliable block or month from the dataset rather than keeping it in with an unreliably low sample size. ```{r} # 1 row = 1 site x 1 fixed N-day block ds_week <- build_site_block(recordTable_soReta, camOp_soReta, block_days = 7, threshold_min = 30) head(ds_week) # 1 row = 1 site x 1 calendar month ds_month <- build_site_month(recordTable_soReta, camOp_soReta, threshold_min = 30) head(ds_month) # 1 row = 1 site x 1 named period, recurring every year -- our example # data only spans January to April 2026, so a two-period split fits # better here than a full four-season year ds_period <- build_site_period( recordTable_soReta, camOp_soReta, period_names = c("early", "late"), period_starts = c("01/01/2026", "01/03/2026"), threshold_min = 30 ) head(ds_period) ``` `build_site_block()`, `build_site_month()`, and `build_site_period()` all include also a `mid_day` column: the day roughly halfway between the start and end of each block/month/period, rounded down when the span is even so it always lands on a real day. It's there to make joining an external daily covariate straightforward -- lunar illumination, average temperature, whatever your analysis needs -- without you having to compute a representative date yourself: ```{r, eval = FALSE} # example: joining a per-day covariate you already have, e.g. lunar fraction ds_month |> dplyr::left_join(my_lunar_fraction, by = c("mid_day" = "Date")) ``` Station-level covariates (elevation, habitat type, distance to the nearest road...) join just as directly -- every function in this package returns a Station column, so a single left_join() is all it takes: ```{r, eval = FALSE} ds_month |> dplyr::left_join(my_station_covariates, by = "Station") ``` To exclude species you're not interested in (domestic animals, unclear identification, etc.) from every downstream calculation, filter recordTable before calling any build_*() function -- RAI and richness are recomputed correctly from whatever species remain: ```{r, eval = FALSE} recordTable_soReta |> dplyr::filter(!Species %in% c("...")) |> build_site_month(camOp_soReta, threshold_min = 30) ``` Finally, two related functions collapse a different dimension: `build_site_total()` gives one row per site over the whole survey -- handy for data exploration, community analyses (e.g. `vegan::vegdist()`), and modelling -- while `build_day_total()` gives one row per calendar day, summed across all stations. `build_day_total()` also returns the number of active stations that day and RAI values, calculated as the sum of events for each species divided by the number of active stations that day, times 100, analogous to the classic RAI proposed by O'Brien et al. (2003). This shape is specifically suited to GLMM/GAMM analyses of behaviour in response to day-to-day environmental variation, such as the lunar cycle, and can be readily further aggregated for tests such as the chi-square test. Both functions also take `independence_method`/`require_uninterrupted`, as explained above. ```{r} # 1 row = 1 site ds_site_tot <- build_site_total(recordTable_soReta, camOp_soReta, threshold_min = 30) head(ds_site_tot) # 1 row = 1 day ds_day_tot <- build_day_total(recordTable_soReta, camOp_soReta, threshold_min = 30) head(ds_day_tot) ``` ## 2. Group size For species where the number of animals per photo matters -- herd size, pack size, flock size -- the `build_group_size_*` family mirrors the `build_site_*` family, but tracks group size instead of just counting events. All seven functions take a `countCol` argument: the name of the column in `recordTable` holding the number of animals per photo. In `recordTable_soReta` that column is called `"N_individuals"`. They also support `independence_method` and `require_uninterrupted`, exactly as explained in Section 1: the boundary of a "bout" is defined identically here and in `build_site_*()`, so the event counts and the group sizes always agree on where one passage ends and the next begins. `countCol` is only needed for this family -- every other function in the package works without it. `build_group_size_events()` is the base of the family: one row per independent event, with no temporal aggregation at all, not even daily. It has no counterpart in `build_site_*()` -- there, an event is always worth exactly 1 towards the count, so a raw, unaggregated listing of events wouldn't add any information beyond `build_site_day()` itself. Here, the raw group size per event is itself the finest-grained information there is, before any averaging or summing collapses it. Within a single bout, `group_size` is always the maximum count across the photos in that bout, never the sum: summing would treat repeated photos of the same passing group as if they were separate individuals. ```{r} gr_size_ev <- build_group_size_events(recordTable_soReta, camOp_soReta, countCol = "N_individuals", independence_method = "window", threshold_min = 30) head(gr_size_ev) ``` `build_group_size_day()`, `build_group_size_block()`, `build_group_size_month()`, `build_group_size_period()`, `build_group_size_total()`, and `build_group_day_total()` aggregate `build_group_size_events()` at the same temporal grains as the `build_site_*` family, each producing three columns per species: `_mean_group_size`, `_max_group_size`, and `_sum_group_size` -- plus `n_days_active`/`n_stations_active`, `N_sp`, and `_RAI_individuals` (= sum_group_size / n_days_active * 100, the individual-based counterpart of the event-based RAI in `build_site_*()`) on every level except the daily one, where they would be redundant. Unlike `build_site_*()`, a species with no events in a given day/period gets `NA` in the group-size columns, not `0`: a group size of zero never happens, and using `0` would silently distort any later averaging across days or periods. ```{r} # most site x day x species combinations are genuinely empty # at this grain, so filter for the informative rows ds_group_day <- build_group_size_day(recordTable_soReta, camOp_soReta, countCol = "N_individuals", threshold_min = 30) ds_group_day[!is.na(ds_group_day$red_deer_mean_group_size), ] |> head() |> print(width = 90) ``` ```{r} ds_group_total <- build_group_size_total(recordTable_soReta, camOp_soReta, countCol = "N_individuals", threshold_min = 30) print(ds_group_total, width = 100) ``` ## 3. Occupancy detection histories `build_occupancy_day()` and `build_occupancy_block()` return, for each species, a site x occasion matrix of 0/1/NA -- ready for `unmarked`, `ubms`, or `spOccupancy`. `NA` marks an occasion with no active camera effort at all; `0` means the station was active but the species was not detected. `build_occupancy_day()` creates a daily matrix; `build_occupancy_block()` aggregates days over a specified interval (`block_days`), as several functions in the `build_site_*` and `build_group_size_*` families also do. Only `build_occupancy_block()` takes `min_days` -- and, unlike elsewhere in the package, it defaults to `1` here rather than `NA`: a block needs at least one active day to be trusted as a real `0` (absence) rather than left as `NA` (effort too low to draw any conclusion). These two functions have no independence threshold at all (presence/absence doesn't need one), so `independence_method`/`require_uninterrupted` don't apply here. ```{r} occ_day <- build_occupancy_day(recordTable_soReta, camOp_soReta) head(occ_day[["wolf"]]) occ_week <- build_occupancy_block(recordTable_soReta, camOp_soReta, block_days = 7, min_days = 4) head(occ_week[["wolf"]]) ``` ## 4. Kernel density and circular activity analysis `extract_radians()` converts detection times into radians (clock time, 0 to 2*pi) -- the format `overlap::densityPlot()`, `overlap::overlapPlot()`, and `activity::fitact()` expect, one vector per species. It computes independence over the entire detection history before any subsetting, so a threshold-based cut never splits one continuous visit into two. Like the functions above, it also supports `independence_method`/`require_uninterrupted` (Section 1). ```{r} rad <- extract_radians(recordTable_soReta, threshold_min = 30) names(rad) # overlap::densityPlot(rad[["wolf"]], xcenter = "midnight") ``` With no further arguments, each species' result is one flat vector, covering the whole dataset. The `group_col` argument splits it further -- one nested list level per column you name, in that order -- useful if you want a separate activity-pattern estimate per station, per season, or both, without calling the function once per subgroup yourself: ```{r} recordTable_soReta$bimonth <- paste0("bim", ceiling(lubridate::month(recordTable_soReta$DateTimeOriginal) / 2)) # one level: split by station rad_by_station <- extract_radians(recordTable_soReta, threshold_min = 30, group_col = "Station") rad_by_station[["wolf"]][["S_01"]] # two levels: station, then two-month period within station rad_by_station_bimonth <- extract_radians(recordTable_soReta, threshold_min = 30, group_col = c("Station", "bimonth")) names(rad_by_station_bimonth[["wolf"]][["S_01"]]) # check which periods actually # exist for this species/station # before indexing further rad_by_station_bimonth[["wolf"]][["S_01"]][["bim1"]] ``` Don't confuse `group_col` with the separate `group_cols` argument. `group_cols` decides which events get compared against each other when checking independence -- by default, only events at the same station and of the same species are compared, which is the right choice for almost every study. `group_col`, instead, only decides how the *already-computed* result gets organised into nested lists for you to browse -- it never changes which photos count as one event. One situation where you might genuinely want to change `group_cols`: two camera traps placed a few meters apart, effectively watching the same spot (a narrow trail, a den entrance). An animal walking past could trigger both cameras within seconds of each other -- two separate "events" at two different `Station` values, even though it was really one single passage. Merging that pair into one combined station name before calling `extract_radians()`, and passing that combined name via `group_cols`, treats the two cameras as one: ```{r} recordTable_soReta$Cluster <- ifelse( recordTable_soReta$Station %in% c("S_01", "S_02"), "S_01_S_02_cluster", recordTable_soReta$Station ) # group_cols changes what counts as independent: the two real stations # are now merged for this purpose rad_clustered <- extract_radians(recordTable_soReta, threshold_min = 30, group_cols = c("Cluster", "Species")) # group_col, unchanged in meaning: still just splits the finished # result -- here, by the same clustered column, just to display it rad_clustered_split <- extract_radians(recordTable_soReta, threshold_min = 30, group_cols = c("Cluster", "Species"), group_col = "Cluster") rad_clustered_split[["wolf"]][["S_01_S_02_cluster"]] ``` One more detail worth knowing before plotting: the radians this function returns are in **clock time**, not solar time. For comparisons across seasons or across sites at different latitudes, convert the result with `activity::solartime()` or `overlap::sunTime()` before handing it to `densityPlot()`/`fitact()` -- `extract_radians()` itself does not do this conversion. If you specifically need one separate object per species/group combination in your environment -- to reuse an older script that was already written that way -- `radians_to_env()` takes the (possibly nested) list `extract_radians()` returns and creates one object per vector, named by prefix + species + group levels: ```{r} Rad_obj <- radians_to_env(rad_by_station, prefix = "Rad_", sep = "-") Rad_obj str(get(Rad_obj[1])) ``` ## 5. Temporal interactions between species `build_species_pair_intervals()` computes the AB/BA time intervals between two species (Parsons et al. 2016, Niedballa et al. 2019): the time from a detection of species A to the next detection of species B at the same station, and vice versa. Intervals with no follow-up detection before the end of a station's monitoring period are not discarded -- they are flagged `censored = TRUE`, so you can hand them to a proper survival model (`survival::survreg()`) instead of losing that information. It also supports `independence_method`/`require_uninterrupted` (Section 1). ```{r} pair_data <- build_species_pair_intervals( recordTable_soReta, camOp_soReta, speciesA = "wolf", speciesB = "wild boar", threshold_min = 30 ) head(pair_data) ``` Its output is exactly what all three of Niedballa et al. (2019)'s AB/BA-level methods need, unchanged: ```{r} # 1. linear model (log-transformed, as Niedballa et al. 2019 did to meet # linear model assumptions) mod <- lm(log(delta_hours) ~ direction, data = pair_data[!pair_data$censored, ]) summary(mod) # 2. Mann-Whitney U-test wilcox.test(delta_hours ~ direction, data = pair_data[!pair_data$censored, ]) # 3. permutation test (shuffle species labels, keeping real timestamps # and each species' total count fixed, recompute the AB/BA ratio # each time) set.seed(1) n_perm <- 999 rt_pair <- recordTable_soReta[recordTable_soReta$Species %in% c("wolf", "wild boar"), ] obs_ratio <- median(pair_data$delta_hours[pair_data$direction == "AB" & !pair_data$censored]) / median(pair_data$delta_hours[pair_data$direction == "BA" & !pair_data$censored]) null_ratio <- replicate(n_perm, { rt_perm <- rt_pair rt_perm$Species <- sample(rt_perm$Species) out_perm <- build_species_pair_intervals(rt_perm, camOp_soReta, speciesA = "wolf", speciesB = "wild boar", threshold_min = 30) median(out_perm$delta_hours[out_perm$direction == "AB" & !out_perm$censored]) / median(out_perm$delta_hours[out_perm$direction == "BA" & !out_perm$censored]) }) mean(null_ratio >= obs_ratio, na.rm = TRUE) # empirical p-value ``` **A methodological caution before interpreting any of these results**: with two species detected at very different rates (a common situation), a strong AB/BA asymmetry can appear even with no real behavioural avoidance -- it's largely an artefact of how often each species is detected at all (Dymit & Levi 2025). Before drawing conclusions, compare the two species' overall detection rates (`build_site_total()` is the quickest way); the permutation test above already accounts for this by holding each species' total count fixed under the null. `build_species_pair_interruptions()` covers the complementary AA/BB/ABA/BAB intervals from the same framework (also supports `independence_method`/`require_uninterrupted`). Unlike AB/BA, **Niedballa et al. (2019) only applied the permutation test to these four interval types** -- not the linear model or Mann-Whitney U-test used above, which in their paper were reserved for AB/BA specifically: ```{r} interruptions <- build_species_pair_interruptions( recordTable_soReta, speciesA = "wolf", speciesB = "wild boar", threshold_min = 30 ) table(interruptions$type) # permutation test on AA vs BB (same logic as above, applied to the # interruption-type intervals instead of AB/BA) obs_ratio_aabb <- median(interruptions$delta_hours[interruptions$type == "AA"]) / median(interruptions$delta_hours[interruptions$type == "BB"]) null_ratio_aabb <- replicate(n_perm, { rt_perm <- rt_pair rt_perm$Species <- sample(rt_perm$Species) out_perm <- build_species_pair_interruptions(rt_perm, speciesA = "wolf", speciesB = "wild boar", threshold_min = 30) median(out_perm$delta_hours[out_perm$type == "AA"]) / median(out_perm$delta_hours[out_perm$type == "BB"]) }) mean(null_ratio_aabb >= obs_ratio_aabb, na.rm = TRUE) ``` ## 6. Hierarchical diel activity models `build_diel_binomial_block()`, `build_diel_binomial_month()`, and `build_diel_binomial_period()` turn detection times into a binomial success/failure dataset -- how many active days a species was detected in each time-bin (`bin_hours` sets the width of each bin, e.g. 1 for hourly bins), at whatever temporal grain you choose -- ready for the trigonometric GLMMs and cyclic cubic spline HGAMs described in Iannarilli et al. (2024)'s tutorial for hierarchical diel activity models. There is no independence threshold here by design (see the caution in Section 4): `detected` is already a per-day, per-bin binary indicator, so more photos in the same bin don't change the result. ```{r, eval = requireNamespace("GLMMadaptive", quietly = TRUE) && requireNamespace("mgcv", quietly = TRUE)} diel_month <- build_diel_binomial_month(recordTable_soReta, camOp_soReta, bin_hours = 1, min_days = 10) diel_wolf <- diel_month[diel_month$Species == "wolf", ] diel_wolf$Station <- factor(diel_wolf$Station) # trigonometric GLMM (Iannarilli et al. 2024, section 3.3) trig_model <- GLMMadaptive::mixed_model( fixed = cbind(success, failure) ~ cos(2 * pi * Time / 24) + sin(2 * pi * Time / 24) + cos(2 * pi * Time / 12) + sin(2 * pi * Time / 12), random = ~ 1 | Station, data = diel_wolf, family = binomial() ) summary(trig_model) ``` ## 7. Classic capture-mark-recapture For individually identifiable species, `build_cmr_day()` and `build_cmr_block()` build an individual x occasion capture history from a recordTable with an individual-ID column (the same shape as camtrapR's own `recordTableIndividual()` output). Station plays no role in the result. `as_capture_strings()` converts that into the capture-history string format used by RMark, MARK, and `marked`. Neither of these two functions has an independence threshold: distinct individual sightings on the same day are already unambiguous. `recordTableIndividuals_soReta` provides this directly: 8 individually-recognizable red deer (`"RD_01"` to `"RD_08"`), built by tagging the "red deer" detections already present in `recordTable_soReta` -- same stations, same `camOp_soReta` used throughout this vignette, no separate dataset or dependency needed. ```{r} ch <- build_cmr_block(recordTableIndividuals_soReta, camOp_soReta, block_days = 7, min_days = 4) as_capture_strings(ch) ``` ## Where to go from here Every function's help page (`?build_site_month`, `?build_species_pair_intervals`, ...) documents its parameters and return value in full. This vignette only shows the default behaviour -- most functions have a `threshold_min`, `min_days`, or similar argument worth tuning to your own study design. ## References - Dymit, E., Garcia‐Anleu, R., Levi, T., 2025. Avoidance–attraction ratios incorrectly characterize behavioral interactions with camera trap data. Ecology 106, e70134. https://doi.org/10.1002/ecy.70134 - Iannarilli, F., Gerber, B.D., Erb, J., Fieberg, J.R., 2024. A ’how-to’ guide for estimating animal diel activity using hierarchical models. The Journal of animal ecology. - Niedballa, J., Sollmann, R., Courtiol, A., Wilting, A., 2016. camtrapR: an R package for efficient camera trap data management. Methods Ecol Evol 7, 1457–1462. https://doi.org/10.1111/2041-210X.12600 - Niedballa, J., Wilting, A., Sollmann, R., Hofer, H., Courtiol, A., 2019. Assessing analytical methods for detecting spatiotemporal interactions between species from camera trapping data. Remote Sens Ecol Conserv 5, 272–285. https://doi.org/10.1002/rse2.107 - O’Brien, T.G., Kinnaird, M.F., Wibisono, H.T., 2003. Crouching tigers, hidden prey: Sumatran tiger and prey populations in a tropical forest landscape, in: Animal Conservation Forum. Cambridge University Press, pp. 131–139. - Parsons, A.W., Bland, C., Forrester, T., Baker-Whatton, M.C., Schuttler, S.G., McShea, W.J., Costello, R., Kays, R., 2016. The ecological impact of humans and dogs on wildlife in protected areas in eastern North America. Biological Conservation 203, 75–88.