Generates realistic synthetic creel data using a three-level hierarchical
generative model (day → trip → catch). Caller supplies distributional
parameters via params; no default data are bundled with the package.
The generative model follows Su & Clapp (2013) for the day and trip levels; the roving-clerk step follows Greene et al. (1995). Only the sampling step is taken from the latter: its own simulated anglers are deterministic – evenly spaced around the shoreline, all starting one hour into an eight-hour day, with trip lengths alternating between 3 and 6 hours – whereas the levels below draw from distributions.
Day level: Sample
n_sampled_daysdays from the season. Each sampled day draws the number of angler trips arriving from a Negative Binomial distribution.Trip level: Each trip draws effort (hr) from a Gamma distribution, party size from a zero-truncated Poisson, and trip completion from a Bernoulli draw.
Catch level: Each complete or incomplete trip draws total catch from a Negative Binomial; harvest is Binomial given total catch.
Roving clerk: Interview probability is proportional to trip length (length-biased sampling). Incomplete trips contribute elapsed effort, not total effort.
Usage
simulate_creel_data(
params,
season_days = 100L,
n_sampled_days = 30L,
day_types = NULL,
species = "walleye",
species_weights = NULL,
p_complete = 0.75,
p_zero_catch = 0.4,
n_anglers_per_day = NULL,
start_date = Sys.Date(),
n_counts_per_day = 3L,
lat = NULL,
daylight_hours = NULL,
seed = NULL
)Arguments
- params
Named list of distributional parameters. Required. Must include named sub-lists:
effort(withgamma_shape,gamma_rateforrgamma());party(withmeanparty size forrpois());catch_per_trip(withmeanandnb_sizeforrnbinom());harvest(withmean_pctas a percentage, e.g.35for 35%);counts(withmean_total_anglers, used whenn_anglers_per_day = NULL).- season_days
Integer. Total days in the season. Default 100.
- n_sampled_days
Integer. Number of days actually surveyed. Must be
<= season_days. Default 30.- day_types
Named numeric vector of stratum proportions, e.g.
c(weekday = 5/7, weekend = 2/7). Names become theday_typevalues in the output. WhenNULL(default), uses a single stratum"all". Note: passing a character vector (e.g.c("weekday", "weekend")) will error — the vector must be numeric with named elements.- species
Character vector. Species names for the catch table. Default
"walleye". Catch is split proportionally across species usingspecies_weights.- species_weights
Numeric vector. Relative catch weight per species. Must be same length as
species. Default equal weights.- p_complete
Numeric in (0, 1]. Probability a trip is complete (intercepted at trip end). Default
0.75.- p_zero_catch
Numeric in [0, 1). Probability a trip has zero total catch (zero-inflation). Default
0.40. Note: this cannot be derived from a survey inventory (API artifact); the default is an empirical estimate from the creel literature.- n_anglers_per_day
Numeric. Mean number of angler parties arriving per sampled day. Overrides
params$counts$mean_total_anglerswhen specified. DefaultNULL(use params).- start_date
Date. First day of the simulated season. Default
Sys.Date().- n_counts_per_day
Integer. Number of instantaneous count observations per sampled day. Default
3.- lat
Numeric latitude in decimal degrees, positive north. When given, the daily fishing period \(T\) is computed per date with
day_lengthand the counts table gainsdaylight_hoursandangler_hourscolumns. DefaultNULL. Mutually exclusive withdaylight_hours.- daylight_hours
Numeric. Length of the daily fishing period \(T\), in hours, used to convert instantaneous counts to angler-hours. Accepts a single value applied to every day, or a named length-12 vector of monthly values (names
"1"…"12", ormonth.abb) which is matched on the month of each count date. Use this when \(T\) is set by regulation or field protocol rather than by daylight. DefaultNULL. Mutually exclusive withlat.Supplying neither leaves
daylight_hoursandangler_hoursoff the counts table, rather than substituting a latitude the caller never gave.- seed
Integer or
NULL. Random seed for reproducibility. DefaultNULL.
Value
A named list with four data frames:
scheduleFull-season calendar, one row per day. Columns:
date,day_type,sampled(logical). Pass directly tocreel_designas thecalendarargument. Unsampled days have day_type assigned proportionally fromday_types.interviewsOne row per intercepted angler party. Columns:
date,day_type,interview_id,trip_status("complete"or"incomplete"),hours_fished,trip_duration(total trip length; equalshours_fishedfor complete trips),n_anglers,catch_total,catch_kept,species_sought.countsOne row per instantaneous count. Columns:
date,day_type,count_time(integer index 1…n_counts_per_day) andtotal_anglers, plusdaylight_hours(\(T\) for that day) andangler_hours(total_anglers * daylight_hours) whenlatordaylight_hourswas supplied. Passcount_time_col = count_timetoadd_countswhenn_counts_per_day > 1.catchLong-format catch table. Columns:
interview_id,species,count,catch_type("caught","harvested","released").
Pass angler_hours, not total_anglers, to
add_counts. An instantaneous count estimates the mean number of
anglers present, not effort; effort is that count multiplied by the length of
the period the count was randomised within (Hoenig et al. 1993).
estimate_effort() expands whatever numeric column it is given and
cannot tell the two apart, so handing it total_anglers yields
angler-days silently mislabelled as angler-hours. When lat or
daylight_hours is supplied the counts table carries three numeric
columns and add_counts will not guess between them: name the one
you mean with
add_counts(design, sim$counts, count_col = angler_hours).
When n_counts_per_day > 1 you must also drop the measures you
are not using before attaching. total_anglers differs between the
counts taken within one day, so aggregation has no single value to carry
forward and would otherwise keep whichever came first. add_counts()
aborts rather than do that (GH #162); select the columns you need, as the
second example below does. daylight_hours is constant within a day and
can stay.
Note that day_length gives astronomical daylight. Where the
fishing day is fixed by regulation or access hours instead, pass that period
as daylight_hours.
The schedule output can be passed directly to creel_design
as the calendar argument. The interviews and counts
outputs are then passed to add_interviews and
add_counts.
References
Su, Z. & Clapp, D.F. (2013). Evaluation of sample design and estimation methods for Great Lakes angler surveys. Trans. Am. Fish. Soc. 142: 234–246. doi:10.1080/00028487.2012.728167
Greene, C.J., Hoenig, J.M., Barrowman, N.J. & Pollock, K.H. (1995). Programs to simulate catch rate estimation in a roving creel survey of anglers. DFO Atlantic Fisheries Research Document 95/99. Department of Fisheries and Oceans, St. John's, NL.
Petrere, M. Jr., Giacomini, H.C. & De Marco, P. Jr. (2010). Catch-per-unit-effort: which estimator is best? Braz. J. Biol. 70: 483–491. doi:10.1590/S1519-69842010005000010
See also
Other "Simulation":
day_length(),
simulate_creel_catch()
Examples
my_params <- list(
effort = list(gamma_shape = 2.0, gamma_rate = 0.8),
party = list(mean = 1.5),
catch_per_trip = list(mean = 1.8, nb_size = 0.5),
harvest = list(mean_pct = 35),
counts = list(mean_total_anglers = 10)
)
# Basic simulation (single stratum)
set.seed(42)
sim <- simulate_creel_data(
params = my_params,
season_days = 90,
n_sampled_days = 20,
species = c("walleye", "northern_pike"),
species_weights = c(0.6, 0.4)
)
head(sim$schedule)
#> date day_type sampled
#> 1 2026-09-14 all FALSE
#> 2 2026-09-15 all FALSE
#> 3 2026-09-16 all TRUE
#> 4 2026-09-17 all FALSE
#> 5 2026-09-18 all TRUE
#> 6 2026-09-19 all FALSE
head(sim$interviews)
#> date day_type interview_id trip_status hours_fished trip_duration
#> 1 2026-09-18 all 1 complete 2.249 2.249
#> 2 2026-09-18 all 6 complete 3.170 3.170
#> 3 2026-09-18 all 4 complete 3.920 3.920
#> 4 2026-09-18 all 2 complete 4.609 4.609
#> 5 2026-09-18 all 5 complete 3.272 3.272
#> 6 2026-09-18 all 3 incomplete 2.124 4.904
#> n_anglers catch_total catch_kept species_sought
#> 1 1 3 2 walleye
#> 2 1 1 1 walleye
#> 3 1 0 0 walleye
#> 4 2 0 0 walleye
#> 5 1 0 0 walleye
#> 6 1 1 0 walleye
head(sim$counts)
#> date day_type count_time total_anglers
#> 1 2026-09-16 all 1 9
#> 2 2026-09-16 all 2 1
#> 3 2026-09-16 all 3 9
#> 4 2026-09-18 all 1 8
#> 5 2026-09-18 all 2 0
#> 6 2026-09-18 all 3 3
head(sim$catch)
#> interview_id species count catch_type
#> 1 1 walleye 3 caught
#> 2 1 walleye 3 harvested
#> 3 1 walleye 0 released
#> 4 6 walleye 1 caught
#> 5 6 walleye 1 harvested
#> 6 6 walleye 0 released
# Multi-stratum simulation with day_types (named numeric vector).
# `lat` derives the daily fishing period from day_length(), which adds the
# daylight_hours and angler_hours columns to sim2$counts.
set.seed(1)
sim2 <- simulate_creel_data(
params = my_params,
season_days = 90,
n_sampled_days = 20,
day_types = c(weekday = 5/7, weekend = 2/7),
lat = 40.699
)
# Round-trip: simulate → creel_design → add_counts → add_interviews
# total_anglers is dropped: it varies between the counts taken within a day,
# so aggregation cannot carry it forward (see the note above).
counts2 <- sim2$counts[, c("date", "day_type", "count_time", "angler_hours")]
design <- creel_design(sim2$schedule, date = date, strata = day_type) |>
add_counts(
counts2,
count_col = angler_hours, # counts alone are angler-days, not effort
count_time_col = count_time
) |>
add_interviews(
sim2$interviews,
catch = "catch_total",
effort = "hours_fished",
harvest = "catch_kept",
trip_status = "trip_status",
trip_duration = "trip_duration",
n_anglers = "n_anglers",
interview_type = "roving"
)
#> Warning: No weights or probabilities supplied, assuming equal probability
#> Warning: 72 interviews have zero catch.
#> ℹ Zero catch may be valid (skunked) or indicate missing data.
#> ℹ Added 121 interviews: 97 complete (80%), 24 incomplete (20%)
