The problem preening() solves

Age categorisation is one of the most routine and most error-prone steps in surveillance analysis. The same dataset might need ABS 5-year bands for a national comparison, ATAGI program bands for a vaccine effectiveness study, and FluCAN bands for a sentinel surveillance report — all in the same week. preening() provides a single function backed by a catalogue of ~50 named, citable schemes so that band choice is explicit, reproducible, and traceable to a published source.

The name comes from the way a bird re-sorts its feathers into whichever functional arrangement suits the moment, without changing anything about the bird itself. The same raw age values are re-sorted into whichever standard grouping the analysis calls for.


Three ways to specify a scheme

1. Exact name

The clearest approach — name the scheme directly.

set.seed(1)
df <- data.frame(age = c(0.2, 3, 14, 25, 50, 67, 80, 92))

preening(df, age_col = "age", scheme = "atagi_covid19_2025")$age_group
#> [1] 0-<5  0-<5  5-<18 18-64 18-64 65-74 75+   75+  
#> Levels: 0-<5 < 5-<18 < 18-64 < 65-74 < 75+

2. Filter to a single match

Supply family, focus, and/or max_bands — if exactly one scheme matches, it is applied automatically and a message names it so the choice is never silent.

# vaccination + paediatric + max 3 bands → exactly one match
preening(df, age_col = "age", family = "vaccination",
         focus = "paediatric", max_bands = 3)$age_group
#> [1] 0-<6m 2y+   2y+   2y+   2y+   2y+   2y+   2y+  
#> Levels: 0-<6m < 6m-<2y < 2y+

3. Let list_age_schemes() guide you

Browse the catalogue before committing to a scheme.

list_age_schemes(family = "surveillance")
#>                     scheme       family                                 focus
#>               ed_syndromic surveillance                    surveillance|broad
#>            flucan_sentinel surveillance        surveillance|broad|national_au
#>  hospital_admitted_patient surveillance                          surveillance
#>              nndss_decadal surveillance              surveillance|national_au
#>             nndss_standard surveillance surveillance|fine_grained|national_au
#>              nors_outbreak surveillance                    surveillance|broad
#>         notifiable_std_bbv surveillance             surveillance|fine_grained
#>             racf_aged_care surveillance                surveillance|aged_care
#>  n_bands age_range
#>        6        0+
#>        5        0+
#>        6        0+
#>        9        0+
#>       10        0+
#>        5        0+
#>        6        0+
#>        5        0+

When a filter matches multiple schemes, preening() stops and lists them so you can pick one explicitly. This is intentional — preening() never guesses among ties.

# Multiple paediatric schemes exist — preening() asks you to choose
preening(df, age_col = "age", focus = "paediatric")
#> Error:
#> ! (*)> mudnester::preening() — 10 age schemes match the supplied filters (family = NULL, focus = c("paediatric"), max_bands = NULL):
#>   - unicef_child_bands
#>   - atagi_nip_schedule
#>   - pneumococcal_program
#>   - rsv_maternal_infant
#>   - neonatal_early
#>   - paediatric_developmental
#>   - school_entry_bands
#>   - who_paediatric_growth
#>   - influenza_research
#>   - rsv_research
#> Supply `scheme` explicitly to choose one, or narrow your filters further.

The scheme families

Schemes are organised into six families. Use family = to restrict your search.

Family family = value Count Examples
National statistical standards "national_stats" 10 abs_5yr, abs_broad_lifecourse
International statistical standards "international_stats" 7 who_life_course, eurostat_5yr
Vaccination/immunisation guidance "vaccination" 10 atagi_covid19_2025, flucan_sentinel
Surveillance-system conventions "surveillance" 8 nndss_standard, racf_aged_care
Clinical/developmental staging "clinical_developmental" 8 geriatric_fine, paediatric_developmental
Disease/research-specific "disease_specific" 7 rsv_research, covid19_severity_strata

Focus tags — cross-cutting filters

Every scheme carries one or more focus tags that cut across families.

list_age_schemes(focus = "aged_care")
#>                scheme                 family
#>      abs_5yr_extended         national_stats
#>      aihw_10yr_65plus         national_stats
#>       who_5yr_100plus    international_stats
#>    atagi_covid19_2025            vaccination
#>  pneumococcal_program            vaccination
#>       rsv_older_adult            vaccination
#>       shingles_zoster            vaccination
#>        racf_aged_care           surveillance
#>        geriatric_fine clinical_developmental
#>     geriatric_frailty clinical_developmental
#>   cardiovascular_risk       disease_specific
#>                                focus n_bands age_range
#>   fine_grained|national_au|aged_care      22        0+
#>                national_au|aged_care       7        0+
#>  fine_grained|who_standard|aged_care      21        0+
#>    vaccination|national_au|aged_care       5        0+
#>     vaccination|paediatric|aged_care       4        0+
#>    vaccination|aged_care|national_au       3        0+
#>                vaccination|aged_care       5        0+
#>               surveillance|aged_care       5        0+
#>               aged_care|fine_grained       7        0+
#>                      aged_care|broad       4        0+
#>             research|aged_care|broad       5        0+
# Quick summary schemes for small datasets or executive reports
list_age_schemes(focus = "broad", max_bands = 5)
#>                    scheme                 family                          focus
#>           atagi_influenza            vaccination  vaccination|national_au|broad
#>           flucan_sentinel           surveillance surveillance|broad|national_au
#>             nors_outbreak           surveillance             surveillance|broad
#>         geriatric_frailty clinical_developmental                aged_care|broad
#>        school_entry_bands clinical_developmental               paediatric|broad
#>       cardiovascular_risk       disease_specific       research|aged_care|broad
#>  mental_health_lifecourse       disease_specific                 research|broad
#>      oncology_trial_bands       disease_specific                 research|broad
#>  n_bands age_range
#>        4        0+
#>        5        0+
#>        5        0+
#>        4        0+
#>        4        0+
#>        5        0+
#>        5        0+
#>        5        0+

Neonatal schemes: age_unit = "days"

Schemes in the neonatal family use days rather than years. Pass age_unit = "days" and ensure the age column is in days.

neonates <- data.frame(age_days = c(0, 0.5, 2, 5, 15, 30))
preening(neonates, age_col = "age_days", scheme = "neonatal_early",
         age_unit = "days")$age_group
#> [1] 0-<24h  0-<24h  24-<72h 72h-<7d 7-<28d  28d+   
#> Levels: 0-<24h < 24-<72h < 72h-<7d < 7-<28d < 28d+

Custom schemes

When no standard scheme fits, supply your own breaks and labels.

preening(
  df,
  age_col    = "age",
  scheme     = "custom",
  age_breaks = c(0, 18, 40, 65, Inf),
  age_labels = c("0-17", "18-39", "40-64", "65+")
)$age_group
#> [1] 0-17  0-17  0-17  18-39 40-64 65+   65+   65+  
#> Levels: 0-17 < 18-39 < 40-64 < 65+

Catch-all bands and the full-lifespan rule

Every scheme in the mudnester library spans 0 to Inf, so preening() never returns NA purely because a record fell outside a scheme’s “intended” range. Bands marked with ⁺ in the documentation (e.g. the 0-<60 floor band in rsv_older_adult) are catch-alls — a meaningful count in one of these bands is a signal to review the scheme choice, not a finding to report.

# rsv_older_adult is scoped to 60+. A child record still gets a band.
data.frame(age = c(3, 65, 80)) |>
  preening(age_col = "age", scheme = "rsv_older_adult")
#>   age age_group
#> 1   3     0-<60
#> 2  65     60-74
#> 3  80       75+

After preening: what comes next

preening() is typically called before roost() to enable age-stratified counts:

df |>
  preening(age_col = "age", scheme = "flucan_sentinel") |>
  roost(date_col = "onset_date", time_unit = "month",
        group_cols = "age_group")

See vignette("roost") for aggregation options, and vignette("age-schemes") for the full scheme catalogue with source citations.