Skip to contents

Attaches count-side effort data to a creel_design object and constructs the internal survey design object eagerly. The preferred workflow is to standardize raw count-process data into sampled-day effort rows with prep_counts_daily_effort() (or another prep_counts_*() helper) before calling add_counts(). This keeps survey-specific count reconstruction logic out of the core estimator path.

Compatibility paths for raw-ish count inputs remain supported: add_counts() can still aggregate multiple within-day observations via count_time_col and can still compute progressive-count daily effort from circuit_time and period_length_col. Eager construction catches design errors at add_counts() time when users have context about what data they are adding.

period_length_col applies to instantaneous counts as well as progressive ones. An instantaneous count is a snapshot of the anglers present at one moment, so it becomes effort only once multiplied by the period it was randomised within; supply that column and the estimate is in angler-hours.

Usage

add_counts(
  design,
  counts,
  count_col = NULL,
  psu = NULL,
  count_time_col = NULL,
  count_type = "instantaneous",
  circuit_time = NULL,
  period_length_col = NULL,
  unit_cols = NULL,
  allow_invalid = FALSE
)

Arguments

design

A creel_design object (created with creel_design())

counts

Data frame containing count-side effort data. Preferred input: sampled-day effort rows that already have a Date column matching the design's date_col, all strata columns from the design's strata_cols, at least one numeric effort column, and a PSU column (specified via psu, defaults to date_col). Use prep_counts_daily_effort() to build this form from raw source data.

Compatibility input paths remain available for raw-ish count workflows with multiple within-day observations or progressive counts.

count_col

Tidy selector for the numeric column holding the angler counts. Defaults to NULL, in which case the column is inferred as the only numeric column that is not design metadata (date, strata, PSU, section, count-time, or period-length). If more than one numeric column qualifies, add_counts() aborts and lists the candidates rather than choosing by position — name the column here to resolve it. The resolved name is stored on the design and used by every downstream estimator.

psu

Character string naming the PSU (Primary Sampling Unit) column in the count data. Defaults to NULL, which uses the design's date_col as the PSU (day-as-PSU is the most common creel design). For other designs, specify the PSU column explicitly (e.g., "site_day" for day-site PSUs).

count_time_col

Tidy selector for a column that identifies distinct sub-PSU count observations (e.g., count_time_col = count_time where count_time contains "am" / "pm"). When supplied, multiple rows per PSU are aggregated to a single PSU-level mean (C-bar_d) and within-day variance components (SS_d, K_d) are stored in design$within_day_var. When NULL (default), a single row per PSU is expected; duplicate PSU rows emit a CNT-06 warning.

count_type

Character string specifying the count method. Must be "instantaneous" (default) or "progressive". When "progressive", circuit_time and period_length_col are required.

circuit_time

Numeric. Circuit duration \(\tau\) in hours — the time required to complete one roving count circuit of the water body. Required when count_type = "progressive" (CNT-05). Used to compute \(\kappa = T_d / \tau\) and \(\hat{E}_d = C \times \tau \times \kappa\). Ignored (with a warning) when count_type = "instantaneous".

period_length_col

Tidy selector for the column containing T_d — the length in hours of the period each count was randomised within. Required when count_type = "progressive" (CNT-05), optional but strongly recommended when count_type = "instantaneous". Values must be positive and finite. The column is dropped from design$counts after Ê_d is computed (it must not be passed to estimate_effort() as a count variable).

unit_cols

Optional character vector naming the columns that together identify one sampling unit. When omitted, the unit is inferred from the design: the PSU column plus any strata, section, and site columns.

Supply it when the counts table carries a dimension the design does not declare. The commonest case is effort_type, which prep_counts_daily_effort() emits: bank and boat counts on the same day are two units, not one day counted twice. Inference cannot see such a column, so it would treat those rows as repeats — warning about them without count_time_col, and averaging across them with it (GH #162).

You are not required to guess when this matters. If aggregation would collapse rows that differ in an undeclared column, add_counts() aborts and names the column rather than silently taking its first value.

For instantaneous counts, supplying this is what makes the estimate angler-hours. A count is a snapshot of how many anglers were present at one moment; effort is that count times the period it was randomised within, \(\hat{E}_d = \bar{C}_d \times T_d\) (Hoenig et al. 1993). Without it, estimate_effort() expands the counts to the season and returns them unmultiplied, and warns once per session that it has done so.

Not accepted on aerial designs, which carry their period length as h_open and scale the count by h_open / v when estimating. Supplying both would apply time twice, so this errors rather than choosing one.

T_d is a property of the survey protocol — the period set by regulation, access hours, or field practice. It is not astronomical daylight. day_length() is for simulation and planning; do not feed it here as a substitute for the period actually surveyed.

The multiplication happens per PSU, before aggregation. Converting after the fact computes \(\bar{C} \times \bar{T}\) where the target is the mean of \(C \times T\); the two differ by Cov(C, T), which is positive in practice because anglers fish more on long days, so the collapsed form biases low.

allow_invalid

Logical flag for validation behavior. If FALSE (default), validation failures abort with detailed error messages. If TRUE, validation failures generate warnings and attach counts anyway (use with caution).

Value

A new creel_design object (list) with components:

calendar

Original calendar data frame

date_col

Character name of date column

strata_cols

Character vector of strata column names

site_col

Character name of site column, or NULL

design_type

Character design type

counts

The count data frame (newly attached)

psu_col

Character name of PSU column

survey

Internal survey.design2 object (newly constructed)

validation

creel_validation object with Tier 1 results

Immutability

add_counts() follows functional programming patterns and returns a new creel_design object. The original design object is not modified. This prevents accidental data loss and makes the workflow explicit: design2 <- add_counts(design, counts) not add_counts(design, counts)

Validation

add_counts() performs Tier 1 validation:

  • Count data schema (Date column, numeric column) via validate_count_schema()

  • Design column presence (date_col, strata_cols, PSU in count data)

  • No NA values in design-critical columns (date, strata, PSU)

  • Survey construction (catches lonely PSU errors, stratification issues)

Party-size expansion carriers

derive_angler_count() attaches expansion_basis, expansion_se, expansion_group, and expansion_of to the counts table so the party-size sampling error can reach the effort standard error. The four are written together and must travel together: add_counts() aborts on a table carrying only some of them, since that state can only come from partial deletion and would otherwise drop the variance component while leaving an expansion_se visible in the data.

add_counts() also aborts when count_col is not the column named in expansion_of. The basis is d(count)/d(party_size), so a count transformed after derive_angler_count() no longer matches the basis carried beside it, and the variance component would come out understated by exactly the scale factor while still reading as propagated. Supply the untransformed count and period_length_col, which scales the count and the basis together.

Dropping all four — which an ordinary select() will do — is indistinguishable here from counts that never had them, and is silent.

PSU Specification

Per user decision (clarified 2026-02-08), PSU is specified only in add_counts(), not in creel_design() constructor. PSU is only meaningful when count data is present, making add_counts() the correct abstraction boundary. This design allows the same creel_design calendar to be used with different PSU structures.

Examples

# Preferred workflow: standardize sampled-day effort before attachment
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)

raw_counts <- data.frame(
  sample_date = as.Date(c("2024-06-01", "2024-06-02", "2024-06-03", "2024-06-04")),
  day_type = c("weekday", "weekday", "weekend", "weekend"),
  effort_kind = c("bank", "bank", "bank", "bank"),
  effort_value = c(15, 23, 45, 52)
)

counts_ready <- prep_counts_daily_effort(
  raw_counts,
  date = sample_date,
  strata = day_type,
  effort_type = effort_kind,
  daily_effort = effort_value
)

design_with_counts <- add_counts(design, counts_ready)
#> Warning: No weights or probabilities supplied, assuming equal probability
print(design_with_counts)
#> 
#> ── Creel Survey Design ─────────────────────────────────────────────────────────
#> Type: "instantaneous"
#> Date column: date
#> Strata: day_type
#> Calendar: 4 days (2024-06-01 to 2024-06-04)
#> day_type: 2 levels
#> Counts: 4 observations
#> PSU column: date
#> Count column: daily_effort
#> Party-size term: not carried
#> Count type: "instantaneous"
#> Survey: <survey.design2> (constructed)
#> Interviews: "none"
#> Sections: "none"

# Compatibility path: raw count rows with a custom PSU column
counts_with_site_psu <- 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"),
  site_day = paste0("site_", 1:4),
  count = c(15, 23, 45, 52)
)

design2 <- add_counts(design, counts_with_site_psu, psu = "site_day")
#> Warning: No weights or probabilities supplied, assuming equal probability

# Compatibility path: multiple counts per day (within-day variance via count_time_col)
# Two circuits per day: "am" and "pm"
calendar2 <- 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")
)
design3 <- creel_design(calendar2, date = date, strata = day_type)

multi_counts <- data.frame(
  date = as.Date(rep(c("2024-06-01", "2024-06-02", "2024-06-03", "2024-06-04"), each = 2)),
  day_type = rep(c("weekday", "weekday", "weekend", "weekend"), each = 2),
  count_time = rep(c("am", "pm"), 4),
  n_anglers = c(12, 18, 20, 26, 40, 50, 48, 56)
)
design_multi <- add_counts(design3, multi_counts,
  count_time_col = count_time # nolint: object_usage_linter
)
#> Warning: No weights or probabilities supplied, assuming equal probability

# Compatibility path: progressive count type (Ê_d = C x circuit_time x kappa)
calendar3 <- 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")
)
design4 <- creel_design(calendar3, date = date, strata = day_type)

prog_counts <- 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"),
  n_anglers = c(15L, 23L, 45L, 52L),
  shift_hours = rep(8, 4)
)
design_prog <- add_counts(
  design4, prog_counts,
  count_type = "progressive",
  circuit_time = 2,
  period_length_col = shift_hours # nolint: object_usage_linter
)
#> Warning: No weights or probabilities supplied, assuming equal probability