Why a Creel Survey Needs Linked Tables

Keeping counts, interviews, catch, and fish measurements connected

tidycreel
rstats
fisheries
Counts, interviews, catch, and fish measurements describe different units. A tidycreel 7.0.0 example shows how linked tables preserve those distinctions without losing the connections needed for analysis.
Author

Christopher Chizinski

Published

September 18, 2026

Watercolor illustration of a fisheries clerk measuring a fish at a lakeside field station, beside a panel of linked table icons representing counts, interviews, catch, and length measurements.

The previous post asked how an angler entered the interview sample. This post follows those observations back to the data: what does one row represent, and how does it connect to the rest of the survey?

A count records fishing activity at a particular place and time. An interview records information about a trip or party. Catch records describe species and what happened to the fish. Length and age records describe the fish sampled for biological information. These observations belong together in an analysis, but they do not all belong on the same row.

In brief

  • Keep each table at its own unit of observation.
  • Link catch and biological records to interviews with stable identifiers.
  • Preserve interviews with zero catch and distinguish them from missing data.
  • Attach the tables to the design before asking what they estimate.

The example below uses tidycreel 7.0.0, “Goldeye”, and the simulated data shipped with that release. Its release notes also document a change directly relevant here: interview identifiers are normalized to character when catch, length, and age tables are attached.

Start with what one row means

Before joining anything, I want to be able to finish this sentence for every table: one row represents… That answer tells us which values can be repeated and which must remain unique.

For the instantaneous-count workflow used here, the main tables have these roles:

Table What one row represents How it connects
Calendar A date or sampling unit in the declared design Dates, strata, and other design keys
Counts A count event at a recorded time Calendar dates and applicable sampling-unit keys
Interviews An interviewed trip or party under the field protocol Calendar/design keys and an interview identifier
Catch A species and catch type within an interview Interview identifier
Lengths An individual fish measurement or a release-length bin Interview identifier and species
Ages An individual aged fish Interview identifier and species

The sampling-calendar post explained why dates and strata matter. Here, the additional connection is the interview identifier. It allows several species records and several fish measurements to belong to the same interview without turning that interview into several independent trips.

Counts do not need to belong to individual interviews. A clerk might count anglers across a section and interview only some of them. Connecting every count to every interview on that date would create combinations that the field crew never observed.

One large spreadsheet can quietly repeat effort

Suppose an interview records four hours of fishing and catch of two species. A species-level table needs at least two rows to describe that catch. If we join those rows to the interview, the four hours appear twice. Summing the joined effort column now gives eight hours, although only four were reported.

Adding individual fish measurements can multiply rows again. Three length records for one species are three measurements, not three more interviews.

A join can be useful for a particular question. The problem is forgetting that it changes what a row represents. Keeping the original tables separate gives us a way to check a derived table against the observations that produced it.

This is the practical role of the design object. It holds the related tables and the information needed to interpret them, without requiring us to flatten everything into one spreadsheet.

Attach counts and interviews first

The first part of the example should look familiar. Define the calendar, attach counts, and then attach interviews with their catch and effort fields. The interview type remains explicit for the same reason discussed in the last post: it documents how the observations were collected.

library(tidycreel)

# This walkthrough was checked against version 7.0.0.
packageVersion("tidycreel")

data(example_calendar)
data(example_counts)
data(example_interviews)
data(example_catch)
data(example_lengths)
data(example_ages)

design <- creel_design(
  example_calendar,
  date = date,
  strata = day_type
) |>
  add_counts(example_counts) |>
  add_interviews(
    example_interviews,
    catch = catch_total,
    effort = hours_fished,
    harvest = catch_kept,
    trip_status = trip_status,
    trip_duration = trip_duration,
    n_anglers = n_anglers,
    interview_type = "access"
  )

This example declares access-point interviews. A roving survey should retain its own interview type and appropriate trip information; organizing the tables does not change the sampling protocol. The example also produces an equal-probability warning because it supplies no explicit sampling weights. It demonstrates table attachment; a survey with unequal selection probabilities needs those probabilities represented in its design before estimation.

The interview table still contains one row per interview, including interviews with no catch. Those zero-catch observations matter when estimating rates. A catch table alone cannot tell us how much fishing occurred without a fish being caught.

Keep biological observations connected too

Length and age records attach to interviews in the same way. They do not need a matching row number in the catch table. Several measured or aged fish can belong to one interview, and many interviews may have no biological records.

design <- design |>
  add_lengths(
    example_lengths,
    length_uid = interview_id,
    interview_uid = interview_id,
    species = species,
    length = length,
    length_type = length_type,
    count = count,
    release_format = "binned"
  ) |>
  add_ages(
    example_ages,
    age_uid = interview_id,
    interview_uid = interview_id,
    species = species,
    age = age,
    age_type = age_type
  )

print(design)

The shipped length example mixes individual harvested-fish measurements with binned release records. That is why this call specifies release_format = "binned" and supplies a count column. The count is the number of released fish in a bin, not an interview count. Length and age fate labels are "harvest" and "release", whereas the catch table uses "harvested" and "released". The add_lengths() and add_ages() references describe those formats.

An interview ID identifies the interview, not an individual fish. If a project needs to match a fish’s length to its age, retain a separate fish or specimen identifier in the source records. Interview and species alone cannot establish which measurement belongs to which aged fish.

These links preserve biological observations; they do not automatically make the measured fish representative of the entire catch. That still depends on which fish were available, which were selected for measurement, and the estimation method used.