--- title: "From fertility data to a stable survivorship profile" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{From fertility data to a stable survivorship profile} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 4.5 ) ``` ```{r setup} library(StablePopulation) ``` ## Purpose of this workflow `StablePopulation` reconstructs age-specific survivorship profiles under a stable and stationary population assumption. The central demographic condition is \[ R_0 = \sum_x l_x m_x = 1, \] where \(l_x\) is survivorship to age class \(x\) and \(m_x\) is age-specific fertility. The package uses consecutive internal class indices `0, 1, 2, ..., n - 1`. An external age column can be kept as a label in Excel workflows, but it does not redefine the internal time scale. This vignette gives a complete minimal workflow: 1. start from a fertility schedule and an observed survivorship profile; 2. calculate the reference net replacement value; 3. select a constrained Weibull candidate using `select_beta()`; 4. derive the demographic quantities `R`, `D`, `D_relative`, and `B`; 5. compare the constrained route with a free Weibull reference fit; 6. show the scenario route used when observed survivorship is unavailable; 7. indicate how the same logic is used in an Excel workbook. The data below are deliberately small and artificial. They are intended for reproducibility and for understanding the workflow, not as a biological case study. ## Input data A minimal analysis needs an age-specific fertility schedule. If observed survivorship is available, it can be used to select the best constrained candidate among a grid of Weibull shape values. ```{r input-data} 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 ) input_data <- data.frame( age = age, mx = mx, lx_observed = lx_observed ) knitr::kable(input_data, digits = 4) ``` The observed net replacement value for these data is: ```{r observed-r0} R0_observed <- sum(lx_observed * mx) R0_observed ``` In this toy example, the observed schedule is already very close to the stable and stationary condition. In empirical applications, the observed value may be different from one. `StablePopulation` never rescales fertility silently; if normalisation is a methodological decision, it should be done explicitly with `normalize_fertility()` before the reconstruction. ```{r explicit-normalisation} normalised <- normalize_fertility( fertility_rates = mx, lx_reference = lx_observed ) normalised$R0_reference normalised$check_R0 ``` The normalised fertility vector is then available as `normalised$fertility_rates_normalized`. In the rest of this vignette we keep the original `mx`, because the toy data already satisfy the reference condition. ## Route with observed survivorship: `select_beta()` When both fertility and observed survivorship are available, use `select_beta()`. For each candidate `beta`, the package solves the corresponding Weibull scale parameter `alpha` under \(R_0 = 1\), reconstructs \(l_x\), and calculates the RMSE against the observed profile. ```{r select-beta} selection <- select_beta( fertility_rates = mx, lx_observed = lx_observed, beta_values = seq(0.50, 1.80, by = 0.05) ) selection ``` The selected constrained candidate is stored in the main elements of the result: ```{r selected-values} selection$best_beta selection$best_alpha selection$best_RMSE ``` The age-by-age comparison between the observed and reconstructed profiles is in `best_profile`. ```{r selected-profile} knitr::kable(selection$best_profile, digits = 4) ``` The complete grid is retained in `results`, so the selected value can be checked against the neighbouring candidates. ```{r candidate-grid} selected_neighbourhood <- selection$results[ selection$results$beta >= selection$best_beta - 0.10 & selection$results$beta <= selection$best_beta + 0.10, c("beta", "alpha", "R0", "RMSE", "selected") ] knitr::kable(selected_neighbourhood, digits = 6) ``` If the selected `beta` is the first or last value in the scanned range, `select_beta()` issues a warning and records a boundary note in the returned object. This is a diagnostic cue: it does not mean that the result is invalid, but it does mean that the scanned range should be extended deliberately before interpreting the selected value as an internal optimum. ```{r boundary-fields} selection$beta_at_boundary selection$beta_boundary_note ``` ## Derived demographic profile The reconstructed survivorship profile can be converted into the demographic quantities used by downstream population and ecological interpretation. ```{r derived-profile} demographic_profile <- derive_demographic_profile( lx = selection$best_lx, fertility_rates = mx ) demographic_profile ``` The resulting table contains: - `lx`: survivorship; - `stable_structure`: the normalised stable age structure \(R_x\); - `mortality_profile`: the raw exit-by-death profile \(D_x\); - `mortality_profile_relative`: the separately normalised version of `D`; - `conditional_survival`: survival between consecutive classes \(B_x\); - `lxmx`: the contribution of each class to \(R_0\). ```{r derived-table} knitr::kable(demographic_profile$table, digits = 4) ``` These checks are useful when explaining the output: ```{r derived-checks} demographic_profile$checks ``` `stable_structure` sums to one. `mortality_profile_relative` also sums to one, because it is explicitly normalised. The raw `mortality_profile` is different: its total equals the first value of the stable age structure. ## Free Weibull reference fit `fit_weibull_free()` provides a descriptive two-parameter Weibull fit to the observed survivorship profile. It does not impose \(R_0 = 1\). If fertility is supplied, the implied \(R_0\) is reported after fitting. ```{r free-fit} free_fit <- fit_weibull_free( lx_observed = lx_observed, fertility_rates = mx ) free_fit ``` The comparison is useful, but it answers a different question. `select_beta()` selects among constrained candidates compatible with the stable-population condition; `fit_weibull_free()` estimates both Weibull parameters freely and only then reports the demographic consequence. ```{r free-fit-profile} knitr::kable(free_fit$profile, digits = 4) ``` ## Route without observed survivorship: `scan_beta()` When observed survivorship is unavailable, the package should not pretend that a single empirical optimum has been selected. In that case, use `scan_beta()` to build a family of constrained candidate profiles. A terminal-survivorship window can be used to retain scenarios whose final class remains within a stated interval. ```{r scan-beta} scan <- scan_beta( fertility_rates = mx, beta_values = seq(0.50, 1.80, by = 0.05), terminal_window = c(0.35, 0.39) ) scan ``` The retained candidates are stored in `admissible_summary`. ```{r scan-summary} knitr::kable(scan$admissible_summary, digits = 6) ``` When a terminal window is active, `terminal_extremes$first` and `terminal_extremes$last` are the first and last admissible candidates in ascending beta order. They are scenario endpoints, not automatically minima or maxima of all downstream quantities. ```{r terminal-extremes} scan$terminal_extremes$first$beta scan$terminal_extremes$last$beta ``` ## Fixed-beta reconstruction If a value of `beta` has already been justified externally, the direct route is `reconstruct_population()`. ```{r fixed-beta} fixed <- reconstruct_population( fertility_rates = mx, beta = 1.10 ) fixed knitr::kable(fixed$table, digits = 4) ``` This route solves `alpha` under \(R_0 = 1\) for the chosen `beta`. It is useful for sensitivity analysis, for reproducing a predefined scenario, or for using a shape value selected outside the current dataset. ## Excel workflow The same reconstruction logic can be applied to external workbooks with `run_reconstruction_excel()`. A typical input sheet contains columns such as: ```{r excel-columns, echo = FALSE} excel_template <- data.frame( Age = age, mx = mx, lx = lx_observed, Beta = c(1.10, rep(NA, length(age) - 1L)) ) knitr::kable(excel_template, digits = 4) ``` The function can then be called as: ```{r excel-call, eval = FALSE} run_reconstruction_excel( input_file = "demography.xlsx", output_file = "demography_results.xlsx", mode = "auto", beta_values = seq(0.05, 3.00, by = 0.05), terminal_window = c(0.0001, 0.05), output_detail = "standard" ) ``` In `mode = "auto"`, the route is inferred from the available columns. For example, observed survivorship supports the selection route, while a single fixed value in a `Beta` column supports the fixed-beta route when observed survivorship is absent. The Excel workflow uses the fertility values supplied in the workbook; explicit normalisation should therefore be done before saving the input if the analysis is intended to use a rescaled fertility schedule. ## Summary The package separates three analytical situations: | Situation | Function | Interpretation | |---|---|---| | Fertility plus observed survivorship | `select_beta()` | Selects the constrained candidate with the smallest RMSE. | | Fertility without observed survivorship | `scan_beta()` | Retains a family of constrained scenarios. | | A fixed beta is already justified | `reconstruct_population()` | Reconstructs one constrained profile for that beta. | `fit_weibull_free()` is useful as a descriptive reference, but it is not the same model as the constrained StablePopulation reconstruction. The distinction is important when reporting methods, comparing results, or linking the package to downstream ecological analyses.