This vignette explains the main exported functions in
StablePopulation from both a computational and a
demographic perspective. It is intended as a companion to the workflow
vignette. The workflow vignette shows one complete analysis from start
to finish; this guide explains what each function is for, what question
it answers, and what should not be inferred from its output.
The common demographic rule behind the constrained functions is
\[ R_0 = \sum_x l_x m_x = 1, \]
where lx is survivorship to age class x and
mx is age-specific fertility. All functions use consecutive
internal class indices 0, 1, 2, ..., n - 1.
We use the same small artificial example throughout.
age <- 0:5
mx <- c(0, 0, 0.30, 0.75, 0.60, 0.20)
lx_observed <- c(1.0000000, 0.8480619, 0.7023945, 0.5759026, 0.4689639, 0.3798816)
example_data <- data.frame(age = age, mx = mx, lx_observed = lx_observed)
knitr::kable(example_data, digits = 4)| age | mx | lx_observed |
|---|---|---|
| 0 | 0.00 | 1.0000 |
| 1 | 0.00 | 0.8481 |
| 2 | 0.30 | 0.7024 |
| 3 | 0.75 | 0.5759 |
| 4 | 0.60 | 0.4690 |
| 5 | 0.20 | 0.3799 |
weibull_survival()For a given pair of Weibull parameters, what survivorship value is predicted at each age class?
weibull_survival() is the low-level mathematical
function used by the higher level reconstruction routes. It
evaluates
\[ l_x = \exp\left[-\left(\frac{x}{\alpha}\right)^\beta\right]. \]
lx_candidate <- weibull_survival(
alpha = 4.5,
beta = 1.2,
age = age
)
knitr::kable(
data.frame(age = age, lx_candidate = lx_candidate),
digits = 4
)| age | lx_candidate |
|---|---|
| 0 | 1.0000 |
| 1 | 0.8483 |
| 2 | 0.6853 |
| 3 | 0.5408 |
| 4 | 0.4197 |
| 5 | 0.3215 |
This function does not use fertility and does not impose
R0 = 1. It simply returns a survivorship curve for supplied
values of alpha and beta.
calculate_population()Given alpha, beta, and fertility, what
survivorship profile and total births are implied?
This historical helper returns the Weibull survivorship vector under
the name population and the resulting sum
births = sum(population * fertility_rates).
The returned population is cumulative survivorship
lx, not the normalized stable age structure R.
Use derive_demographic_profile() when R,
D, and B are needed.
find_alphas()For one selected beta, which alpha makes
the fertility schedule satisfy sum(lx * mx) = 1?
This is a root-finding step. It is useful for understanding the
historical algorithm, but ordinary analyses should usually use
reconstruct_population(), scan_beta(), or
select_beta(), because those functions also return clearer
diagnostics and output tables.
reconstruct_population()If beta is fixed in advance, what constrained Weibull
profile is obtained?
reconstruction <- reconstruct_population(
fertility_rates = mx,
beta = 1.20
)
reconstruction
#> Stable Population Reconstruction (Weibull, R0 = 1)
#> ---------------------------------------------------
#> alpha: 4.97151
#> beta: 1.2
#> R0: 1
#> Stable: TRUE
#> Age classes: 6
#>
#> Use $table for the full age-specific profile.
knitr::kable(reconstruction$table, digits = 4)| age | fertility_rates | lx | lxmx |
|---|---|---|---|
| 0 | 0.00 | 1.0000 | 0.0000 |
| 1 | 0.00 | 0.8642 | 0.0000 |
| 2 | 0.30 | 0.7151 | 0.2145 |
| 3 | 0.75 | 0.5796 | 0.4347 |
| 4 | 0.60 | 0.4629 | 0.2777 |
| 5 | 0.20 | 0.3654 | 0.0731 |
This is the fixed-beta route. It should be used when
beta is already justified by an external argument or when a
particular value is being examined as an explicit scenario. The function
solves alpha under R0 = 1 and returns the
reconstructed lx.
scan_beta()Which constrained candidate profiles are possible across a grid of
beta values?
scan <- scan_beta(
fertility_rates = mx,
beta_values = seq(0.50, 1.80, by = 0.10)
)
scan
#> Stable Population Scan (Weibull, R0 = 1)
#> -----------------------------------------
#> Beta values scanned: 14
#> Numerically stable: 14
#> Admissible: 14
#> Age classes: 6
#>
#> Use $summary for all candidates, $admissible_summary for retained ones.
knitr::kable(head(scan$summary), digits = 4)| beta | alpha | R0 | residual | lx_terminal | stable | terminal_admissible | admissible | status |
|---|---|---|---|---|---|---|---|---|
| 0.5 | 8.6742 | 1 | 0 | 0.4680 | TRUE | NA | TRUE | converged |
| 0.6 | 7.3882 | 1 | 0 | 0.4533 | TRUE | NA | TRUE | converged |
| 0.7 | 6.5908 | 1 | 0 | 0.4386 | TRUE | NA | TRUE | converged |
| 0.8 | 6.0520 | 1 | 0 | 0.4239 | TRUE | NA | TRUE | converged |
| 0.9 | 5.6653 | 1 | 0 | 0.4092 | TRUE | NA | TRUE | converged |
| 1.0 | 5.3754 | 1 | 0 | 0.3945 | TRUE | NA | TRUE | converged |
scan_beta() is the scenario route. It does not select a
single empirical optimum. It returns a family of profiles, all solved
under R0 = 1 when the solver converges.
A terminal survivorship window can be used when the analysis is framed around a final-class criterion.
terminal_scan <- scan_beta(
fertility_rates = mx,
beta_values = seq(0.50, 1.80, by = 0.10),
terminal_window = c(0.10, 0.40)
)
knitr::kable(terminal_scan$admissible_summary, digits = 4)| beta | alpha | R0 | residual | lx_terminal | stable | terminal_admissible | admissible | status | |
|---|---|---|---|---|---|---|---|---|---|
| 6 | 1.0 | 5.3754 | 1 | 0 | 0.3945 | TRUE | TRUE | TRUE | converged |
| 7 | 1.1 | 5.1505 | 1 | 0 | 0.3799 | TRUE | TRUE | TRUE | converged |
| 8 | 1.2 | 4.9715 | 1 | 0 | 0.3654 | TRUE | TRUE | TRUE | converged |
| 9 | 1.3 | 4.8259 | 1 | 0 | 0.3509 | TRUE | TRUE | TRUE | converged |
| 10 | 1.4 | 4.7053 | 1 | 0 | 0.3366 | TRUE | TRUE | TRUE | converged |
| 11 | 1.5 | 4.6040 | 1 | 0 | 0.3225 | TRUE | TRUE | TRUE | converged |
| 12 | 1.6 | 4.5179 | 1 | 0 | 0.3085 | TRUE | TRUE | TRUE | converged |
| 13 | 1.7 | 4.4439 | 1 | 0 | 0.2947 | TRUE | TRUE | TRUE | converged |
| 14 | 1.8 | 4.3796 | 1 | 0 | 0.2810 | TRUE | TRUE | TRUE | converged |
The terminal window filters scenarios. It does not estimate a true beta and it does not create a central result.
select_beta()When observed survivorship is available, which constrained candidate profile is closest to it?
selection <- select_beta(
fertility_rates = mx,
lx_observed = lx_observed,
beta_values = seq(0.50, 1.80, by = 0.05)
)
selection
#> Stable Population Selection (Weibull, R0 = 1, best RMSE)
#> ---------------------------------------------------------
#> Best beta: 1.1
#> Best alpha: 5.15055
#> R0: 1
#> RMSE: 2.902e-08
#> Age classes: 6
#> Candidates evaluated: 27
#>
#> Use $best_profile for the selected profile, $results for all candidates.
selection$best_beta
#> [1] 1.1
selection$best_alpha
#> [1] 5.150545
selection$best_RMSE
#> [1] 2.901736e-08| age | fertility_rates | lx_observed | lx_reconstructed | residual | squared_error | lxmx |
|---|---|---|---|---|---|---|
| 0 | 0.00 | 1.0000 | 1.0000 | 0 | 0 | 0.0000 |
| 1 | 0.00 | 0.8481 | 0.8481 | 0 | 0 | 0.0000 |
| 2 | 0.30 | 0.7024 | 0.7024 | 0 | 0 | 0.2107 |
| 3 | 0.75 | 0.5759 | 0.5759 | 0 | 0 | 0.4319 |
| 4 | 0.60 | 0.4690 | 0.4690 | 0 | 0 | 0.2814 |
| 5 | 0.20 | 0.3799 | 0.3799 | 0 | 0 | 0.0760 |
select_beta() scans a grid of beta values. For each
beta, alpha is derived from the stability condition. The selected
profile is the grid candidate with the lowest RMSE against
lx_observed. This is therefore not a free two-parameter
Weibull fit.
If the selected beta is exactly at the lower or upper boundary of the scanned range, the result should be treated as a diagnostic cue to extend the beta grid before interpreting the selected value.
normalize_fertility()How should a fertility schedule be rescaled if the analysis is
deliberately expressed relative to R0 = 1 for a reference
survivorship profile?
normalised <- normalize_fertility(
fertility_rates = mx,
lx_reference = lx_observed
)
normalised
#> Normalized Fertility Schedule
#> -----------------------------
#> R0 (reference): 1
#> Scaling factor: 1
#> Check R0 (after normalization): 1
#> Age classes: 6
#>
#> Use $fertility_rates_normalized for the adjusted schedule.Normalisation is explicit. It multiplies every fertility value by the
same factor and preserves the age pattern of fertility. Neither
select_beta() nor run_reconstruction_excel()
normalises fertility silently.
derive_demographic_profile()Once lx has been reconstructed, what stable age
structure and mortality profiles follow from it?
demographic_profile <- derive_demographic_profile(
lx = selection$best_lx,
fertility_rates = mx
)
demographic_profile
#> Stable Population Demographic Profile
#> --------------------------------------
#> Age classes: 6
#> R0: 1
#> Stable R0: TRUE
#> sum(R): 1
#> sum(D): 0.25155938 (expected: 0.25155938 )
#> sum(D_relative): 1
#>
#> Use $table for the full profile with R, D, D_relative and B.
knitr::kable(demographic_profile$table, digits = 4)| age | fertility_rates | lx | stable_structure | mortality_profile | mortality_profile_relative | conditional_survival | lxmx |
|---|---|---|---|---|---|---|---|
| 0 | 0.00 | 1.0000 | 0.2516 | 0.0382 | 0.1519 | 0.8481 | 0.0000 |
| 1 | 0.00 | 0.8481 | 0.2133 | 0.0366 | 0.1457 | 0.8282 | 0.0000 |
| 2 | 0.30 | 0.7024 | 0.1767 | 0.0318 | 0.1265 | 0.8199 | 0.2107 |
| 3 | 0.75 | 0.5759 | 0.1449 | 0.0269 | 0.1069 | 0.8143 | 0.4319 |
| 4 | 0.60 | 0.4690 | 0.1180 | 0.0224 | 0.0891 | 0.8100 | 0.2814 |
| 5 | 0.20 | 0.3799 | 0.0956 | 0.0956 | 0.3799 | NA | 0.0760 |
This function creates the demographic bridge from survivorship to downstream eco-demographic quantities:
R or stable_structure: normalized stable
age structure;D or mortality_profile: raw exit-by-death
profile;D_relative: separately normalized death profile;B or conditional_survival: survival
between consecutive classes.The raw D and the relative D are not the
same object. This distinction is important when later ecological layers
combine mortality with body mass or species abundance.
fit_weibull_free()What two-parameter Weibull curve best describes the observed survivorship, without imposing the stable-population constraint?
free_fit <- fit_weibull_free(
lx_observed = lx_observed,
fertility_rates = mx
)
free_fit
#> Free Weibull Fit (unconstrained, descriptive reference)
#> -------------------------------------------------------
#> alpha: 5.15055
#> beta: 1.1
#> RMSE: 8.666e-08
#> Converged: TRUE
#> R0 (implied): 1
#> Optimization starts: 12 ( 12 converged )
#>
#> Use $profile for the fitted profile, $attempts for all starts.
free_fit$R0_fitted
#> [1] 1This is a descriptive reference fit. Both alpha and
beta are estimated freely. If fertility is supplied, the
implied R0 is reported after the fit. It should not be
confused with the constrained route used by
select_beta().
run_reconstruction_excel() and
run_analysis()How can the same reconstruction routes be applied to Excel workbooks?
run_reconstruction_excel() is the recommended
external-workbook interface. In mode = "auto", each sheet
is routed according to the available columns:
| Input columns | Route used |
|---|---|
| fertility plus observed survivorship | select |
| fertility plus one fixed beta value | fixed |
| fertility only | scan |
run_analysis() is the historical source-tree workflow
retained for compatibility with version 1.0.3. New analyses should
normally use run_reconstruction_excel().
| Scientific situation | Recommended function |
|---|---|
| One beta is already justified | reconstruct_population() |
| Observed survivorship is available | select_beta() |
| Only fertility is available | scan_beta() |
| Fertility must be explicitly rescaled | normalize_fertility() first |
| A fitted curve is needed only as a descriptive reference | fit_weibull_free() |
| Stable structure and death profiles are needed | derive_demographic_profile() |
| Data are stored in Excel sheets | run_reconstruction_excel() |