23  Biological Data from Interviews: Length Distributions and Size Compliance

Author

Christopher Chizinski

Keywords

creel survey, fisheries, design-based inference, R, tidycreel, Quarto

Section 20.1 through Section 22.2 estimated how many fish were caught, kept, and released. Those are count quantities — numbers that answer “how many?” But many management decisions require a different kind of question: “what size?” A walleye harvest total of 4,000 fish means something very different depending on whether those fish averaged 300 mm or 500 mm. A size-limit regulation can only be evaluated if the agency knows how many harvested fish were actually legal-sized. A population assessment comparing creel data to electrofishing samples requires both surveys to speak the same biological language.

Creel surveys have always produced biological data alongside catch and effort data. Interview clerks routinely measure harvested fish, record lengths of fish returned to the water, and sometimes collect scale or fin ray samples for aging. That information lives in a separate table — one row per fish measured, linked back to the interview by a common identifier — and it requires a distinct estimation workflow. This chapter covers that workflow from data preparation through design-weighted length-frequency estimation to regulatory compliance analysis.

23.1 Why biological data require their own estimator

The intuition behind catch-rate estimation is straightforward: a ratio of means applied across a weighted interview sample. Length estimation has the same logical foundation but a subtle complication. Fish are subsampled within interviews. Not every harvested fish is measured — a party of six anglers returning with a mixed bag of 18 white bass and 4 walleye will often have lengths recorded for only a portion of each species. The clerk may measure all walleye but only a random five of the white bass, or measure no fish at all if the party is rushed.

This two-stage structure — sampling days within a survey season, then sampling fish within interviews — means that naive pooling of all measured lengths is not a valid population-level estimate. The sample of fish you measure is not a simple random sample of all fish harvested at Harlan Reservoir during the season; it is a convenience subsample embedded in a probability sample of interviews. To recover a population-weighted distribution, you must propagate interview-level uncertainty through the survey design, exactly as you do for effort and catch totals (Pollock et al. 1994).

tidycreel provides two functions for this workflow. summarize_length_freq() produces unweighted sample frequencies — the raw fraction of measured fish in each size bin, useful for exploratory work and for checking data structure. est_length_distribution() applies the interview survey weights from the creel_design object to produce design-based estimates of length-frequency with standard errors and confidence intervals. Both require the same data preparation step: attaching a length table to the design with add_lengths().

23.2 The length data structure

Length data at Harlan Reservoir are stored in harlan_lengths, a table with one row per measured fish (for individual measurements) or per occupied bin (for binned release records).

Code
harlan_lengths |>
  group_by(species, length_type) |>
  summarise(
    n_records = n(),
    n_fish    = sum(ifelse(length_type == "release", count, 1), na.rm = TRUE),
    .groups   = "drop"
  )
# A tibble: 4 × 4
  species         length_type n_records n_fish
  <chr>           <chr>           <int>  <dbl>
1 Channel Catfish harvest            20     20
2 Walleye         harvest           108    108
3 Walleye         release            82    498
4 White Bass      harvest          1524   1524

Three species with length records: White Bass (the most abundant harvest at Harlan), Walleye (a regulated species with a 381-mm minimum size limit), and Channel Catfish. Walleye release records use a binned format — the clerk recorded how many released fish fell into each 50-mm bin (e.g., "300-350", "350-400") rather than measuring each individual. This is common practice for large numbers of released fish: counting by length class at streamside is faster than individual measurement and retains the information needed for compliance and recruitment analysis. Individual harvest lengths are stored as a numeric value in the length column, with count = NA; binned release records carry the bin label as a character string and a non-missing fish count in count.

Code
# Individual harvest records: length is numeric, count is NA
harlan_lengths |> filter(length_type == "harvest") |> slice_head(n = 5)
  interview_id    species length length_type count
1           97 White Bass    325     harvest    NA
2           97 White Bass    239     harvest    NA
3           97 White Bass    273     harvest    NA
4           97 White Bass    299     harvest    NA
5           97 White Bass    273     harvest    NA
Code
# Binned release records: length is a bin label, count is fish per bin
harlan_lengths |> filter(length_type == "release") |> slice_head(n = 8)
  interview_id species  length length_type count
1          135 Walleye 250-300     release     2
2          135 Walleye 300-350     release     2
3          135 Walleye 350-400     release     1
4          389 Walleye 250-300     release     1
5          389 Walleye 300-350     release     2
6          264 Walleye 250-300     release     7
7          264 Walleye 300-350     release    26
8          264 Walleye 350-400     release    15

The column names and mixed types in length (numeric for harvest, character for release bins) are handled internally by add_lengths() through the release_format argument. Setting release_format = "binned" tells the function that release rows carry count totals per bin label rather than individual measurements.

23.3 Building the design with length data

The length table attaches to the creel design after add_interviews(). As in Section 21.3, the interview table must carry species-level catch summaries as separate columns before being passed to add_interviews().

Code
catch_summary <- harlan_catch |>
  group_by(interview_id, catch_type) |>
  summarise(n_fish = sum(n_fish), .groups = "drop") |>
  pivot_wider(
    names_from   = catch_type,
    values_from  = n_fish,
    values_fill  = 0L
  ) |>
  mutate(total_catch = harvested + released)

harlan_int_bio <- harlan_interviews |>
  left_join(catch_summary, by = "interview_id")
Code
design_bio <- creel_design(harlan_schedule, date = "date", strata = "day_type") |>
  add_interviews(
    interviews     = harlan_int_bio,
    catch          = "total_catch",
    harvest        = "harvested",
    effort         = "hours_fished",
    trip_status    = "trip_status",
    n_anglers      = "n_anglers",
    angler_type    = "angler_type",
    interview_type = "access"
  ) |>
  add_lengths(
    data           = harlan_lengths,
    length_uid     = "interview_id",
    interview_uid  = "interview_id",
    species        = "species",
    length         = "length",
    length_type    = "length_type",
    count          = "count",
    release_format = "binned"
  )
Warning: 21 interviews have zero catch.
ℹ Zero catch may be valid (skunked) or indicate missing data.
Code
design_bio

── Creel Survey Design ─────────────────────────────────────────────────────────
Type: "instantaneous"
Date column: date
Strata: day_type
Calendar: 211 days (2022-04-02 to 2022-10-29)
day_type: 2 levels
Counts: "none"
Interviews: 600 observations
Type: "access"
Catch: total_catch
Effort: hours_fished
Harvest: harvested
Trip status: 390 complete, 210 incomplete
Angler type: angler_type
Party size: n_anglers
Survey: <survey.design2> (constructed)
Length Data: 1734 rows, 3 species, length: 153–622 mm
harvest: 1652 individual
release: 82 binned
Sections: "none"

Printing design_bio confirms that the $lengths slot is now populated. The design object carries the length column mapping internally — species, length, length_type, and count are registered at attachment time so that summarize_length_freq() and est_length_distribution() can locate the right columns without requiring the analyst to re-specify them.

add_lengths() validates that every interview ID in harlan_lengths exists in design_bio$interviews — interviews with no length records are valid (not every angler’s catch is measured), but lengths cannot appear for interviews that are absent from the design. The function registers the column mapping internally so subsequent calls to summarize_length_freq() and est_length_distribution() know which column holds lengths and which identifies the interview link.

23.4 Raw length-frequency distributions

The first look at biological data should always be the unweighted sample frequencies. summarize_length_freq() returns the raw count of measured fish per bin, the percentage within each species, and a cumulative percentage — the same numbers you would compute from a flat spreadsheet. This is fast and useful for detecting data entry errors (lengths outside plausible biological range), checking bin coverage, and building a sense of the data before applying the survey weights.

Code
lf_raw <- summarize_length_freq(design_bio, type = "harvest", by = species, bin_width = 25)
lf_raw
           species length_bin   N percent cumulative_percent
1  Channel Catfish  [250,275)   1     5.0                5.0
2  Channel Catfish  [275,300)   1     5.0               10.0
3  Channel Catfish  [325,350)   2    10.0               20.0
4  Channel Catfish  [400,425)   2    10.0               30.0
5  Channel Catfish  [425,450)   2    10.0               40.0
6  Channel Catfish  [450,475)   5    25.0               65.0
7  Channel Catfish  [475,500)   3    15.0               80.0
8  Channel Catfish  [500,525)   1     5.0               85.0
9  Channel Catfish  [550,575)   2    10.0               95.0
10 Channel Catfish  [600,625)   1     5.0              100.0
11         Walleye  [275,300)   1     0.9                0.9
12         Walleye  [300,325)   4     3.7                4.6
13         Walleye  [325,350)   5     4.6                9.2
14         Walleye  [350,375)  17    15.7               24.9
15         Walleye  [375,400)  16    14.8               39.7
16         Walleye  [400,425)  19    17.6               57.3
17         Walleye  [425,450)  10     9.3               66.6
18         Walleye  [450,475)  19    17.6               84.2
19         Walleye  [475,500)  10     9.3               93.5
20         Walleye  [500,525)   4     3.7               97.2
21         Walleye  [525,550)   3     2.8              100.0
22      White Bass  [150,175)   4     0.3                0.3
23      White Bass  [175,200)  33     2.2                2.5
24      White Bass  [200,225) 128     8.4               10.9
25      White Bass  [225,250) 278    18.2               29.1
26      White Bass  [250,275) 382    25.1               54.2
27      White Bass  [275,300) 358    23.5               77.7
28      White Bass  [300,325) 235    15.4               93.1
29      White Bass  [325,350)  91     6.0               99.1
30      White Bass  [350,375)  13     0.9              100.0
31      White Bass  [375,400)   2     0.1              100.1

Figure 23.1 shows these distributions side by side. The three species have strikingly different shapes: White Bass form a compact, near-symmetric distribution around 265 mm; Walleye are spread broadly from about 275 mm to 550 mm with a mode near 425 mm and a visible left tail below the 381-mm size limit; Channel Catfish span the widest range, 250–625 mm, reflecting the wide age and size structure typical of catfish populations.

Code
size_limits <- tibble(
  species   = "Walleye",
  limit_mm  = 381
)

lf_raw |>
  mutate(bin_mid = as.numeric(sub("\\[(\\d+),.*", "\\1", length_bin)) + 12.5) |>
  ggplot(aes(x = bin_mid, y = N)) +
  geom_col(fill = "#2171b5", colour = "white", linewidth = 0.3) +
  geom_vline(
    data    = size_limits,
    aes(xintercept = limit_mm),
    linetype = "dashed",
    colour   = "#cb181d",
    linewidth = 0.7
  ) +
  facet_wrap(~species, scales = "free") +
  labs(
    x = "Total length (mm)",
    y = "Fish measured (n)"
  ) +
  theme_creel()
Faceted histogram showing White Bass concentrated 225–325mm, Walleye broadly distributed 300–550mm with dashed size-limit line at 381mm, and Channel Catfish 250–625mm.
Figure 23.1: Sample length-frequency distributions for three species at Harlan Reservoir. Bars show raw counts of individually measured harvest fish in 25-mm bins. Vertical dashed line on the Walleye panel marks the Nebraska 381-mm minimum size limit. White Bass have the tightest distribution; Channel Catfish the widest. Distributions are unweighted sample frequencies, not population-weighted estimates.

The raw counts are informative but not directly interpretable as population estimates. An interview from a busy summer weekend with 40 measured walleye gets the same weight as a quiet Tuesday interview with 3 measured fish, even though the weekday interview represents a stratum sampled at a different rate. The weighted estimator corrects for this.

23.5 Design-weighted length distributions

est_length_distribution() produces a pressure-weighted length-frequency estimate: each interview’s length contribution is scaled by the survey weights in the creel_design object, so the resulting distribution reflects the frequency at which different size fish were harvested across the full survey season, not just the frequency with which they appeared in the sampled interviews.

Code
lf_weighted <- est_length_distribution(
  design_bio,
  type      = "harvest",
  by        = species,
  bin_width = 25
)

lf_weighted |> filter(species == "Walleye")
   species length_bin bin_lower bin_upper estimate       se    ci_lower
1  Walleye  [275,300)       275       300        1 1.000000 -0.95996398
2  Walleye  [300,325)       300       325        4 1.996924  0.08610115
3  Walleye  [325,350)       325       350        5 2.226885  0.63538578
4  Walleye  [350,375)       350       375       17 4.522258  8.13653670
5  Walleye  [375,400)       375       400       16 4.639018  6.90769099
6  Walleye  [400,425)       400       425       19 4.943353  9.31120669
7  Walleye  [425,450)       425       450       10 3.437975  3.26169281
8  Walleye  [450,475)       450       475       19 4.739393  9.71096000
9  Walleye  [475,500)       475       500       10 3.139966  3.84577895
10 Walleye  [500,525)       500       525        4 1.993843  0.09213956
11 Walleye  [525,550)       525       550        3 1.730867 -0.39243759
    ci_upper percent cumulative_percent   n
1   2.959964     0.9                0.9 600
2   7.913899     3.7                4.6 600
3   9.364614     4.6                9.2 600
4  25.863463    15.7               24.9 600
5  25.092309    14.8               39.7 600
6  28.688793    17.6               57.3 600
7  16.738307     9.3               66.6 600
8  28.289040    17.6               84.2 600
9  16.154221     9.3               93.5 600
10  7.907860     3.7               97.2 600
11  6.392438     2.8              100.0 600

Each row now carries a se, ci_lower, and ci_upper alongside the point estimate. The standard error propagates interview-level variation through the stratified design — bins that appeared inconsistently across sampled interviews have wider intervals, reflecting genuine uncertainty about how common that size class was in the harvested population.

Figure 23.2 shows the weighted Walleye distribution. Several features stand out that are not obvious from the raw count histogram: the confidence intervals are widest in the central bins (400–450 mm) where interview-to-interview variation in the count of fish of this size is largest, and narrowest at the tails where either fish are consistently absent or consistently few. The dashed line marks the Nebraska minimum size limit of 381 mm (15 in); the large majority of the estimated harvest falls above this threshold.

Code
lw_walleye <- lf_weighted |>
  filter(species == "Walleye") |>
  mutate(
    bin_mid = bin_lower + 12.5,
    legal   = bin_lower >= 381
  )

ggplot(lw_walleye, aes(x = bin_mid, y = estimate, fill = legal)) +
  geom_col(colour = "white", linewidth = 0.3) +
  geom_errorbar(
    aes(ymin = pmax(0, ci_lower), ymax = ci_upper),
    width     = 8,
    linewidth = 0.5,
    colour    = "grey40"
  ) +
  geom_vline(
    xintercept = 381,
    linetype   = "dashed",
    colour     = "#cb181d",
    linewidth  = 0.7
  ) +
  scale_fill_manual(
    values = c(`FALSE` = "#9ecae1", `TRUE` = "#2171b5"),
    labels = c(`FALSE` = "Below limit (<381 mm)", `TRUE` = "At or above limit (≥381 mm)"),
    guide  = guide_legend(title = NULL)
  ) +
  labs(
    x = "Total length (mm)",
    y = "Estimated fish (design-weighted)"
  ) +
  theme_creel() +
  theme(legend.position = "bottom")
Bar chart of Walleye harvest length distribution with confidence intervals, showing most fish 375–500mm, with bins below 381mm shown in lighter color and a dashed vertical line at 381mm.
Figure 23.2: Design-weighted harvest length distribution for Walleye at Harlan Reservoir. Point estimates (bars) reflect interview-survey-weighted proportions of harvested fish by 25-mm length class. Error bars are 95% confidence intervals. Dashed red line marks the 381-mm (15-in) Nebraska minimum size limit. Bins entirely below the size limit appear in a lighter shade to highlight the sublegal fraction.

The confidence intervals in Figure 23.2 are wide — a natural consequence of the modest number of interviews with length records and the across-interview variability in fish size. This uncertainty should accompany any regulatory reporting that relies on these estimates.

The creel_length_distribution object has an autoplot() method that renders a quick faceted bar chart with error bars across all grouping levels — useful for exploring all three species simultaneously before deciding which to examine more closely.

Code
ggplot2::autoplot(lf_weighted, theme = "creel")
Three-panel bar chart from autoplot showing harvest length distributions by species with confidence interval error bars.
Figure 23.3: Design-weighted harvest length distributions for all three species at Harlan Reservoir, produced by ggplot2::autoplot(). Error bars show 95% confidence intervals. The faceted layout reveals that White Bass have the narrowest distribution, Channel Catfish the widest, and Walleye straddle the 381-mm size limit. Use this plot for rapid exploration; Figure 23.2 provides the annotated version for reporting.

23.6 Interview coverage and subsampling intensity

Before relying on a length distribution estimate, it is worth asking how many interviews contributed length records and whether that subsample is spread across the survey schedule. A length distribution estimated from twelve interviews — even if those interviews covered many fish — carries more uncertainty than one supported by sixty interviews spread across weekdays and weekends throughout the season.

Code
coverage <- harlan_interviews |>
  left_join(
    harlan_lengths |>
      group_by(interview_id) |>
      summarise(has_lengths = TRUE, .groups = "drop"),
    by = "interview_id"
  ) |>
  replace_na(list(has_lengths = FALSE)) |>
  group_by(day_type) |>
  summarise(
    n_interviews     = n(),
    n_with_lengths   = sum(has_lengths),
    pct_with_lengths = round(100 * mean(has_lengths), 1),
    .groups          = "drop"
  )
coverage
# A tibble: 2 × 4
  day_type n_interviews n_with_lengths pct_with_lengths
  <chr>           <int>          <int>            <dbl>
1 weekday           489            152             31.1
2 weekend           111             26             23.4

Length records were collected from approximately 27% of interviews in each stratum. Coverage that is roughly equal across strata — as it is here — means that subsampling is not systematically biasing the length distribution toward one day type. If weekday interviews had dramatically lower coverage than weekend interviews, the estimated distribution would lean toward the weekend fish composition, which could differ in size structure if fishing pressure and selectivity differ by day type.

The subsample rate also has a direct effect on precision. More measured fish within an interview reduces within-interview variability in the length column; more interviews with any length records reduces between-interview variability. For a fixed field budget, measuring a few fish across more interviews is more efficient than measuring many fish in fewer interviews, because between-interview variance dominates total variance in stratified survey estimators — a pattern visible in the variance decomposition for effort in Section 24.1.

est_length_distribution() offers three variance estimation methods (set via the variance argument): "taylor" (default, delta-method), "bootstrap", and "jackknife". For small biological subsamples — fewer than twenty to thirty interviews with length records — the bootstrap or jackknife approaches often give more conservative and reliable uncertainty bounds than the delta-method approximation.

23.7 Released fish: binned length distributions

The type argument to est_length_distribution() controls which fish are included. Setting type = "release" pulls the binned Walleye release records and produces a distribution of the returned fish. Because released fish were binned in 50-mm intervals rather than measured individually, a wider bin_width is appropriate.

Code
lf_release <- est_length_distribution(
  design_bio,
  type      = "release",
  by        = species,
  bin_width = 50
)
lf_release
  species length_bin bin_lower bin_upper estimate        se   ci_lower
1 Walleye  [250,300)       250       300       97 25.529682  46.962742
2 Walleye  [300,350)       300       350      250 66.313448 120.028030
3 Walleye  [350,400)       350       400      130 37.119155  57.247793
4 Walleye  [400,450)       400       450       20  6.116023   8.012815
5 Walleye  [450,500)       450       500        1  1.000000  -0.959964
    ci_upper percent cumulative_percent   n
1 147.037258    19.5               19.5 600
2 379.971970    50.2               69.7 600
3 202.752207    26.1               95.8 600
4  31.987185     4.0               99.8 600
5   2.959964     0.2              100.0 600

The release distribution tells a complementary story to the harvest distribution. Whereas harvested Walleye were concentrated above 375 mm, released fish are concentrated below 381 mm — almost 96% of estimated released Walleye fell below the size limit (Figure 23.4). This is the expected pattern under a minimum size regulation: anglers keep legal fish and return sublegal fish.

Code
lf_release |>
  filter(species == "Walleye") |>
  mutate(
    bin_mid = bin_lower + 25,
    legal   = bin_lower >= 381
  ) |>
  ggplot(aes(x = bin_mid, y = estimate, fill = legal)) +
  geom_col(colour = "white", linewidth = 0.3) +
  geom_errorbar(
    aes(ymin = pmax(0, ci_lower), ymax = ci_upper),
    width     = 18,
    linewidth = 0.5,
    colour    = "grey40"
  ) +
  geom_vline(
    xintercept = 381,
    linetype   = "dashed",
    colour     = "#cb181d",
    linewidth  = 0.7
  ) +
  scale_x_continuous(breaks = seq(250, 550, 50)) +
  scale_fill_manual(
    values = c(`FALSE` = "#9ecae1", `TRUE` = "#2171b5"),
    labels = c(`FALSE` = "Sublegal (<381 mm)", `TRUE` = "Legal (≥381 mm)"),
    guide  = guide_legend(title = NULL)
  ) +
  labs(
    x = "Total length (mm, bin midpoint)",
    y = "Estimated fish (design-weighted)"
  ) +
  theme_creel() +
  theme(legend.position = "bottom")
Bar chart of Walleye release length distribution showing nearly all fish in 250–400mm range, with bins above 381mm very small.
Figure 23.4: Design-weighted length distribution of released Walleye at Harlan Reservoir, binned at 50-mm intervals. The 381-mm size limit (dashed red line) cleanly separates the release distribution from the harvest distribution in Figure 23.2: nearly all released fish were sublegal, consistent with angler compliance with the minimum size regulation.

The complementarity of harvest and release distributions is itself a data quality diagnostic. If a substantial fraction of estimated released fish exceeded the size limit, or if harvested fish frequently appeared below it, that would raise questions about either reporting accuracy or measurement error — both worth investigating before the data enter a regulatory assessment.

23.8 Weighted mean length from the estimated distribution

est_mean_length() computes the design-weighted mean length directly from a creel_length_distribution object, using the ratio estimator \bar{L} = \sum_h L_h \hat{N}_h / \hat{N} with delta-method standard error. Bin midpoints serve as representative lengths within each class.

Code
mean_length_est <- est_mean_length(lf_weighted)
mean_length_est
          species mean_length mean_length_se mean_length_ci_lower
1 Channel Catfish    447.5000      21.107987             406.1291
2         Walleye    417.8241       5.494321             407.0554
3      White Bass    270.9318       1.629656             267.7377
  mean_length_ci_upper
1             488.8709
2             428.5927
3             274.1258

For comparison, the unweighted sample mean from the raw length records:

Code
mean_length_raw <- harlan_lengths |>
  filter(length_type == "harvest") |>
  mutate(length_mm = as.numeric(length)) |>
  group_by(species) |>
  summarise(
    mean_length_raw_mm = round(mean(length_mm, na.rm = TRUE), 1),
    n                  = n(),
    .groups            = "drop"
  )
mean_length_raw
# A tibble: 3 × 3
  species         mean_length_raw_mm     n
  <chr>                        <dbl> <int>
1 Channel Catfish               451.    20
2 Walleye                       418.   108
3 White Bass                    270.  1524

When the two means agree closely, the interview subsampling was roughly self-weighting with respect to mean fish size — interviews that measured more fish did not systematically measure larger or smaller fish than interviews that measured fewer. A discrepancy between the two means signals a correlation between interview-level subsampling intensity and fish size, which can arise, for example, if clerks measured more fish during periods when anglers were catching larger individuals (peak season, favorable conditions). In that case, the design-weighted mean from est_mean_length() is the more defensible estimate, because it propagates survey weights through the ratio estimator rather than treating all measured fish as equally representative. For Harlan Walleye, the two means should be close, consistent with the balanced subsampling coverage shown in the previous section.

23.9 Size-limit compliance analysis

est_compliance() estimates the design-weighted compliance proportion directly from a creel_length_distribution object. Bins whose lower bound is at or above the minimum legal length are classified as legal; bins that straddle the threshold are classified as illegal (a conservative rule that avoids overstating compliance when fish cluster near the limit). The estimator is the ratio of estimated legal fish to estimated total fish, with delta-method standard error.

Code
compliance_est <- est_compliance(lf_weighted, min_length = 381)
compliance_est
          species min_length n_legal_est n_total_est compliance_prop
1 Channel Catfish        381          16          20       0.8000000
2         Walleye        381          65         108       0.6018519
3      White Bass        381           0        1524       0.0000000
  compliance_se compliance_ci_lower compliance_ci_upper
1    0.09374713           0.6162590            0.983741
2    0.05137196           0.5011647            0.702539
3    0.00000000           0.0000000            0.000000
Code
walleye_compliance <- compliance_est |> filter(species == "Walleye")
walleye_compliance
  species min_length n_legal_est n_total_est compliance_prop compliance_se
1 Walleye        381          65         108       0.6018519    0.05137196
  compliance_ci_lower compliance_ci_upper
1           0.5011647            0.702539

60.2% of estimated harvested Walleye at Harlan Reservoir met or exceeded the 381-mm size limit (95% CI: 50.1–70.3%). This is the design-weighted estimate: it accounts for the stratified survey structure rather than treating all measured fish as a simple random sample.

Figure 23.5 breaks compliance down by angler type using the raw length sample — a decomposition that can reveal whether boat and bank anglers differ in the size composition of their harvest. Access differences between angler types often translate into size-structure differences because boat anglers can reach deeper structures where larger fish concentrate.

Code
walleye_harvest_lengths <- harlan_lengths |>
  filter(species == "Walleye", length_type == "harvest") |>
  mutate(length_mm = as.numeric(length),
         legal     = length_mm >= 381)

compliance_by_angler <- harlan_int_bio |>
  select(interview_id, angler_type) |>
  inner_join(
    walleye_harvest_lengths |> select(interview_id, legal),
    by = "interview_id"
  ) |>
  group_by(angler_type) |>
  summarise(
    n_fish    = n(),
    pct_legal = round(100 * mean(legal), 1),
    .groups   = "drop"
  ) |>
  filter(!is.na(angler_type))

ggplot(compliance_by_angler,
       aes(x = pct_legal, y = reorder(angler_type, pct_legal))) +
  geom_col(fill = "#2171b5", width = 0.55) +
  geom_text(
    aes(label = paste0(pct_legal, "% (n = ", n_fish, ")")),
    hjust   = -0.1,
    size    = 3.5
  ) +
  geom_vline(xintercept = 100, linetype = "dotted", colour = "grey50") +
  scale_x_continuous(limits = c(0, 115), breaks = seq(0, 100, 25),
                     labels = function(x) paste0(x, "%")) +
  labs(
    x = "Fish ≥ 381 mm (%)",
    y = NULL
  ) +
  theme_creel()
Horizontal bar chart comparing Walleye size-limit compliance between boat and bank anglers, both exceeding 70%, with sample sizes labeled.
Figure 23.5: Walleye harvest size compliance by angler type at Harlan Reservoir. Bars show the percentage of individually measured harvested Walleye that met or exceeded the 381-mm (15-in) minimum size limit. Labels to the right of each bar show the percentage and the sample size of measured fish. Both angler types show high compliance, with boat anglers harvesting a modestly larger fraction of legal-size fish.

Boat anglers show marginally higher compliance than bank anglers, consistent with their ability to target deeper water where larger fish concentrate. The difference is not statistically tested here — a formal design-weighted contrast would require a separate est_length_distribution(design_bio, ..., by = angler_type) call followed by est_compliance() per angler type. The overall design-weighted estimate produced above is the more defensible number for regulatory reporting.

23.10 Estimating biomass from the length distribution

est_biomass() converts a design-weighted length-frequency distribution into a total biomass estimate using the allometric length-weight equation W = a \cdot L^b. It applies the relationship at each bin midpoint to approximate the distribution-weighted mean weight and propagates uncertainty via the delta method, yielding a biomass estimate with a confidence interval.

For Walleye, a commonly used length-weight relationship takes the form W = a \cdot L^b where W is weight in grams and L is total length in mm. Example parameters (a = 0.0000106, b = 3.093) are illustrative; programs should fit species- and water-body-specific coefficients from their own biological sampling data.

Code
# Length-weight parameters for Walleye (W in grams, L in mm)
a <- 0.0000106
b <- 3.093

lf_walleye_harvest <- lf_weighted |> filter(species == "Walleye")
biomass_walleye <- est_biomass(lf_walleye_harvest, a = a, b = b)
biomass_walleye
  species biomass_estimate biomass_se biomass_ci_lower biomass_ci_upper
1 Walleye         154689.9    17055.6         121261.5         188118.2

This calculation uses the bin midpoint as a representative length for all fish in that class — an approximation that is accurate when bins are narrow relative to the range of the distribution, which is the case with 25-mm bins. Unlike a manual bin-by-bin calculation, est_biomass() propagates uncertainty from the estimated fish counts through the length-weight transformation via the delta method, so the result includes a 95% confidence interval on total biomass.

23.11 tidycreel support

tidycreel supports the full biological estimation workflow through ten functions:

Function Purpose
add_lengths() Attach a length table to a creel_design; validates interview IDs and column types; handles individual and binned formats
add_ages() Attach an age table (scale, fin ray, or otolith) to a creel_design; mirrors add_lengths()
summarize_length_freq() Unweighted sample frequency by bin and species; for exploration and QA
est_length_distribution() Design-weighted length-frequency with SE and 95% CI; produces creel_length_distribution object
autoplot() Default plot method for creel_length_distribution objects; quick visualization without manual ggplot construction
est_mean_length() Design-weighted mean length from a creel_length_distribution object; ratio estimator with delta-method SE
est_compliance() Design-weighted size-limit compliance proportion; bins at or above min_length classified as legal
est_biomass() Total biomass from a creel_length_distribution object via W = aL^b; delta-method CI
est_age_distribution() Design-weighted age-frequency with SE and 95% CI from an add_ages() object; produces creel_age_distribution
est_mean_age() Design-weighted mean age from a creel_age_distribution object; ratio estimator with delta-method SE

The workflow for length data is: add_lengths()est_length_distribution()est_mean_length() / est_compliance() / est_biomass(). The workflow for age data mirrors it exactly: add_ages()est_age_distribution()est_mean_age(). The following example uses the tidycreel package’s built-in example_ages dataset; field programs that collect otolith or scale ages follow the same pipeline with their own age tables.

Code
data(example_calendar)
data(example_interviews)
data(example_ages)

design_age <- creel_design(example_calendar, date = date, strata = day_type) |>
  add_interviews(example_interviews,
    catch = catch_total, effort = hours_fished, harvest = catch_kept,
    trip_status = trip_status
  ) |>
  add_ages(example_ages,
    age_uid      = interview_id,
    interview_uid = interview_id,
    species      = species,
    age          = age,
    age_type     = age_type
  )
ℹ No `n_anglers` provided — assuming 1 angler per interview.
ℹ Pass `n_anglers = <column>` to use actual party sizes for angler-hour
  normalization.
ℹ Added 22 interviews: 17 complete (77%), 5 incomplete (23%)
Code
ad <- est_age_distribution(design_age, by = species, type = "harvest")
ad
  species age estimate       se   ci_lower ci_upper percent cumulative_percent
1    bass   2        1 1.000000 -0.9599640 2.959964    33.3               33.3
2    bass   3        2 1.322876 -0.5927886 4.592789    66.7              100.0
3 panfish   1        1 1.000000 -0.9599640 2.959964    50.0               50.0
4 panfish   2        1 1.000000 -0.9599640 2.959964    50.0              100.0
5 walleye   3        1 1.000000 -0.9599640 2.959964    14.3               14.3
6 walleye   4        2 1.414214 -0.7718076 4.771808    28.6               42.9
7 walleye   5        2 1.414214 -0.7718076 4.771808    28.6               71.5
8 walleye   6        2 2.000000 -1.9199280 5.919928    28.6              100.1
   n
1 22
2 22
3 22
4 22
5 22
6 22
7 22
8 22
Code
est_mean_age(ad)
  species mean_age mean_age_se mean_age_ci_lower mean_age_ci_upper
1    bass 2.666667   0.2664351         2.1444635          3.188870
2 panfish 1.500000   0.3535534         0.8070481          2.192952
3 walleye 4.714286   0.4680549         3.7969150          5.631656

The creel_age_distribution object returned by est_age_distribution() has the same structure as creel_length_distribution: one row per age class (with grouping columns when by is supplied), plus estimate, se, ci_lower, ci_upper, percent, and cumulative_percent. est_mean_age() returns the ratio-estimated mean age and its delta-method standard error.

One gap remains without automated support: exploitation rate by size class, which requires combining creel harvest estimates with population abundance from an independent survey (electrofishing or gill-netting). That calculation is not automated because the abundance denominator comes from outside tidycreel’s scope. The key principle for analysts who need it is the same as for the functions above: express the biological quantity as an interview-level observation, attach it to the design, and let the stratified estimator propagate uncertainty.

23.12 QA check: combined catch distribution

A useful final check before any biological results leave the analyst’s desk is to compare the combined catch distribution (type = "catch", which includes harvest and release together) against the harvest-only and release-only distributions. The combined total should equal the sum of the two components, and the size structure should fall between them — more large fish than the release distribution, fewer than the harvest distribution alone.

Code
summarize_length_freq(design_bio, type = "catch", by = species, bin_width = 25) |>
  filter(species == "Walleye") |>
  select(species, length_bin, N, percent, cumulative_percent)
   species length_bin   N percent cumulative_percent
1  Walleye  [275,300)  98    16.2               16.2
2  Walleye  [300,325)   4     0.7               16.9
3  Walleye  [325,350) 255    42.1               59.0
4  Walleye  [350,375)  17     2.8               61.8
5  Walleye  [375,400) 146    24.1               85.9
6  Walleye  [400,425)  19     3.1               89.0
7  Walleye  [425,450)  30     5.0               94.0
8  Walleye  [450,475)  19     3.1               97.1
9  Walleye  [475,500)  11     1.8               98.9
10 Walleye  [500,525)   4     0.7               99.6
11 Walleye  [525,550)   3     0.5              100.1

The combined Walleye distribution is distinctly bimodal: a left mode from sublegal released fish concentrated below 381 mm, and a right mode from legal harvested fish concentrated above 381 mm. Neither component alone shows this bimodality. This is the full size structure of Walleye contacted at Harlan Reservoir — the population the fishery is drawing on — and it confirms that the regulation is achieving its intended selectivity: anglers are retaining fish that have grown past the minimum length while returning the smaller cohort to continue growing.

If the combined distribution were unimodal and concentrated above the size limit, it would suggest either that sublegal fish were present but not being reported as released, or that the fish population lacks a strong sublegal cohort — both worth documenting. If it were concentrated entirely below the limit, it would indicate a recruitment failure or harvest of sublegal fish.

23.13 Takeaway

Biological data from creel interviews require a two-stage estimation approach: fish are subsampled within interviews, which are themselves sampled as part of a stratified survey. Treating measured fish as a simple random sample ignores the survey structure and produces length distributions that reflect sampler convenience rather than angler harvest. add_lengths() attaches the length table to the creel_design object so that est_length_distribution() can apply interview-level survey weights to recover a design-based estimate of the harvested size composition.

At Harlan Reservoir, the design-weighted Walleye harvest compliance estimate was 60.2% (95% CI: 50.1–70.3%). The complementary release distribution — concentrated below the size limit — confirms that anglers at Harlan were returning sublegal fish rather than harvesting them. Together, these two distributions provide the regulatory evidence base that effort and catch totals alone cannot supply.

Section 24.1 covers precision diagnostics for effort and catch-rate estimates; the same CV-based framework applies to length distribution estimates, where the coefficient of variation on key bins (the sublegal fraction, the modal bin) is the natural precision target for planning biological subsampling intensity in future seasons.

The biological estimation workflow adds one or two steps to the standard pipeline — add_lengths() for size data, add_ages() for age data, each placed after add_interviews() — and unlocks the full suite of biological summary functions. The complete sequence from linked tables to design-weighted compliance, mean length, biomass, age distribution, and mean age requires no additional packages beyond tidycreel and tidyverse.

Pollock, K. H., C. M. Jones, and T. L. Brown. 1994. Angler survey methods and their applications in fisheries management. American Fisheries Society, Bethesda, Maryland.