
Estimate CPUE (Catch Per Unit Effort) from a creel survey design
Source:R/creel-estimates.R
estimate_catch_rate.RdComputes CPUE estimates with standard errors and confidence intervals from a creel survey design with attached interview data. Supports both ratio-of-means (for complete trips) and mean-of-ratios (for incomplete trips) estimation methods.
Usage
estimate_catch_rate(
design,
by = NULL,
variance = "taylor",
conf_level = 0.95,
estimator = NULL,
use_trips = NULL,
truncate_at = 0.5,
targeted = TRUE,
missing_sections = "warn",
force_origin = TRUE
)Arguments
- design
A creel_design object with interviews attached via
add_interviews. The design must have an interview survey object constructed with catch and effort columns.- by
Optional tidy selector for grouping variables. Accepts bare column names (e.g.,
by = day_type), multiple columns (e.g.,by = c(day_type, location)), or tidyselect helpers (e.g.,by = starts_with("day")). When NULL (default), computes a single CPUE estimate across all interviews.Two kinds of column are not groupings and are refused: the interview id, as registered by
add_catch(),add_lengths()oradd_ages(), which holds one value per interview and so leaves no within-group variance to estimate; and columns the package derived rather than the user supplying, such as.angler_effort. A wildcard selector drops the derived columns silently; asking for one specifically is an error. A column of your own is never treated as derived, whatever it is called.- variance
Character string specifying variance estimation method. Options:
"taylor"(default, Taylor linearization),"bootstrap"(bootstrap resampling with 500 replicates), or"jackknife"(jackknife resampling, automatic JKn/JK1 selection).- conf_level
Numeric confidence level for confidence intervals (default: 0.95 for 95% confidence intervals). Must be between 0 and 1.
- estimator
Character string specifying estimation method. Options:
"ratio-of-means"(default, for complete trips),"mor"(mean-of-ratios, for incomplete trips), or"mortr"(truncated mean-of-ratios — same as"mor"buttruncate_atis mandatory and defaults to 0.5 h). MOR and MORtr require the trip_status field and error if no incomplete trips are available. See Details.- use_trips
Character string specifying which trip type to use when trip_status field is provided. Options:
"complete"uses only complete trips with ratio-of-means estimator;"incomplete"uses only incomplete trips with MOR;"all"uses all interviews (complete + incomplete) with MOR — the correct choice for roving designs;"diagnostic"estimates CPUE using both trip types and returns a comparison table. Default is NULL. When NULL andinterview_type = "roving"(set viaadd_interviews), automatically defaults to"all"+ MOR. Otherwise defaults to"complete". Parameter is ignored when trip_status field is not provided (backward compatibility). For bus-route and ice designs the set is"complete"(default),"incomplete"or"diagnostic";"all"is not an estimator there, and the roving auto-route to"all"+ MOR does not apply. See Details.- truncate_at
Numeric minimum trip duration (hours) for MOR estimation. Default is 0.5 hours (30 minutes) per Hoenig et al. (1997) to prevent unstable variance from very short trips. Trips with duration < truncate_at are excluded before MOR estimation. Set to NULL to disable truncation (research mode only). Ignored for ratio-of-means estimator.
- targeted
Logical. When
TRUE(default), all trips are used. WhenFALSE, zero-catch trips are excluded before estimation — appropriate for non-targeted species where most trips have zero catch. The estimate is then the rate among the trips that were kept, not the fishery-wide rate. Acli_warn()is emitted when more than 70\ trips have zero catch andtargeted = TRUE(possible mis-specification).Which estimators read it, and what "zero catch" means to each:
"mor"/"mortr"withoutby = species: excludes trips with zero total catch."mor"/"mortr"withby = species: excludes, per species, the trips that caught none of that species. The 70\ total catch, which made them inert on any species request (GH #304)."regression"withby = species: same per-species exclusion (GH #290)."regression"withoutby = species: ignored."ratio-of-means": ignored on every path.
- missing_sections
Character string controlling behavior when a registered section has no interview observations.
"warn"(default) emits acli_warn()and inserts an NA row withdata_available = FALSE."error"aborts withcli_abort(). Ignored for non-sectioned designs.- force_origin
Logical. When
estimator = "regression", whether to force the regression through the origin (catch ~ effort - 1). DefaultTRUE(standard CPUE\(_3\) formulation per Petrere et al. 2010). Set toFALSEto allow a free intercept. Ignored for other estimators.
Value
A creel_estimates S3 object (list) with components: estimates
(tibble with estimate, se, ci_lower, ci_upper, n columns, plus grouping
columns if by is specified), method (character: names the estimator
and the shape of the result. The base names are
"ratio-of-means-cpue", "mean-of-ratios-cpue",
"mean-of-ratios-truncated-cpue" and "regression-cpue", each
gaining a "-sections" suffix on a sectioned design. The
"-species" suffix, and the "-per-angler" suffix when
normalized, mark a species-level and an angler-normalized result
respectively; "regression-cpue-species" is returned for a species
request under estimator = "regression"),
variance_method (character: the variance that actually ran, which is the
variance argument for every estimator except "regression" –
the regression slope carries a leave-one-out jackknife SE and reports
"jackknife" whatever variance was set to),
design (reference to source creel_design), conf_level (numeric), and
by_vars (character vector of grouping variable names or NULL).
The estimator component records the estimator as you asked for it,
"mortr" included, which method cannot: it reports mandatory
truncation and the default threshold with the same string.
Details
Trip Type Selection (use_trips):
When trip_status is provided, the use_trips parameter controls which
trips are used for estimation. For access-point designs
(interview_type = "access", the default), use_trips defaults
to "complete": interviews are taken at trip end, so complete trips
are representative and avoid length-of-stay bias (Pollock et al. 1994).
For roving designs (interview_type = "roving"), use_trips
automatically defaults to "all" with the MOR estimator: the clerk
intercepts trips mid-stream, so all interviews — complete and incomplete —
are valid inputs (Hoenig et al. 1997). Set use_trips = "incomplete"
to restrict MOR to incomplete trips only. Set use_trips = "diagnostic"
to run both complete and incomplete trip estimation and return a comparison
object with difference metrics. Diagnostic mode requires both trip types.
When trip_status is not provided, use_trips is ignored for backward
compatibility with v0.2.0.
Ratio-of-Means (default): CPUE is estimated as the ratio of total catch to total effort. This is the appropriate estimator for complete trip interviews (interview at trip end). The function uses survey::svyratio() internally, which correctly accounts for the correlation between catch and effort in variance estimation.
Mean-of-Ratios (MOR):
When estimator = "mor", CPUE is estimated as the mean of individual
catch/effort ratios. This is the statistically appropriate estimator for
incomplete trip interviews (interview during trip). MOR automatically filters
to incomplete trips only and requires the trip_status field. The function
uses survey::svymean() on individual ratios.
Trip Truncation:
Very short incomplete trips can produce extreme catch/effort ratios that
dominate variance estimation. Following Hoenig et al. (1997), the default
truncate_at = 0.5 hours (30 minutes) excludes trips shorter than
this threshold before MOR estimation. The survey design is rebuilt with
the truncated sample for correct variance computation. Set truncate_at = NULL
to disable truncation (research mode only). Truncation only applies to MOR
estimator; ratio-of-means ignores this parameter.
The function performs sample size validation before estimation: errors if n < 10 (ungrouped or any group), warns if 10 <= n < 30. For MOR, validation uses the post-truncation sample size. This follows best practices for ratio estimation stability.
When grouped estimation is used (by is not NULL), survey::svyby()
correctly accounts for domain estimation variance.
Variance estimation methods:
"taylor"(default): Taylor linearization, computationally efficient and appropriate for smooth statistics like ratios."bootstrap": Bootstrap resampling with 500 replicates. Appropriate for verifying Taylor assumptions."jackknife": Jackknife resampling (automatic JKn or JK1 selection based on design). Alternative resampling method.
Note
When called on a sectioned design, no .lake_total row is
produced. Catch rates (fish per angler-hour) are not additive across
sections. Lake-wide catch rate requires a separate unsectioned call on
the full design. See estimate_total_catch() for lake-wide total
catch estimation.
estimator = "regression" on a sectioned design fits one
regression per section, on that section's interviews alone. The
leave-one-out jackknife standard error therefore rests on the interviews in
that section rather than on the whole sample, so section-level regression
standard errors are based on fewer points than the unsectioned form and are
correspondingly less stable. This is a property of sectioning rather than of
the estimator; a section with fewer than three interviews cannot be fitted
at all. species in by fits one regression per species, on
that species' catch against the same angler effort, with zero-catch
interviews retained by default.
Package Options
Complete Trip Percentage Threshold:
The package option tidycreel.min_complete_pct controls the threshold
for complete trip percentage warnings (default: 0.10 = 10\
percentage of complete trips falls below this threshold, a warning is issued
referencing Pollock et al. roving-access design best practices. Users can
set a custom threshold for their session:
options(tidycreel.min_complete_pct = 0.05)
The default 10\ scientifically valid estimation. Lowering the threshold is appropriate only for special cases with documented justification. Warnings help ensure data quality and guide users toward diagnostic validation when complete trip samples are insufficient.
See also
Other "Estimation":
compare_cpue_estimators(),
est_age_distribution(),
est_biomass(),
est_compliance(),
est_effort_camera_mi(),
est_length_distribution(),
est_mean_age(),
est_mean_length(),
estimate_effort(),
estimate_effort_aerial_glmm(),
estimate_harvest_rate(),
estimate_release_rate(),
estimate_total_catch(),
estimate_total_harvest(),
estimate_total_release()
Examples
# Basic ungrouped CPUE
calendar <- data.frame(
date = as.Date(c("2024-06-01", "2024-06-02", "2024-06-03", "2024-06-04")),
day_type = c("weekday", "weekday", "weekend", "weekend")
)
design <- creel_design(calendar, date = date, strata = day_type)
interviews <- data.frame(
date = as.Date(rep(c("2024-06-01", "2024-06-02", "2024-06-03", "2024-06-04"), each = 10)),
catch_total = rpois(40, lambda = 3),
hours_fished = runif(40, min = 1, max = 6),
trip_status = rep(c("complete", "incomplete"), each = 20),
trip_duration = runif(40, min = 1, max = 6)
)
design_with_interviews <- add_interviews(design, interviews,
catch = catch_total,
effort = hours_fished,
trip_status = trip_status,
trip_duration = trip_duration
)
#> Warning: ! No `n_anglers` provided — assuming 1 angler per interview.
#> ℹ Pass `n_anglers = <column>` to use actual party sizes for angler-hour
#> normalization.
#> ℹ If the interviews really are one angler each, pass `n_anglers = 1` to state
#> that and silence this warning.
#> Warning: 3 interviews have zero catch.
#> ℹ Zero catch may be valid (skunked) or indicate missing data.
#> ℹ Added 40 interviews: 20 complete (50%), 20 incomplete (50%)
result <- estimate_catch_rate(design_with_interviews)
#> ℹ Using complete trips for CPUE estimation
#> (n=20, 50% of 40 interviews) [default]
#> Warning: Small sample size for CPUE estimation.
#> ! Sample size is 20. Ratio estimates are more stable with n >= 30.
#> ℹ Variance estimates may be unstable with n < 30.
print(result)
#>
#> ── Creel Survey Estimates ──────────────────────────────────────────────────────
#> Method: Ratio-of-Means CPUE
#> Variance: Taylor linearization
#> Confidence level: 95%
#> Unit: fish/party-hour
#>
#> # A tibble: 1 × 5
#> estimate se ci_lower ci_upper n
#> <dbl> <dbl> <dbl> <dbl> <int>
#> 1 0.610 0.0734 0.466 0.754 20
# Grouped by day_type
result_grouped <- estimate_catch_rate(design_with_interviews, by = day_type)
#> Warning: ! Only 0.0% of interviews are complete trips (threshold: 10%)
#> ℹ Pollock et al. recommends >=10% complete trips for valid estimation
#> ℹ Consider use_trips='diagnostic' to validate incomplete trip estimates
#> ℹ Using complete trips for CPUE estimation
#> (n=20, 50% of 40 interviews) [default]
#> Warning: Small sample size in 1 group:
#> • Group day_type=weekday: n=20
#> ! Ratio estimates are more stable with n >= 30 per group.
#> ℹ Variance estimates may be unstable with n < 30.
print(result_grouped)
#>
#> ── Creel Survey Estimates ──────────────────────────────────────────────────────
#> Method: Ratio-of-Means CPUE
#> Variance: Taylor linearization
#> Confidence level: 95%
#> Grouped by: day_type
#> Unit: fish/party-hour
#>
#> # A tibble: 1 × 6
#> day_type estimate se ci_lower ci_upper n
#> <chr> <dbl> <dbl> <dbl> <dbl> <int>
#> 1 weekday 0.610 0.0734 0.466 0.754 20
# Custom confidence level
result_90 <- estimate_catch_rate(design_with_interviews, conf_level = 0.90)
#> ℹ Using complete trips for CPUE estimation
#> (n=20, 50% of 40 interviews) [default]
#> Warning: Small sample size for CPUE estimation.
#> ! Sample size is 20. Ratio estimates are more stable with n >= 30.
#> ℹ Variance estimates may be unstable with n < 30.
# Bootstrap variance estimation
result_boot <- estimate_catch_rate(design_with_interviews, variance = "bootstrap")
#> ℹ Using complete trips for CPUE estimation
#> (n=20, 50% of 40 interviews) [default]
#> Warning: Small sample size for CPUE estimation.
#> ! Sample size is 20. Ratio estimates are more stable with n >= 30.
#> ℹ Variance estimates may be unstable with n < 30.
# Mean-of-ratios for incomplete trips
result_mor <- estimate_catch_rate(design_with_interviews, estimator = "mor")
#> ℹ Using incomplete trips for CPUE estimation
#> (n=20, 50% of 40 interviews)
#> Warning: ! MOR estimator for incomplete trips. Complete trips preferred.
#> ℹ Using MOR with n=20 incomplete of 20 total interviews.
#> ℹ Incomplete trips may have length-of-stay bias (Pollock et al.).
#> ℹ Validate incomplete estimates with `validate_incomplete_trips()` (Phase 19).
#> ℹ MOR truncation: 0 trips excluded (all >= 0.5 hours)
#> Warning: Small sample size for CPUE estimation.
#> ! Sample size is 20. Ratio estimates are more stable with n >= 30.
#> ℹ Variance estimates may be unstable with n < 30.
# Mean-of-ratios with custom truncation threshold
result_mor_1h <- estimate_catch_rate(design_with_interviews, estimator = "mor", truncate_at = 1.0)
#> ℹ Using incomplete trips for CPUE estimation
#> (n=20, 50% of 40 interviews)
#> Warning: ! MOR estimator for incomplete trips. Complete trips preferred.
#> ℹ Using MOR with n=20 incomplete of 20 total interviews.
#> ℹ Incomplete trips may have length-of-stay bias (Pollock et al.).
#> ℹ Validate incomplete estimates with `validate_incomplete_trips()` (Phase 19).
#> ℹ MOR truncation: 0 trips excluded (all >= 1 hours)
#> Warning: Small sample size for CPUE estimation.
#> ! Sample size is 20. Ratio estimates are more stable with n >= 30.
#> ℹ Variance estimates may be unstable with n < 30.