--- title: "Camera Survey Analysis with tidycreel" output: rmarkdown::html_vignette: highlight: null vignette: > %\VignetteIndexEntry{Camera Survey Analysis with tidycreel} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ## Overview Remote cameras mounted at boat launches or trail heads record angler arrivals with no observer present. This enables 24-hour coverage of access points that would otherwise require continuous staffing. The `tidycreel` package supports two camera-based sub-modes: - **Counter mode** — the camera records a single daily total of incoming anglers (e.g., a passive infrared counter). The data arrive as one row per sampling day with a numeric ingress count. - **Ingress-egress mode** — the camera records individual arrival and departure timestamps for each angler or party. The raw data are paired POSIXct timestamps that are preprocessed with `preprocess_camera_timestamps()` before entering the standard effort estimation workflow. In both modes, use the `camera_status` column to identify failures. Unlike random missed observations (modeled via `missing_sections`), a camera failure leaves an **informative gap**: the number of anglers that passed during the outage is unknown and cannot be estimated from nearby observations. Remove these rows before calling `add_counts()`. ## Example Data This vignette uses three built-in datasets representing a hypothetical summer creel survey at a Nebraska reservoir boat launch in June 2024. ```{r data} library(tidycreel) data(example_camera_counts) data(example_camera_timestamps) data(example_camera_interviews) head(example_camera_counts) head(example_camera_timestamps) head(example_camera_interviews) ``` The `example_camera_counts` dataset contains 10 rows: nine operational days plus one battery failure gap. The `example_camera_timestamps` dataset has 14 raw arrival/departure pairs across four sampling days. The `example_camera_interviews` dataset has 40 complete angler interviews targeting walleye and bass across eight sampling days. ## Counter Mode ### Build a Survey Calendar Counter-mode surveys require a calendar that covers the sampling frame. Here we build one from the unique dates in the counts and interview data. ```{r counter-calendar} # Collect all unique sampling dates across datasets all_dates <- sort(unique(c( example_camera_counts$date, example_camera_interviews$date ))) # Assign day type for each date (weekday = Mon-Fri, weekend = Sat-Sun) cam_calendar <- data.frame( date = all_dates, day_type = ifelse( weekdays(all_dates) %in% c("Saturday", "Sunday"), "weekend", "weekday" ), stringsAsFactors = FALSE ) head(cam_calendar) ``` ### Design Construction Build a camera design using `creel_design()` with `survey_type = "camera"`. The `camera_mode` argument is required. Omitting it produces an informative error: ```{r counter-error, error = TRUE} creel_design( cam_calendar, date = date, strata = day_type, survey_type = "camera" ) ``` Build the correct design with `camera_mode = "counter"`: ```{r counter-design} design_counter <- creel_design( cam_calendar, date = date, strata = day_type, survey_type = "camera", camera_mode = "counter" ) print(design_counter) ``` ## Handling Informative Gaps The battery failure row on 2024-06-11 has `ingress_count = NA`. This is not a random unsampled day — the camera was physically unable to record. Including it in `add_counts()` would silently propagate a missing value into the Horvitz-Thompson estimator. ```{r gap-row} # The gap row subset(example_camera_counts, camera_status != "operational") ``` The correct approach is to **filter to operational rows** before calling `add_counts()`. This is fundamentally different from `missing_sections`, which models probabilistic non-coverage within a sampled period. A camera failure means no data exist — the effort during that period is unknown. ```{r counter-filter} # Keep only days when the camera was working counts_clean <- subset(example_camera_counts, camera_status == "operational") nrow(counts_clean) # 9 operational rows ``` ### Effort Estimation Camera effort is estimated by `est_effort_camera()`, not by the generic `estimate_effort()`. The distinction matters and is not cosmetic: a camera count is a daily total of **arrivals**, not an instantaneous count of anglers present, so summing it over days gives arrivals rather than effort. The generic estimator refuses a camera design for that reason: ```{r counter-refusal, error = TRUE} design_counter <- add_counts(design_counter, counts_clean) estimate_effort(design_counter) ``` `est_effort_camera()` converts counts to effort by calibrating them against interview data: for each stratum it estimates `rho`, the hours of effort per camera count, from the days that carry both a count and interviews, then applies it to that stratum's total counts. The counts are the first phase of a double sample and the days carrying interviews are the second, so the estimator is a double-sampling ratio estimator (Cochran 1977, Chapter 12); the ratio's variance is Cochran's eq. 2.46 with the finite-population correction omitted. Calibrating camera counts against paired creel observations is established practice -- Hartill et al. (2016), van Poorten et al. (2015), Eckelbecker et al. (2022) -- but each of those uses a different estimator, and this ratio-of-totals form is the package's own application rather than a reproduction of any of them. See `?est_effort_camera` for the full note. `example_camera_interviews` records one row per angler, so `n_anglers = 1` states that `hours_fished` is already an individual angler's hours. Supplying it is what earns the result its `angler-hours` label — without it the ratio inherits whatever unit the effort column holds, which the package cannot identify, and the unit is reported as unknown. ```{r counter-effort} effort_counter <- suppressWarnings(est_effort_camera( design_counter, interviews = example_camera_interviews, n_anglers = 1 )) print(effort_counter) ``` The `estimate` column is total angler-hours across the survey period. The standard error carries two named components, which `se_components` reports separately: ```{r counter-se-components} effort_counter$se_components ``` `count_sampling` is the sampling variance of the camera counts themselves; `calibration` is the variance of the estimated `rho`. Splitting them shows which half of the uncertainty dominates — here the calibration ratio does, so more interview days would buy more precision than more camera days would. ## Ingress-Egress Mode When the camera records individual arrival and departure timestamps, use `preprocess_camera_timestamps()` to aggregate the raw pairs into daily effort hours before calling `add_counts()`. ### Preprocess Timestamps ```{r ie-preprocess} daily_effort <- preprocess_camera_timestamps( example_camera_timestamps, date_col = date, ingress_col = ingress_time, egress_col = egress_time ) print(daily_effort) ``` `preprocess_camera_timestamps()` sums all valid trip durations within each day and returns a data frame with `date` and `daily_effort_hours`. Rows where `egress_time < ingress_time` (negative durations) are dropped from that sum, and a warning reports how many. Read that warning rather than dismissing it, because the returned data will not repeat it. A dropped pair does not make the day `NA`; it makes the day's total smaller. On the fixture above, flipping a single ingress/egress pair takes one day from 11 hours to 7.75 — a 30% undercount that arrives as an ordinary number, indistinguishable in `daily_effort_hours` from a day that genuinely saw less fishing. The warning is the only place the exclusion is visible, so treat a non-zero count as something to resolve in the source data before estimating, not as routine noise. Because `add_counts()` requires all design strata columns, merge the day type back in from the raw timestamps: ```{r ie-merge-strata} day_type_key <- unique(example_camera_timestamps[, c("date", "day_type")]) daily_effort <- merge(daily_effort, day_type_key, by = "date") print(daily_effort) ``` ### Build the Design and Estimate Effort ```{r ie-design} ie_calendar <- data.frame( date = daily_effort$date, day_type = daily_effort$day_type, stringsAsFactors = FALSE ) design_ie <- creel_design( ie_calendar, date = date, strata = day_type, survey_type = "camera", camera_mode = "ingress_egress" ) design_ie <- add_counts(design_ie, daily_effort) ``` Ingress-egress counts are already in hours, so the calibration ratio here is close to dimensionless: it corrects camera-measured hours to interview-measured angler-hours rather than converting a count into a duration. The estimator is the same one. ```{r ie-effort} ie_interviews <- example_camera_interviews[ example_camera_interviews$date %in% daily_effort$date, ] effort_ie <- suppressWarnings(est_effort_camera( design_ie, interviews = ie_interviews, n_anglers = 1 )) print(effort_ie) ``` The standard error is `NA` here, and that is the estimator working rather than failing. These four sampling days give the `weekend` stratum only one day carrying both a count and interviews, and a single paired day gives the calibration ratio no measurable spread. Reporting `0` for that variance would present the most uncertain calibration as the most precise one, so the package carries it as `NA` and says which stratum is responsible. A second matched interview day in that stratum recovers a standard error. ## Catch Estimation Camera designs estimate **effort only**. `estimate_catch_rate()` still works — a catch rate comes from the interviews and does not involve the camera at all: ```{r catch-rate} design_catch <- suppressMessages(add_interviews( design_counter, example_camera_interviews, catch = walleye, effort = hours_fished, trip_status = trip_status, n_anglers = 1 )) catch_rate <- suppressWarnings(estimate_catch_rate(design_catch)) print(catch_rate) ``` A **total** catch is a different matter, and `estimate_total_catch()` refuses a camera design: ```{r total-catch-refusal, error = TRUE} estimate_total_catch(design_catch) ``` A total is the product of a rate and an effort, and this function builds its own effort by the generic route — the one that sums arrivals. It would therefore multiply a rate per angler-hour by a count of arrivals and report the product as fish. The number looked entirely plausible, which is why this is refused rather than warned about. There is no camera catch estimator to reach for instead. `est_effort_camera()` gives calibrated angler-hours, and multiplying those by the catch rate above is arithmetic a reader can do deliberately — but the package will not do it silently, because the standard error of that product needs the calibration variance and the rate variance combined, and nothing here does that yet. `estimate_total_harvest()` and `estimate_total_release()` refuse camera designs for the same reason. ## Summary The table below contrasts the two camera sub-modes: ```{r summary-table, echo = FALSE} knitr::kable(data.frame( Feature = c( "Input data", "Preprocessing step", "Effort unit", "Gap handling", "camera_mode value" ), `Counter mode` = c( "One count per day", "None (counts used directly)", "Daily ingress count", "Exclude camera_status != 'operational' rows", '"counter"' ), `Ingress-egress mode` = c( "POSIXct arrival/departure pairs", "preprocess_camera_timestamps()", "Daily effort-hours", "Negative durations warned and excluded", '"ingress_egress"' ), check.names = FALSE ), caption = "Comparison of camera survey sub-modes in tidycreel") ``` Both sub-modes are estimated by the same function, `est_effort_camera()`, so no changes to downstream code are required when switching between them. Neither sub-mode goes through `estimate_effort()`, which refuses camera designs, nor through `estimate_total_catch()`, `estimate_total_harvest()` or `estimate_total_release()`, which refuse them for the same reason. ## References - Jones, C. M., & Pollock, K. H. (2012). Recreational survey methods: estimation of effort, harvest, and abundance. Chapter 19 in *Fisheries Techniques* (3rd ed.), pp. 883-919. American Fisheries Society. - Malvestuto, S. P. (1996). Sampling the recreational angler. Chapter 20 in *Fisheries Techniques* (2nd ed.), pp. 591-623. American Fisheries Society. - Cochran, W. G. (1977). *Sampling Techniques* (3rd ed.). Wiley. Section 2.11 (ratio estimator and its variance, eq. 2.46) and Chapter 12 (double sampling), Section 12.9, p. 343. - Hartill, B. W., Payne, G. W., Rush, N., & Bian, R. (2016). Bridging the temporal gap: continuous and cost-effective monitoring of dynamic recreational fisheries by web cameras and creel surveys. *Fisheries Research*, 183, 488-497. - van Poorten, B. T., Carruthers, T. R., Ward, H. G. M., & Varkey, D. A. (2015). Imputing recreational angling effort from time-lapse cameras using an hierarchical Bayesian model. *Fisheries Research*, 172, 265-273. - Eckelbecker, R. W., Coleman, T. S., & Catalano, M. J. (2022). Incorporating time-lapse digital cameras into creel surveys at three Alabama reservoirs. *North American Journal of Fisheries Management*, 42, 1349-1358. - Hartill, B. W., Taylor, S. M., Keller, K., & Weltersbach, M. S. (2020). Digital camera monitoring of recreational fishing effort: applications and challenges. *Fish and Fisheries*, 21, 204-215. A review of camera monitoring practice; it presents no estimator or variance.