Demography and population dynamics

Cormack-Jolly-Seber apparent survival

"What proportion of my marked animals survive from one year (season, month) to the next, does that differ between males and females or juveniles and adults, and is it associated with body condition or winter weather?" The model separates survival from imperfect recapture: an animal not seen this year may be alive but missed. It conditions on first capture, so it estimates survival and recapture probability, not population size.

Is this the right analysis?

Use it when

Individually marked animals (bands, PIT tags, photo-ID, genetic tags) recaptured or resighted on repeated occasions; the question is how many survive from one occasion to the next and whether survival differs by sex/age class or varies with individual traits, environmental conditions or time.

Not for

Abundance or recruitment (use jolly-seber / scr-open), unmarked counts (nmixture), dead recoveries (band-recovery models), movement between sites or states (multistate), known-fate telemetry (use a known-fate / survival-time model).

The data you need

Capture history per individual (wide 0/1 history, long table of id x occasion, or long table of id x capture date), optional individual table (group, age at marking, covariates) and occasion table (environmental covariates).

What the example's main data file looks like:

First 5 of 1,283 rows of sosp_captures.csv.
band_numbercapture_datenetbander
2841-224652016-05-10N03KL
2841-808672016-05-10N07JT
2841-423262016-05-11N02MR
2841-599412016-05-11N07MR
2841-956752016-05-12N01JT

Not sure? Use the finder to compare modules.

Try it

The worked example

Adult song sparrow apparent survival from constant-effort banding (Cormack-Jolly-Seber, simulated data with known truth)

A constant-effort mist-netting station catches adult song sparrows (Melospiza melodia) each breeding season, May to August, from 2016 to 2024. Every new adult gets a numbered aluminium band. Banders record sex, body mass and wing chord at first capture. Many birds are caught once and never again. Some of them die. Others are passing migrants or floaters ("transients") that never settle at the site.

The question: What is annual apparent survival of adult song sparrows at a banding station, does it differ between sexes, and is it associated with winter cold and body mass, allowing for transient birds that never return?

The data are simulated, so the true answer is known and the analysis can be checked against it.

What the researcher typed

I have nine years of song sparrow banding data from my station (2016 to 2024) in three files: sosp_captures.csv (every capture, with band number and date), sosp_birds.csv (sex and body mass of each bird when first banded) and sosp_years.csv (mean January minimum temperature for each winter). I want annual adult survival, whether it differs between males and females, and whether it is related to winter cold and body mass. Many birds are only caught once; I think some are migrants passing through. Please analyse this and give me a report with figures I can use in a paper.

Run it yourself

  1. Download the example data below and unzip it.

  2. Open the unzipped folder in Claude Code (see Install).

  3. Paste the prompt above. The agent checks the files, writes a plan, runs planted truth, fits the model and writes the report. You can compare your results with the ones below.

Contains sosp_captures.csv, sosp_birds.csv, sosp_years.csv, the settings file (config.yaml), a description of the data and a step-by-step walkthrough.

Study design details
n occasions9
years2016-2024
n intervals8
n birds575
new birds per year66-78 (mean 72) in 2016-2023
prop male0.52
capture rows1283
bird seasons813
birds recaptured158
occasion covariatewinter_min_temp_c (8 winters used)
individual covariatemass_g
groupsex (F reference)
enginemarked (maximum likelihood)
phi formula~ tsm + sex + winter_min_temp_c + mass_g (fitted as ~ tsm * (sex + winter_min_temp_c + mass_g))
p formula~ sex
What the report says

Results from the example data

Figure: Conditional effects of all continuous covariates on apparent survival (line: estimate; bands: 50% and 95% confidence intervals); other covariates at their means.
Conditional effects of all continuous covariates on apparent survival (line: estimate; bands: 50% and 95% confidence intervals); other covariates at their means.

What this means

  • Apparent annual survival at average mean January minimum temperature and body mass of residents: Female 0.54 (95% CI 0.43-0.65); Male 0.62 (95% CI 0.53-0.70).
  • Apparent survival is the probability of surviving AND staying in the study area; animals that emigrate permanently are indistinguishable from deaths, so true survival is at least this high.
  • Sex: Male vs Female (apparent survival): odds ratio 1.34 (95% CI 0.76-2.40). The interval includes 1: the data do not show a clear difference.
  • Mean January minimum temperature: each 1 SD (2.04 deg C) increase multiplies the odds of apparent survival by 1.30 (95% CI 0.91-1.87); female survival 0.45 at -6.0 deg C vs 0.61 at -1.0 deg C. The interval includes no effect. This is an association, not proof of a causal effect.
  • Body mass: each 1 SD (1.66 g) increase multiplies the odds of apparent survival by 1.17 (95% CI 0.90-1.52); female survival 0.49 at 19.8 g vs 0.59 at 23.9 g. The interval includes no effect.
  • Transients: an estimated 0.26 (95% CI 0.00-0.53) (Female); 0.32 (95% CI 0.11-0.48) (Male) of newly marked animals were transients.
  • Detection (recapture) probability per occasion: Female 0.41 (95% CI 0.28-0.55); Male 0.57 (95% CI 0.47-0.65).
  • Goodness of fit: overall c-hat = 0.92 (p = 0.624). Test 3.SR detected transients (p = 0.027): the fitted model accounts for them (tsm). Excluding that component, c-hat = 0.50 (p = 0.990). No evidence of remaining lack of fit.

More figures

Figure: Apparent survival versus mean January minimum temperature (°C) (line: maximum-likelihood estimate; dark band: 50%, light band: 95% confidence interval), one curve per Sex; other covariates at their means.
Apparent survival versus mean January minimum temperature (°C) (line: maximum-likelihood estimate; dark band: 50%, light band: 95% confidence interval), one curve per Sex; other covariates at their means. Rug: observed mean January minimum temperature (°C) values (one per interval).
Figure: Apparent survival versus body mass (g) (line: maximum-likelihood estimate; dark band: 50%, light band: 95% confidence interval), one curve per Sex; other covariates at their means.
Apparent survival versus body mass (g) (line: maximum-likelihood estimate; dark band: 50%, light band: 95% confidence interval), one curve per Sex; other covariates at their means. Rug: observed body mass (g) values (one per individual).
Figure: Apparent survival per interval between occasions (points: estimates; thick bar: 50%, thin bar: 95% confidence interval); colours and symbols = Sex…
Apparent survival per interval between occasions (points: estimates; thick bar: 50%, thin bar: 95% confidence interval); colours and symbols = Sex; values are predicted from mean January minimum temperature (°C) only (no separate year effect in the model).

Figures are the module's default publication figures, reduced for the web. The report also saves them as PDF and full-resolution PNG.

How we know it works

Planted truth at the example's design

Before trusting the example's results, the module was run on many simulated data sets with the same design and known true values. Each row is one quantity: did the method get it back? (What the thresholds are.)

Verdict for this example: ✓ DONE

Every quantity checked passed planted truth at this design.

The example's design compared with the validated design: larger than validated
This example9 annual occasions, 575 birds (about 72 newly banded per year), 25% transients
Design the module was validated at8 annual occasions, 350 animals (50 newly marked per year), recapture probability 0.5

The example has more birds (575 against 350) and one more occasion than the validated default, and a similar recapture probability (0.45-0.57 against 0.5). Transients were validated separately: with 30% transients and the transient model fitted, all 8 estimands passed. At the default design every estimand passed, with survival bias within 1.2% and coverage 0.90-0.98. Problems appeared only at much smaller designs (120 animals, 5 occasions, recapture 0.25).

≈ Borderline results

These calls sit close to the pass/fail line. With another 200 simulated data sets they could come out the other way, so read them as “about at the limit” rather than as a clear pass or fail. Where the allowance is set by the noise floor, a quantity with a small true value can pass with a larger percentage bias than a quantity that fails. PASS there means the bias could not be told apart from simulation noise, not that the quantity is estimated better.

  • phi_beta_body_mass (✓ PASS): bias +9.7% against an allowance of ±11.6% (set by the simulation-noise floor (3 Monte Carlo SE)), so it used 84% of the allowance.

10 of 10 rows pass; 1 is borderline.

Bias: average estimate minus truth, as a percentage of the truth. Allowed bias: the largest bias that still passes, the bigger of 10% of the truth and 3 Monte Carlo standard errors († = set by that simulation-noise floor, so the allowance is wider than 10%). Coverage: share of 95% intervals that contained the true value, with the minimum needed in brackets. ≈ borderline: the bias used 80–125% of its allowance, or coverage was within 0.015 of the minimum; such a result could flip between PASS and FAIL with another set of simulations. Quantity names are as they appear in the report.
QuantityTrue valueAverage estimateBiasAllowed biasCoverage (needed)Result
detection_female0.450.452+0.4%±10.0%0.94 (0.904)✓ PASS
detection_male0.5740.575+0.1%±10.0%0.96 (0.904)✓ PASS
p_beta_sex_male0.50.503+0.5%±13.4%†0.96 (0.904)✓ PASS
phi_beta_body_mass0.250.274+9.7%±11.6%†0.96 (0.904)✓ PASS ≈ borderline
phi_beta_sex_male0.30.302+0.7%±19.0%†0.95 (0.904)✓ PASS
phi_beta_winter_severity-0.4-0.405-1.2%±12.7%†0.95 (0.904)✓ PASS
proportion_transient_female0.250.228-8.7%±14.9%†0.96 (0.904)✓ PASS
proportion_transient_male0.250.248-0.8%±10.0%0.96 (0.904)✓ PASS
survival_female0.550.548-0.3%±10.0%0.94 (0.904)✓ PASS
survival_male0.6230.621-0.2%±10.0%0.96 (0.904)✓ PASS

Limits that matter

  • Low power at this design: 17% for the sex effect on survival, 51-56% for the winter and body mass effects (200-replicate recovery); intervals that include zero are inconclusive, not evidence of no effect.
  • Transients must be modelled: ignoring them biases survival about 13% low with coverage about 0.5 (validated negative control); 3.SR flagged transience here and tsm was fitted.
  • proportion_transient is an unconstrained ratio; the female interval extends below 0 (-0.12) and is truncated to 0 in the report text.
  • False-positive rate for individual covariates such as body mass is 7.5-8% at nominal 5% (Wald tests).
  • The winter effect rests on 8 winters (one value per year) and is an association, not a causal effect.
  • Not modelled: individual heterogeneity in recapture beyond sex, band loss, temporary emigration; apparent survival = true survival x site fidelity.
All known limits from the module's validation record
  1. Low-information designs, Bayesian engine. With about 5 occasions, about 120 animals and p of about 0.25, Nimble posterior means of survival (+11%) and of slopes (+40-76%) are biased (the gate FAILS on bias; coverage is still 0.90-0.96). marked passes at that design, but its effect estimates are uninformative (power below 10%). With few occasions or low p, prefer marked, and report intervals rather than point estimates.
  2. SD of random year effects ((1 | time), Nimble): biased upward by about 14% (0.569 vs 0.5) with 11 intervals under the half-Normal(0, 1) prior. The gate FAILS on bias, while coverage is 0.94. Treat the temporal SD as weakly identified with fewer than about 15 intervals. The other parameters in that model recover well.
  3. Transients must be modelled. If they are ignored, survival is about 13% too low and the gate fails (section 4). The module flags 3.SR. An additive tsm attenuates covariate effects, so keep the default tsm_interact: true.
  4. False-positive rate for individual covariates is 7.5-8% at nominal 5% (Wald, 350 animals).
  5. ML boundary fits. About 5% of replicates in the hard and phi(t)p(t) designs hit a boundary (a probability of 0 or 1). These fits are flagged as not converged and not interpreted.
  6. Not modelled: individual heterogeneity in p beyond covariates (no mixtures), tag loss, temporary emigration, losses on capture, and multistate movement. Apparent survival is always true survival x site fidelity.
  7. proportion_transient = 1 - phi_first/phi_later is an unconstrained ratio (per unit time when intervals are unequal; not validated with tsm and unequal intervals). Its table interval can extend below 0 (no evidence of transients). The report text truncates it to 0-1 for display.
  8. Random time effects are available only with engine: nimble. Random effects on p are not implemented (p_temporal_sd is used only in simulation).

How this module was validated

  • 200 simulated datasets per design for the default maximum-likelihood engine (marked) and 100 for Nimble (3 chains x 3,000 iterations, 1,000 burn-in). To pass, bias had to be within 10% (or 3 Monte Carlo SE) and coverage (how often the 95% interval contained the truth) at least about 0.90.
  • Default design: all 6 estimands passed on all three engines (marked, Nimble marginal and Nimble latent). Variants also passed: time-dependent survival, age plus trap-happiness, and transients with the transient model fitted. The one failure was the random-year SD, which was +13.8% too high.
  • Hard design (120 animals, 5 occasions, recapture 0.25): marked passed, but power to detect the effects was only 8-9%. Nimble failed 3 of 6 estimands (female survival +11.3%).
  • Negative control: ignoring transients biased survival 12-14% low, with coverage 0.45-0.50. The gate failed, as it should.
  • False-positive rate with no true effects: 0.03-0.08.
  • Cross-checks on the classic dipper data: the module matched wiqid (checked against MARK), a hand-coded likelihood and marked (-2 log-likelihood 666.84), and the goodness-of-fit test reproduced the published value.
  • Power: about 80-100 newly marked animals per year over 8 years are needed for 80% power on a +0.4 logit sex difference. A separate verifier agent with its own simulator (unequal intervals) got 8/8 on both engines and null rejection rates of 0.04-0.06, and both of its negative controls failed as they should.

Every run uses the acceptance gate described on the validation page. The full record follows, section by section, exactly as kept in the module's VALIDATION.md.

Settings and pass/fail rule used for every run

All recovery runs: bin/Rscript core/run.R recover cjs --config dev/cjs/cfg/<label>.yaml --nsim N --cores 16 --out results/cjs/recover_<label> on njobvu (SLURM via bin/ejob, 16 cores), module fast_settings (marked: ML via TMB; Nimble: 3 chains x 3,000 iterations, 1,000 burn-in, no GOF). Drivers: dev/cjs/run_marked.sh, dev/cjs/run_nimble.sh. Raw results: results/cjs/recover_<label>/ (summary CSV, per-replicate CSV, HTML report). The tables below come straight from the summary CSVs (dev/cjs/md_tables.R). The marked runs and the Nimble default run were repeated on 2026-10-20 with the final module code and reproduced the earlier numbers exactly (same seeds).

Gate (core/R/recovery.R::recovery_gate, thresholds NOT changed): |bias| <= max(10% of truth, 3 MC SE) (for truth = 0: max(0.1 x empirical SD, 3 MC SE)); 95% interval coverage >= 0.95 - 3 MC SE (floor 0.80); converged in >= 90% of ALL replicates. "Converged" means max R-hat < 1.05 for Nimble. For marked it means a finite Hessian, no probability estimate outside 0.001-0.999, and no logit coefficient with |estimate| or SE > 10 (boundary or non-identifiable fits are flagged).

Default design (module defaults): 8 annual occasions; 50 newly marked per occasion at occasions 1-7 (350 animals); half male; survival 0.60 for females at average covariates; male +0.4 logit; winter severity (occasion covariate) -0.5 per SD; body mass (individual covariate) +0.3 per SD; constant recapture p = 0.5. Fitted: phi ~ sex + winter_severity + body_mass, p ~ 1.

Timing (median per replicate): marked 0.5 s; Nimble marginal (dCJS_vv) 44 s; Nimble latent state 163 s; Nimble with (1 | time) at 12 occasions 292 s. The selftest (4 reps, marked) takes 3.3 s per replicate including GOF.

1. Recovery at the default design: PASS (all three engines)
marked (default engine), nsim 200: 200/200 converged, coverage bound 0.904
estimandtruthmean estrel. biasbias MC SERMSEcoveragepowergate
detection0.5000.500+0.1%0.00260.0360.940PASS
phi_beta_body_mass0.3000.317+5.5%0.00760.1090.9600.89PASS
phi_beta_sex_male0.4000.409+2.3%0.01380.1950.9550.56PASS
phi_beta_winter_severity-0.500-0.516-3.3%0.01260.1780.9800.89PASS
survival_female0.6000.599-0.2%0.00270.0390.945PASS
survival_male0.6910.691+0.0%0.00250.0350.930PASS
Nimble marginal (nimbleEcology dCJS_vv), nsim 100: 100/100 R-hat < 1.05 (max 1.003), bound 0.885
estimandtruthmean estrel. biasbias MC SERMSEcoveragepowergate
detection0.5000.496-0.9%0.00360.0370.930PASS
phi_beta_body_mass0.3000.322+7.4%0.01150.1160.9400.88PASS
phi_beta_sex_male0.4000.437+9.3%0.01920.1950.9600.60PASS
phi_beta_winter_severity-0.500-0.539-7.9%0.02010.2040.9800.90PASS
survival_female0.6000.602+0.3%0.00380.0380.950PASS
survival_male0.6910.699+1.1%0.00370.0370.900PASS
Nimble latent state (z sampled), nsim 100: 100/100 R-hat < 1.05 (max 1.016), bound 0.885
estimandtruthmean estrel. biasbias MC SERMSEcoveragepowergate
detection0.5000.495-1.0%0.00370.0370.930PASS
phi_beta_body_mass0.3000.322+7.5%0.01140.1160.9400.88PASS
phi_beta_sex_male0.4000.438+9.4%0.01930.1950.9600.60PASS
phi_beta_winter_severity-0.500-0.540-8.1%0.01990.2020.9700.91PASS
survival_female0.6000.602+0.3%0.00380.0380.960PASS
survival_male0.6910.699+1.2%0.00370.0370.910PASS

The two Nimble formulations use the same seeds and give nearly identical estimates, which confirms that the marginal (forward algorithm) and latent-state likelihoods agree. The posterior-mean slopes are 7-9% further from zero than the ML estimates, the usual small-sample skew of logit-scale posteriors under N(0, 2) priors. This is within tolerance.

2. Model variants (marked unless stated): all PASS
variant (config)nsimconvergedresult
Fully time-dependent phi(t) p(t) with random year variation in the truth (timedep)200189/200 (94.5%)13/13 PASS: per-interval survival bias -3.3% to +4.3%, coverage 0.937-0.989; last phi x p product -0.1%, coverage 0.968
same, Nimble (timedep_nimble)100100/10013/13 PASS: bias -1.6% to +5.3%, coverage 0.93-0.99
40% juveniles (age_class), trap-happy recapture (td, +0.8 logit), sex effect on p (agetd)200200/20013/13 PASS: juvenile effect -1.033 (truth -1.0, coverage 0.945); td 0.787 (truth 0.8, coverage 0.95); survival in 4 sex x age classes within 1.2%
30% transients, tsm fitted (crossed with the other terms, the default) (transient_tsm)200200/2008/8 PASS: resident survival -0.9 / -0.7%; proportion transient 0.282 / 0.279 (truth 0.30, coverage 0.97 / 0.955); covariate effects within 4%
Random year effects on survival `(1time), 12 occasions, Nimble (randomtime_nimble`)100100/1005/6: survival, detection and slopes PASS (within 2.2%); survival_temporal_sd FAILS on bias (0.569 vs 0.5, +13.8%, MC SE 0.020; coverage 0.94). See Known limits

The 11 replicates of timedep that were not converged were boundary fits (some interval's survival or p estimated at 0 or 1), which the module flags as not converged.

3. Harder design: 5 occasions, 30 newly marked per occasion (120 animals), p = 0.25
marked (hard), nsim 200: 190/200 converged (95%), bound 0.903. All 6 PASS, but see note
estimandtruthmean estrel. biasbias MC SERMSEcoveragepowergate
detection0.2500.267+6.8%0.00530.0750.911PASS
phi_beta_body_mass0.3000.423+40.9%0.04230.5950.9790.08PASS
phi_beta_sex_male0.4000.477+19.2%0.06300.8700.9680.09PASS
phi_beta_winter_severity-0.500-0.575-15.0%0.07441.0260.9790.08PASS
survival_female0.6000.600+0.0%0.01160.1600.953PASS
survival_male0.6910.694+0.4%0.01110.1530.947PASS

Survival and p are recovered without bias and with nominal coverage, but the intervals are wide (RMSE about 0.16). The covariate effects pass only because their MC SE is large. Power is 8-9%, the RMSE (0.6-1.0 logit) is larger than the effects, and the large relative bias comes from a few replicates with near-separation. At this design, survival can be estimated but effects on survival cannot be detected. The report says so through the wide intervals.

Nimble (hard_nimble), nsim 100: 100/100 R-hat < 1.05. 3/6 FAIL (bias)
estimandtruthmean estrel. biasbias MC SERMSEcoveragepowergate
detection0.2500.243-2.9%0.00650.0660.920PASS
phi_beta_body_mass0.3000.511+70.2%0.04750.5180.9100.23FAIL (bias)
phi_beta_sex_male0.4000.703+75.7%0.08890.9350.9100.14FAIL (bias)
phi_beta_winter_severity-0.500-0.704-40.7%0.07040.7300.9600.21PASS
survival_female0.6000.668+11.3%0.01250.1410.940FAIL (bias)
survival_male0.6910.756+9.4%0.01340.1480.900PASS

With about 120 animals and p = 0.25, the posterior mean is pulled by the priors (Logistic(0,1) on the intercept, N(0, 2) on slopes) and by the skew of the posterior. Coverage stays at 0.90-0.96, so the credible intervals remain honest, but point estimates are biased. This is a documented limit (below). At such designs, use the marked engine, or report intervals rather than point estimates.

4. Negative control: 30% transients ignored. The gate FAILS, as it should
estimandtruthmean estrel. biasbias MC SERMSEcoveragegate
detection0.5000.444-11.2%0.00310.0710.710FAIL
phi_beta_body_mass0.3000.273-9.0%0.00820.1190.925PASS
phi_beta_sex_male0.4000.368-7.9%0.01650.2340.920PASS
phi_beta_winter_severity-0.500-0.409+18.3%0.01380.2150.900FAIL
survival_female0.6000.518-13.7%0.00350.0960.500FAIL
survival_male0.6910.608-12.1%0.00300.0940.450FAIL

When transients (animals that leave after marking) are not modelled, apparent survival is biased low by 12-14%, and the 95% intervals contain the truth only half of the time. So the recovery check catches this misspecification. In real data, R2ucare TEST 3.SR detects it: in the example data set, 3.SR p = 0.027 and the report recommends tsm. Fitting tsm (section 2) removes the bias.

A second, related failure: tsm fitted additively on the logit scale (transient_tsm_additive, tsm_interact: false). Survival passes (-0.8% / -2.8%), but covariate effects are attenuated by 16-25%. The body-mass, sex and winter effects and proportion_transient_male FAIL. Transients scale first-interval survival multiplicatively, so an additive logit term mixes them into the covariate slopes. For this reason the module crosses tsm with the other survival terms by default (MARK's phi(a2 x ...)), and that version passes (section 2).

5. False-positive rate with all survival effects = 0: PASS
estimandtruthmean estbiascoveragerejection rate (alpha 0.05)
phi_beta_body_mass00.001+0.0010.9200.080
phi_beta_sex_male0-0.003-0.0030.9250.075
phi_beta_winter_severity00.011+0.0110.9700.030
survival_female / male0.60.597 / 0.596-0.5% / -0.6%0.935 / 0.960

The false-positive rates are 3-8% (MC SE about 1.5%). Individual-level effects are slightly above nominal (Wald test, 350 animals), and the winter effect (8 years) is conservative. Treat a lone individual-covariate effect with p just under 0.05 cautiously.

6. Cross-checks against independent implementations

European dipper (Lebreton et al. 1992; 294 birds, 7 occasions), read from a long CSV by the module's own reader. Its histories are identical to marked::dipper (all 294).

modelquantitymodule markedhand-coded forward likelihoodwiqid::survCJS (checked against MARK by its author)marked::crm directmodule Nimble (posterior mean)
phi(.) p(.)survival0.5602(logit 0.2421)0.5602(logit 0.242)0.5615
recapture0.9027(logit 2.2263)0.9026(logit 2.227)0.8957
-2 lnL666.84666.838666.838666.838
phi(flood) p(.)survival, normal years0.60710.60710.6079
survival, flood years (1982-83, 1983-84)0.46880.4688
recapture0.89980.89980.8924
-2 lnL660.10660.1028660.1028
phi(sex) p(.)logit sex effect0.079290.07929(no individual covariates in wiqid)0.0804

R2ucare on the dipper females gives overall chi-square 10.276 on 12 df (p = 0.592). This is the value printed in the R2ucare vignette (Gimenez et al. 2018).

Simulated data, default design (seed 11). The module marked fit matches the hand-coded likelihood to 5 decimals for every coefficient and SE (e.g. sex 0.48591, SE 0.2084). Nimble marginal and latent agree with ML within 0.03 logit units and have posterior SDs within 3% of the ML SEs. The fit with unequal intervals (one occasion dropped, so a 2-year gap) agrees with the hand likelihood to 4 decimals (intercept 0.4647, sex 0.2765, mass 0.1550) and with Nimble within 0.006 logit units.

phi(t) p(t), simulated (seed 21). The module marked fit equals wiqid::survCJS for all six identifiable interval survivals (e.g. 0.7959, 0.7151, 0.6367) and recaptures (0.5277, 0.4983, ...). wiqid's confounded last pair gives phi7 x p7 = 0.8287 x 0.5501 = 0.4559, which equals the module's survival_x_detection_2021 = 0.4559. Nimble gives 0.4468.

7. Power

Power at alpha = 0.05 (default effects; 8 occasions, p = 0.5):

newly marked per occasion (total animals)body mass (+0.3/SD)sex (+0.4)winter severity (-0.5/SD)survival coverage F / M
10 (70)0.250.110.210.98 / 0.95
20 (140)0.510.220.430.97 / 0.95
40 (280)0.800.410.770.96 / 0.94
80 (560)0.990.780.980.94 / 0.94
160 (1120)1.000.961.000.95 / 0.97

Coverage holds at every sample size (0.90-0.99 for all estimands). To have 80% power for a male-female difference of +0.4 logit (about +9 percentage points of survival), plan for roughly 80-100 newly marked animals per year over 8 years at p = 0.5.

8. Example fits
  • config.yaml: song sparrow banding station, simulated with known truth (TRUTH.txt), dates format, transients, sex, winter temperature, body mass. Marked estimates: resident female survival 0.544 (truth 0.55); male logit effect 0.30 (0.3); winter 0.27 (0.4); mass 0.16 (0.25); proportion transient 0.26 / 0.32 (0.25); female p 0.41 (0.45); male p effect 0.63 (0.5). All truths are inside the 95% CIs. 3.SR flags transience (p = 0.027).
  • config_nimble.yaml: same data, Bayesian, with (1 | time). Estimates are within 0.04 of marked, R-hat <= 1.001, bulk ESS >= 3,600.
  • config_dipper.yaml: the real dipper data with phi(flood). The output matches section 6.
Independent verification (2026-10-20, separate verifier agent)

Literature / code check. Checked line by line against the CJS likelihood (Lebreton et al. 1992), the latent-state formulation (Kery & Schaub 2012 ch. 7: z[f]=1, z[t] ~ Bern(phi z[t-1]), y ~ Bern(p z)), the installed nimbleEcology::dCJS_vv source (forward algorithm, x[1] must be 1, probCapture[1] unused: the module's padding with y = 1, phi = 1, p = 1 before first capture adds log 1 = 0 and is correct), Pradel et al. 1997 (proportion transient = 1 - phi_first/phi_resident) and Pradel 1993 (td = caught at t-1). Priors: Logistic(0,1) intercepts (= U(0,1)), N(0, 2) slopes, half-N(0,1) on the random-year SD (non-centred). Unequal intervals enter as phi^dt in both engines. Simulator and fit share only cjs_scaling()/cjs_prepare() (used to define truth on the standardised scale), so the independent simulator below was written without any module code.

Independent planted truth (dev/cjs/verify/verify_sim.R, own simulator; logs logs/cjs_verify.log, logs/cjs_verify_nimble.log; results results/cjs/verify/). Design differs from the builder's: 10 occasions with unequal intervals (one 2-unit gap), 35 marked per occasion at 1-9 (315 animals), 45% male, group levels "F"/"M", survival 0.72 (F), male -0.5, wing length +0.4/SD (individual), rainfall -0.35/SD (occasion, gamma-distributed), p = 0.35 (F) with male +0.6. Fitted phi ~ sex + wing + rain, p ~ sex.

runnsimconvergedresult
main, marked200200/2008/8 PASS: survival F -0.2% (cov 0.96), M -1.4% (0.945); p F -0.3%, M +1.8%; slopes: sex -6.7%, wing +1.0%, rain +5.0%, p sex +7.6%; coverage 0.93-0.98
main, Nimble marginal100100/100 (max R-hat 1.003)8/8 PASS: survival -0.5% / -0.5%; slopes -0.4% to +6.9%; coverage 0.93-0.99
null (all effects 0), marked200200/2008/8 PASS; rejection rates 0.04 (p sex), 0.05 (rain), 0.06 (sex), 0.04 (wing)
35% transients, tsm fitted, marked200200/20010/10 PASS: proportion transient 0.352 / 0.329 (truth 0.35, cov 0.965 / 0.955); resident survival +0.8% / -0.3%
negative control: 35% transients ignored200200/200FAILS as it should: survival -10.7% / -14.4%, coverage 0.58 / 0.585; p -13 to -17%; rain effect +24%
negative control: unmodelled p heterogeneity (p 0.15 / 0.65 mixture)100100/100FAILS: survival -8.7% / -10.1%, coverage 0.47 / 0.72

Builder's commands rerun (dev/cjs/verify/run_marked_verify.sh, results/cjs/verify/rerun_*): default, null and transient_ignored reproduced sections 1, 4 and 5 exactly (e.g. survival -0.2% / 0.0%, coverage 0.945 / 0.93; FPR 0.080 / 0.075 / 0.030; transients ignored -13.7% / -12.1%, coverage 0.50 / 0.45).

Usability. The three examples were refitted (dev/cjs/verify/run_examples_verify.sh); every interpretation sentence of the song sparrow report was recomputed from the fit (odds ratios, CIs, 10th/90th-percentile survival 0.45 / 0.61 and 0.49 / 0.59, interval range) and is correct; dipper flood survival 0.607 / 0.469 matches section 6. Figures checked by eye (conditional effects in original units with rug, per-interval survival, odds-ratio forest plot, GOF, captures, Nimble trace/posterior). Messy-data tests (dev/cjs/verify/messy_test.R): NA covariate, NA covariate year, missing year row, misspelled band id, duplicated id, bad date all gave clear messages already.

Changes made (backups dev/cjs/verify/*.preverify; fit code untouched, so no recovery result changes):

  • R/io.R: a renamed/missing individual-covariate, group or age column was silently dropped (check_data reported nothing and the fit failed later); now read_data stops and lists the available columns. A group value not in group_levels (e.g. "f" vs "F") was silently turned into NA and reported as "missing"; now it names the bad value and the allowed levels. Missing date_col/occasion_col/occasion_cov_col messages list the available columns. Blank ids in the captures file are reported with row numbers. check_data now checks that every formula variable exists (a misspelled covariate in the formula was not caught before fitting).
  • R/report.R: when survival varies over time only through occasion covariates (no time, Time or (1 | time)), the report said "Survival varied between intervals" and the survival_time caption implied year-specific estimates. Both now say the values are predicted from the named covariate only (no separate year effect).

Additional limit noted. With tsm and an unequal first interval (dt != 1), the first-interval survival is modelled as (phi_first)^dt, so proportion_transient is per unit time, not per release; not validated (the transient runs above use equal intervals).

Verdict: VERIFIED_WITH_LIMITS. Passes at the default design and at an independent design with unequal intervals on both engines; controls fail as they should and the FPR is about 0.05; the documented limits (Bayesian bias at low-information designs, random-year SD bias) stand.

Engine, references and full details

Engine: marked (maximum likelihood, default) or nimble + nimbleEcology (Bayesian; marginal dCJS likelihood or latent alive-state formulation); R2ucare for goodness of fit.

References

  • Cormack RM (1964) Biometrika 51:429-438. Jolly GM (1965) Biometrika 52:225-247. Seber GAF (1965) Biometrika 52:249-259.
  • Lebreton J-D, Burnham KP, Clobert J, Anderson DR (1992) Modeling survival and testing biological hypotheses using marked animals. Ecological Monographs 62:67-118. doi:10.2307/2937171
  • Pradel R (1993) Flexibility in survival analysis from recapture data: handling trap-dependence. In: Marked Individuals in the Study of Bird Population, Birkhauser, 29-37.
  • Pradel R, Hines JE, Lebreton J-D, Nichols JD (1997) Capture-recapture survival models taking account of transients. Biometrics 53:60-72. doi:10.2307/2533097
  • Pradel R, Gimenez O, Lebreton J-D (2005) Principles and interest of GOF tests for multistate capture-recapture models. Animal Biodiversity and Conservation 28:189-204.
  • Gimenez O, Lebreton J-D, Choquet R, Pradel R (2018) R2ucare: An R package to perform goodness-of-fit tests for capture-recapture models. Methods in Ecology and Evolution 9:1749-1754. doi:10.1111/2041-210X.13014
  • Choquet R, Lebreton J-D, Gimenez O, Reboulet A-M, Pradel R (2009) U-CARE: Utilities for performing goodness of fit tests and manipulating CApture-REcapture data. Ecography 32:1071-1074. doi:10.1111/j.1600-0587.2009.05968.x
  • Meredith M (2024) wiqid: Quick and Dirty Estimates for Wildlife Populations, R package (survCJS; used only for the cross-check in VALIDATION.md).
  • Laake JL, Johnson DS, Conn PB (2013) marked: an R package for maximum likelihood and MCMC analysis of capture-recapture data. Methods in Ecology and Evolution 4:885-890. doi:10.1111/2041-210X.12065
  • Gimenez O et al. (2007) State-space modelling of data on marked individuals. Ecological Modelling 206:431-438. doi:10.1016/j.ecolmodel.2007.03.040
  • Kery M, Schaub M (2012) Bayesian Population Analysis using WinBUGS, ch. 7. Academic Press.
  • Schaub M, Kery M (2022) Integrated Population Models, ch. 3. Academic Press.
  • Turek D, de Valpine P, Paciorek CJ (2016) Efficient MCMC sampling for hierarchical hidden Markov models. Environmental and Ecological Statistics 23:549-564. doi:10.1007/s10651-016-0353-z
  • Goldstein BR, Turek D, Ponisio L, de Valpine P (2024) nimbleEcology R package v0.5.0.
  • de Valpine P et al. (2017) NIMBLE. Journal of Computational and Graphical Statistics 26:403-413. doi:10.1080/10618600.2016.1172487
  • Burnham KP, Anderson DR (2002) Model Selection and Multimodel Inference, 2nd ed. Springer.

CascadiaTools is made by Taal Levi, Cascadia Wildlife Lab, Oregon State University. Source code on GitHub · How to cite

Based at
Oregon State University