Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions R/apc-model.R
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ build_initiation_data <- function(data, cfg, variable_details_sheet) {
never_code <- survey_code(cfg, "smoking_status", "never_code")
floor_age <- initiation_floor(cfg)
cohort_min <- cfg$apc$cohort_min
period_min <- cfg$apc$period_min
period_min <- apc_period_min(cfg)
period_max <- cfg$apc$period_max

data <- data[!is.na(data$cohort) & data$cohort >= cohort_min, ]
Expand Down Expand Up @@ -192,6 +192,16 @@ build_initiation_data <- function(data, cfg, variable_details_sheet) {
)
period_range <- seq(period_min, period_max)
denominator <- expand_denominator(denom_source, period_range, floor_age)
# Task 1.2 invariant: every event lies inside the estimation window, so its cell has
# person-years at risk (initiators contribute their own risk years from the floor).
outside <- numerator$period < period_min | numerator$period > period_max
if (any(outside)) {
stop(
sum(outside), " initiation event(s) fall outside the estimation window [",
period_min, ", ", period_max, "] (period range ", min(numerator$period), "-",
max(numerator$period), "). Check cohort_min, initiation_floor_age and period_max."
)
}

out <- rbind(numerator, denominator)
attr(out, "initiation_diagnostics") <- diag
Expand Down Expand Up @@ -358,7 +368,7 @@ build_cessation_data <- function(data, cfg, variable_details_sheet) {
durability <- cfg$apc$cessation_durability_years
if (is.null(durability)) stop("cfg$apc$cessation_durability_years is not set.")
cohort_min <- cfg$apc$cohort_min
period_min <- cfg$apc$period_min
period_min <- as.integer(cohort_min) # cessation: bounded by entry age and cohort_min only
period_max <- cfg$apc$period_max

data <- data[!is.na(data$cohort) & data$cohort >= cohort_min, ]
Expand Down
20 changes: 20 additions & 0 deletions R/config-utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,23 @@ survey_bound <- function(cfg, key, bound) {
if (is.null(val)) stop("survey_bound: no '", bound, "' for key '", key, "' source '", src, "'")
val
}

#' First calendar year of the APC estimation window
#'
#' The window starts when the earliest cohort (`cfg$apc$cohort_min`) reaches the
#' initiation floor (`initiation_floor(cfg)`), so every initiation event has person-years
#' at risk in the same age-period-cohort cells (remediation task 1.2). The reporting
#' start for rate tables (`cfg$apc$report_period_min`, 1965) is a separate, later year.
#'
#' @param cfg Config list
#' @return Integer calendar year
apc_period_min <- function(cfg) {
if (!is.null(cfg$apc$period_min)) {
stop(
"cfg$apc$period_min is set (", cfg$apc$period_min, "). The estimation window start is ",
"derived as cohort_min + initiation_floor_age; remove the key (see config.yml)."
)
}
if (is.null(cfg$apc$cohort_min)) stop("cfg$apc$cohort_min is not set.")
as.integer(cfg$apc$cohort_min + initiation_floor(cfg))
}
14 changes: 13 additions & 1 deletion config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,19 @@ default:
# Period and cohort range for denominator construction
# period_max is the last observed CCHS cycle year (PUMF: 2022; Master: 2023 via statscan profile)
# projection_max is the final projection year for rate tables and smoking histories
period_min: 1965
# period_min is not set here: the estimation window starts in the calendar year the
# earliest cohort (cohort_min) reaches the initiation floor, apc_period_min(cfg) =
# cohort_min + initiation_floor_age (PUMF 1933, Master 1928). Setting it later than
# that (the former 1965) left initiation events before 1965 with no person-years at
# risk in the same cells (task 1.2). Rate tables and reported histories start at
# report_period_min.
report_period_min: 1965
# Reporting limits for rate tables and histories (protocol v0.4.2, section 3.4.1):
# cessation rates are reported from age 15 for comparison with Manuel et al. (2020),
# although cessation risk is estimated from each person's own entry age; rates for ages
# above max_age are held at the max_age values (Holford et al. 2014). Stage 9 consumes these.
report_cessation_min_age: 15
max_age: 85
period_max: 2022 # statscan profile overrides to 2023
projection_max: 2050 # rate tables and smoking histories projected to this year
cohort_min: 1920
Expand Down
58 changes: 58 additions & 0 deletions docs/development/age-range-conventions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Age and duration ranges: what the protocol says, what the references used, what the worksheet gives

Working note, 2026-08-28. Written so the protocol can be made explicit about minimum and maximum ages for initiation and cessation, and so the cchsflow variable-details rules can be checked against those conventions. Update, later the same day: protocol v0.4.2 (section 3.4.1, PR #9) resolves the open items in section 3 as follows: cessation risk stays person-specific and is reported from age 15; the PUMF initiation floor of 13 is stated as a data limit against the reference value of 8; the maximum modelled age is 85 with rates held at the age-85 values above it; reported-value ranges are stated to come from the cchsflow recoding rules. `apc.report_cessation_min_age` and `apc.max_age` were added to `config.yml` for Stage 9. Items 4 (maximum initiation age: left to the data) and 5 (quit-duration grouping: cchsflow fix, task 1.10) remain.

## 1. Where the numbers come from today

Three different things set an age or duration limit in the pipeline, and they are not the same:

| Kind | Where it lives | Examples |
|---|---|---|
| **Analysis decision** | `config.yml` under `apc:` | `initiation_floor_age` (PUMF 13, Master 8); `cessation_durability_years` (2); `cohort_min` (1920); estimation window start derived as `cohort_min + initiation_floor_age` (task 1.2) |
| **Variable range** (what a reported value can be) | derived from the variable-details worksheet by `details_range()` / `survey_range()`; never in config | age at first cigarette 8–51 (PUMF midpoints); years since quitting 0.5–5 (PUMF 2003+) |
| **Reference-study convention** | the protocol (should be), citing Holford and Manuel | initiation zero before 8; cessation zero before 15; age 85 ceiling |

The second row was settled on 2026-08-27 (Sol round 1 on PR #7, PI decision): the worksheet is the reference for min and max; config points at it (`range: variable_details`); a non-missing value with no worksheet range stops the pipeline.

## 2. What the reference studies used

| Convention | Holford et al. 2014 (AJPM; NCI SHG) | Manuel et al. 2020 (Health Reports; Ontario CCHS) | SAS reference (Modeling2013.sas) |
|---|---|---|---|
| Initiation probability zero before | age 8 | age 8 | `if age lt 8 ... delete` (initiation risk set) |
| Cessation probability zero before | age 15 | age 15 | `if age lt 15 ... delete` (cessation risk set); `if age ge 15` |
| No person-year after | survey age | survey date | `age gt surveyage then delete` |
| Upper age | NHIS ages 18–84; cessation probability held at the age-85 level above 85; histories reported to age 99 | CCHS aged 12+ | — |
| Durable cessation | quit at least 2 years before interview; otherwise observation truncated at the quit age | same rule (Holford lineage) | — |
| Missing age sentinel | — | — | 101 |
| Cohorts | 1890–2035 estimated; before-1920 intensity constrained to 1920 | 1920s cohort earliest reported | — |

Meza et al. 2023 (AJPM, race/ethnicity SHG) follows the same Holford construction; its stated limits were not extracted in this pass (the full text is in Zotero, item 9LGL8F9F) — check before citing.

## 3. Where the current protocol and code differ from those conventions

1. **Cessation floor.** Holford and Manuel set cessation probability to zero before age 15; the SAS reference drops cessation person-years before 15. The current code (task 1.3) starts each person's cessation risk at their own age at first cigarette with **no fixed minimum**, and the protocol (v0.4.1 §3.4.1) says so. This is a deliberate departure argued in the adjudication (a floor cannot replace person-specific entry), but the protocol does not say that it departs from the reference studies or why. **Open:** keep person-specific entry (and say so), or add a 15-year reporting floor for comparability with Manuel 2020, or both (estimate person-specific; report from 15).
2. **Initiation floor.** Holford/Manuel: 8. Current: 8 in Master, 13 in PUMF (issue #5 decision; the PUMF 5–11 category is too coarse). Protocol §3.3 records this. Consistent, but the protocol should state the reference value (8) and that 13 is a PUMF data limit rather than a methodological choice.
3. **Upper age.** No protocol statement. The PUMF age variable is grouped and capped (`DHHGAGE_cont` 13–85 for 2001–2018; 14.5–75 for 2019–20 and 2022, where the top category is 65+ with midpoint 75). Holford held cessation at the age-85 level beyond 85 and reported to 99. **Open:** state the maximum modelled age (85 seems the natural choice given the CCHS top code) and how rates above it are carried.
4. **Maximum age at initiation / duration of smoking.** Reference studies have no explicit maximum; "few initiate after 30" is an observation rather than a rule. The worksheet gives 8–51 for age at first cigarette in the PUMF (grouped midpoints; the top group is 50+ → 51) — a reporting range rather than a limit. **Open:** whether the protocol should state a maximum initiation age or leave it to the data.
5. **Quit duration.** PUMF top group "3 or more years" (midpoint 5) for 2003–2014 makes long-term former smokers quit too late; refinable with SMKG09C (2007–14) and SMKDGSTP (2015–22) — see `phase1-1.9-1.10-investigation.md`. Holford's "at least 2 years" durability rule is implementable in every cycle since the 2–3 / 3+ boundary exists.
6. **The 2019–20 and 2022 PUMF age variable** (five broad groups) cannot place a person within a year of age; the APC model is limited to 2001–2018 for age-specific estimation (`project_age_variable_constraints` memory). The protocol should say this.

## 4. What the worksheet currently derives (PUMF, `details_range()`)

| Variable | 2001 | 2003–2014 | 2015–16 | 2017–18 | 2019–20 | 2022 |
|---|---|---|---|---|---|---|
| `age_first_cigarette` | 8–51 | 8–51 | 8–47 | 8–51 | 8–51 | none (no rule) |
| `age_start_smoking` | 8–51 | 8–51 | 8–51 | 8–51 | none | none |
| `time_quit_smoking_complete` | none (not asked) | 0.5–5 | 0.5–5 | 0.5–5 | 0.5–5 | none |
| `time_quit_smoking_daily` | 0.5–15 | 0.5–5 | 0.5–5 | 0.5–5 | 0.5–5 | none |
| `DHHGAGE_cont` | 13–85 | 13–85 | 13–85 | 13–85 | 14.5–75 | 14.5–75 |

Two rows point at worksheet gaps rather than data facts: `age_first_cigarette` has no `cchs2022_p` rule although its feeder `SMKG01C_cont` does, and 2015–16 tops out at 47 (a different midpoint set) — both worth a cchsflow issue.

## 5. Suggested protocol text (for discussion, not applied)

A short "Age and time ranges" paragraph in §3.4.1, in this order: (a) reference conventions (Holford 2014; Manuel 2020): initiation from 8, cessation from 15, histories to 85 (Holford to 99), durable cessation two years; (b) what this study does and where it differs (person-specific cessation entry; PUMF initiation floor 13 as a data limit; maximum modelled age 85 with rates carried forward); (c) that reported-value ranges come from the CCHS recoding rules in cchsflow (variable details), are checked per cycle, and are not analysis limits.

## 6. Status of the PI's protocol edits (2026-08-28)

At 09:31 the protocol QMD was saved in the working copy on branch `fix/phase1-1.2-initiation-window`, but `git status` shows no change: either the edits are not yet saved in Positron, or they were made to the rendered Word file (`docs/protocol/output/full-protocol.docx`, rendered 09:25). Check before editing the QMD.
Loading
Loading