Skip to contents

Overview

Creel surveys sample a fraction of days in a season. Design-based estimators in tidycreel automatically account for unsampled days through survey weights — so when you call estimate_effort() or estimate_total_catch(), the result is already a season-total estimate, not just a sample-day sum.

This vignette shows how to:

  1. Obtain a full-season total from a single design
  2. Break the season into monthly totals using separate monthly designs
  3. Combine monthly estimates to a season or annual total
  4. Assemble multi-estimate reports with season_summary()

How Design-Based Extrapolation Works

When you create a design with creel_design(), the survey object assigns a weight to each sampled day. A day sampled at rate ff receives weight 1/f1/f, so its observed angler-hours represent 1/f1/f days of that stratum type. The estimator then sums weighted observations across strata to produce a population total for the entire design period.

In practice this means:

  • A season design covering May–September gives you a season-total estimate.
  • A monthly design covering just June gives you a June-total estimate.
  • You do not need to multiply results by any “expansion factor” — the weights already perform the extrapolation.

The same target logic applies when the calendar includes schedule-defined special strata. If generate_schedule(..., special_periods = ...) resolved opener or holiday days into a final analysis stratum, estimate_effort(..., target = "stratum_total") and target = "period_total" expand within those declared strata exactly as they do for the usual weekday/weekend case. The important constraint is that the calendar passed to creel_design() must use the resolved final analysis stratum rather than the old baseline label. Sparse special strata still trigger the usual variance diagnostics: warnings for unstable small strata and explicit single-PSU errors when variance cannot be estimated.

Building a Season-Long Dataset

library(tidycreel)

set.seed(2024)

# Full season: May 1 – September 30 (184 days)
all_dates <- seq(as.Date("2024-05-01"), as.Date("2024-09-30"), by = "1 day")
day_types <- ifelse(
  as.integer(format(all_dates, "%u")) %in% c(6L, 7L), "weekend", "weekday"
)

season_calendar <- data.frame(
  date       = all_dates,
  day_type   = day_types,
  open_hours = 10
)

# Stratified random sample: 30% of weekdays, 50% of weekends
set.seed(2024)
sampled <- ave(
  seq_along(all_dates), day_types,
  FUN = function(i) {
    rate <- if (day_types[i[1]] == "weekend") 0.50 else 0.30
    as.integer(runif(length(i)) < rate)
  }
)
sampled_calendar <- season_calendar[as.logical(sampled), ]

# Generate plausible effort counts (angler-hours per count)
# Weekend counts higher than weekday
sampled_counts <- data.frame(
  date = sampled_calendar$date,
  day_type = sampled_calendar$day_type,
  n_anglers = ifelse(
    sampled_calendar$day_type == "weekend",
    round(rnorm(sum(sampled), mean = 55, sd = 18)),
    round(rnorm(sum(sampled), mean = 28, sd = 12))
  )
)
sampled_counts$n_anglers <- pmax(sampled_counts$n_anglers, 0L)

nrow(sampled_calendar) # Number of sampled days
#> [1] 58

Season-Total Effort

Pass the sampled calendar to creel_design(), attach the counts, and call estimate_effort(). The result is the estimated total angler-hours for the entire May–September season.

season_design <- creel_design(
  sampled_calendar,
  date = date, strata = day_type
)
season_design <- add_counts(season_design, sampled_counts)
#> Warning in svydesign.default(ids = psu_formula, strata = strata_formula, : No
#> weights or probabilities supplied, assuming equal probability

season_effort <- estimate_effort(season_design)
season_effort$estimates
#> # A tibble: 1 × 7
#>   estimate    se se_between se_within ci_lower ci_upper     n
#>      <dbl> <dbl>      <dbl>     <dbl>    <dbl>    <dbl> <int>
#> 1     2004  121.       121.         0    1763.    2245.    58

The estimate column is the season-total angler-hours; se is the standard error of that total.

Monthly Effort Totals

To break the season into monthly totals, subset the calendar and counts to each month, build a separate monthly design, and estimate. Monthly designs are independent, so their variances can be combined later.

months <- 5:9
month_labels <- c("May", "June", "July", "August", "September")

monthly_effort <- lapply(seq_along(months), function(i) {
  m <- months[i]
  cal_m <- sampled_calendar[format(sampled_calendar$date, "%m") == sprintf("%02d", m), ]
  cnt_m <- sampled_counts[format(sampled_counts$date, "%m") == sprintf("%02d", m), ]

  if (nrow(cal_m) == 0) {
    return(NULL)
  }

  des_m <- creel_design(cal_m, date = date, strata = day_type)
  des_m <- add_counts(des_m, cnt_m)
  est <- estimate_effort(des_m)$estimates

  cbind(month = month_labels[i], est)
})
#> Warning in svydesign.default(ids = psu_formula, strata = strata_formula, : No
#> weights or probabilities supplied, assuming equal probability
#> Warning in svydesign.default(ids = psu_formula, strata = strata_formula, : No
#> weights or probabilities supplied, assuming equal probability
#> Warning in svydesign.default(ids = psu_formula, strata = strata_formula, : No
#> weights or probabilities supplied, assuming equal probability
#> Warning in svydesign.default(ids = psu_formula, strata = strata_formula, : No
#> weights or probabilities supplied, assuming equal probability
#> Warning in svydesign.default(ids = psu_formula, strata = strata_formula, : No
#> weights or probabilities supplied, assuming equal probability

do.call(rbind, monthly_effort)
#>       month estimate       se se_between se_within ci_lower ci_upper  n
#> 1       May      257 45.19735   45.19735         0 150.1253 363.8747  9
#> 2      June      461 73.10814   73.10814         0 300.0901 621.9099 13
#> 3      July      338 57.00058   57.00058         0 206.5564 469.4436 10
#> 4    August      491 55.13036   55.13036         0 369.6589 612.3411 13
#> 5 September      457 39.73244   39.73244         0 369.5495 544.4505 13

Season Total from Monthly Estimates

Because monthly estimates are independent (non-overlapping time windows), the season total is the sum of monthly estimates and the season variance is the sum of monthly variances.

monthly_df <- do.call(rbind, monthly_effort)

season_from_months <- data.frame(
  stratum  = "Season total",
  estimate = sum(monthly_df$estimate),
  se       = sqrt(sum(monthly_df$se^2))
)

season_from_months
#>        stratum estimate       se
#> 1 Season total     2004 123.5099

This should be close to the single-design season total above (small differences are due to independent stratification within each monthly design).

Monthly Catch Totals

Total catch estimation follows the same monthly pattern: estimate the catch rate from interviews, then multiply by monthly effort. First, generate synthetic interview data:

set.seed(2024)

# Three interviews per sampled day (ensures ≥10 complete trips per month)
n_per_day <- 3L
n_int <- nrow(sampled_calendar) * n_per_day
catch_total <- rpois(n_int, lambda = 1.8)
interviews <- data.frame(
  date         = rep(sampled_calendar$date, each = n_per_day),
  day_type     = rep(sampled_calendar$day_type, each = n_per_day),
  trip_status  = "complete",
  hours_fished = round(rnorm(n_int, mean = 3.5, sd = 1.2), 1),
  catch_total  = catch_total,
  catch_kept   = pmin(rpois(n_int, lambda = 0.6), catch_total)
)
interviews$hours_fished <- pmax(interviews$hours_fished, 0.5)

Then loop over months exactly as for effort:

monthly_catch <- lapply(seq_along(months), function(i) {
  m <- months[i]
  cal_m <- sampled_calendar[format(sampled_calendar$date, "%m") == sprintf("%02d", m), ]
  cnt_m <- sampled_counts[format(sampled_counts$date, "%m") == sprintf("%02d", m), ]
  int_m <- interviews[format(interviews$date, "%m") == sprintf("%02d", m), ]

  if (nrow(cal_m) == 0) {
    return(NULL)
  }

  des_m <- creel_design(cal_m, date = date, strata = day_type)
  des_m <- add_counts(des_m, cnt_m)
  des_m <- add_interviews(des_m, int_m,
    trip_status = trip_status,
    catch = catch_total,
    effort = hours_fished,
    harvest = catch_kept
  )

  effort_est <- estimate_effort(des_m)
  catch_est <- estimate_total_catch(des_m)

  cbind(month = month_labels[i], catch_est$estimates)
})
#> Warning in svydesign.default(ids = psu_formula, strata = strata_formula, : No
#> weights or probabilities supplied, assuming equal probability
#> Warning: 5 interviews have zero catch.
#>  Zero catch may be valid (skunked) or indicate missing data.
#> Warning in svydesign.default(ids = psu_formula, strata = strata_formula, : No
#> weights or probabilities supplied, assuming equal probability
#> Warning: 7 interviews have zero catch.
#>  Zero catch may be valid (skunked) or indicate missing data.
#> Warning in svydesign.default(ids = psu_formula, strata = strata_formula, : No
#> weights or probabilities supplied, assuming equal probability
#> Warning: 4 interviews have zero catch.
#>  Zero catch may be valid (skunked) or indicate missing data.
#> Warning in svydesign.default(ids = psu_formula, strata = strata_formula, : No
#> weights or probabilities supplied, assuming equal probability
#> Warning: 9 interviews have zero catch.
#>  Zero catch may be valid (skunked) or indicate missing data.
#> Warning in svydesign.default(ids = psu_formula, strata = strata_formula, : No
#> weights or probabilities supplied, assuming equal probability
#> Warning: 7 interviews have zero catch.
#>  Zero catch may be valid (skunked) or indicate missing data.

do.call(rbind, monthly_catch)
#>       month estimate       se  ci_lower ci_upper  n
#> 1       May 135.4235 32.68955  71.35318 199.4939 27
#> 2      June 209.1243 44.19099 122.51154 295.7370 39
#> 3      July 143.0169 32.81832  78.69413 207.3396 30
#> 4    August 236.6189 41.85109 154.59227 318.6455 39
#> 5 September 217.9545 38.78860 141.93029 293.9788 39

Season-Total Catch

Sum the monthly totals:

catch_df <- do.call(rbind, monthly_catch)
season_catch <- data.frame(
  stratum  = "Season total",
  estimate = sum(catch_df$estimate),
  se       = sqrt(sum(catch_df$se^2))
)
season_catch
#>        stratum estimate       se
#> 1 Season total 942.1381 85.75874

Assembling a Summary Report

season_summary() assembles multiple creel_estimates objects into a single wide tibble for reporting. For a monthly summary, build a list with named entries and pass it to season_summary().

# Collect effort and catch estimates for each month into named lists
effort_list <- list()
catch_list <- list()

for (i in seq_along(months)) {
  m <- months[i]
  label <- month_labels[i]

  cal_m <- sampled_calendar[format(sampled_calendar$date, "%m") == sprintf("%02d", m), ]
  cnt_m <- sampled_counts[format(sampled_counts$date, "%m") == sprintf("%02d", m), ]
  int_m <- interviews[format(interviews$date, "%m") == sprintf("%02d", m), ]

  if (nrow(cal_m) == 0) next

  des_m <- creel_design(cal_m, date = date, strata = day_type)
  des_m <- add_counts(des_m, cnt_m)
  des_m <- add_interviews(des_m, int_m,
    trip_status = trip_status,
    catch = catch_total,
    effort = hours_fished,
    harvest = catch_kept
  )

  effort_list[[label]] <- estimate_effort(des_m)
  catch_list[[label]] <- estimate_total_catch(des_m)
}
#> Warning in svydesign.default(ids = psu_formula, strata = strata_formula, : No
#> weights or probabilities supplied, assuming equal probability
#> Warning: 5 interviews have zero catch.
#>  Zero catch may be valid (skunked) or indicate missing data.
#> Warning in svydesign.default(ids = psu_formula, strata = strata_formula, : No
#> weights or probabilities supplied, assuming equal probability
#> Warning: 7 interviews have zero catch.
#>  Zero catch may be valid (skunked) or indicate missing data.
#> Warning in svydesign.default(ids = psu_formula, strata = strata_formula, : No
#> weights or probabilities supplied, assuming equal probability
#> Warning: 4 interviews have zero catch.
#>  Zero catch may be valid (skunked) or indicate missing data.
#> Warning in svydesign.default(ids = psu_formula, strata = strata_formula, : No
#> weights or probabilities supplied, assuming equal probability
#> Warning: 9 interviews have zero catch.
#>  Zero catch may be valid (skunked) or indicate missing data.
#> Warning in svydesign.default(ids = psu_formula, strata = strata_formula, : No
#> weights or probabilities supplied, assuming equal probability
#> Warning: 7 interviews have zero catch.
#>  Zero catch may be valid (skunked) or indicate missing data.

# Assemble effort report
effort_summary <- season_summary(effort_list)
effort_summary$table
#> # A tibble: 1 × 35
#>   May_estimate May_se May_se_between May_se_within May_ci_lower May_ci_upper
#>          <dbl>  <dbl>          <dbl>         <dbl>        <dbl>        <dbl>
#> 1          257   45.2           45.2             0         150.         364.
#> # ℹ 29 more variables: May_n <int>, June_estimate <dbl>, June_se <dbl>,
#> #   June_se_between <dbl>, June_se_within <dbl>, June_ci_lower <dbl>,
#> #   June_ci_upper <dbl>, June_n <int>, July_estimate <dbl>, July_se <dbl>,
#> #   July_se_between <dbl>, July_se_within <dbl>, July_ci_lower <dbl>,
#> #   July_ci_upper <dbl>, July_n <int>, August_estimate <dbl>, August_se <dbl>,
#> #   August_se_between <dbl>, August_se_within <dbl>, August_ci_lower <dbl>,
#> #   August_ci_upper <dbl>, August_n <int>, September_estimate <dbl>, …
catch_summary <- season_summary(catch_list)
catch_summary$table
#> # A tibble: 1 × 25
#>   May_estimate May_se May_ci_lower May_ci_upper May_n June_estimate June_se
#>          <dbl>  <dbl>        <dbl>        <dbl> <int>         <dbl>   <dbl>
#> 1         135.   32.7         71.4         199.    27          209.    44.2
#> # ℹ 18 more variables: June_ci_lower <dbl>, June_ci_upper <dbl>, June_n <int>,
#> #   July_estimate <dbl>, July_se <dbl>, July_ci_lower <dbl>,
#> #   July_ci_upper <dbl>, July_n <int>, August_estimate <dbl>, August_se <dbl>,
#> #   August_ci_lower <dbl>, August_ci_upper <dbl>, August_n <int>,
#> #   September_estimate <dbl>, September_se <dbl>, September_ci_lower <dbl>,
#> #   September_ci_upper <dbl>, September_n <int>

Annual Totals Across Multiple Seasons

When survey data span multiple calendar years, apply the same pattern at the year level: build one design per year, run estimators, then sum totals and combine variances.

# Conceptual pattern — replace with actual data per year
year_effort <- list()

for (yr in c(2022, 2023, 2024)) {
  cal_yr <- subset(full_calendar, format(date, "%Y") == yr)
  cnt_yr <- subset(full_counts, format(date, "%Y") == yr)

  des_yr <- creel_design(cal_yr, date = date, strata = day_type)
  des_yr <- add_counts(des_yr, cnt_yr)

  year_effort[[as.character(yr)]] <- estimate_effort(des_yr)
}

# Multi-year report table
season_summary(year_effort)$table

Exporting Results

write_schedule() accepts any data frame, so you can export the assembled summary table directly:

write_schedule(effort_summary$table, "effort_by_month_2024.csv")
write_schedule(effort_summary$table, "effort_by_month_2024.xlsx")

Key Principles

Goal Approach
Season total Single design covering full season; call estimate_effort() once
Monthly totals Separate monthly designs; loop over months
Season total from months Sum monthly estimates; sum monthly variances
Annual comparison Separate annual designs; use season_summary()
Report table season_summary(named_list_of_estimates)$table

References

  • Cochran, W. G. (1977). Sampling Techniques, 3rd ed. Wiley, New York.

  • Pollock, K. H., Jones, C. M., and Brown, T. L. (1994). Angler Survey Methods and Their Applications in Fisheries Management. American Fisheries Society, Bethesda, MD.

  • Su, Y.-S., and Liu, P. (2025). Flexible creel survey estimators. Canadian Journal of Fisheries and Aquatic Sciences, 82, 1–27.