Skip to content

Make age_limits parameter optional in pop_age()#270

Merged
sbfnk merged 3 commits intomainfrom
fix-age-limits-optional
Jan 19, 2026
Merged

Make age_limits parameter optional in pop_age()#270
sbfnk merged 3 commits intomainfrom
fix-age-limits-optional

Conversation

@sbfnk
Copy link
Collaborator

@sbfnk sbfnk commented Jan 9, 2026

Summary

  • Makes age_limits parameter explicitly optional with NULL default
  • Changes conditional from if (!missing(age_limits)) to if (!is.null(age_limits))
  • Updates documentation to clarify the parameter is optional
  • Adds test for calling pop_age() without age_limits

Fixes #268

Summary by CodeRabbit

  • New Features

    • age_limits is now optional; omitting or setting it to NULL returns the population data unchanged.
    • Backwards-compatible legacy parameter names remain supported with deprecation warnings.
  • Bug Fixes

    • Early-return behavior prevents unnecessary processing when age_limits is NULL and improves handling of missing age groups.
  • Documentation

    • Docs updated to describe the NULL default and its effect.
  • Tests

    • Added tests confirming NULL/omitted age_limits behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

The pop_age() function now accepts age_limits = NULL and returns the input unchanged when age_limits is NULL. Deprecated alias parameters were added for backward compatibility and the internal age-group limiting and interpolation flow was reorganized.

Changes

Cohort / File(s) Summary
Function implementation
R/pop_age.R
Added age_limits = NULL default and early return when age_limits is NULL; replaced missing() checks with is.null() logic; added deprecated aliases age.limits, pop.age.column, pop.column; reworked age-limiting/interpolation flow to sort age_limits, compute missing groups, build lower/upper bounds and populations, then reduce and sum to requested groups.
Documentation
man/pop_age.Rd
Updated usage and argument docs to show age_limits = NULL default and clarify that NULL returns the population data unchanged.
Tests
tests/testthat/test-agegroups.r
Added tests asserting pop_age() returns identical data when age_limits is omitted or explicitly NULL, including a data.table input case.

Sequence Diagram(s)

(Skipped — changes are internal to a single function and do not introduce multi-component control flow requiring a diagram.)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐇 I nudged the limits, set them free,
If NULL they stay, as quiet as can be.
Old names bowed out, I tidy the trail,
I hop through tests with a twitchy tail.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: making the age_limits parameter optional in pop_age().
Linked Issues check ✅ Passed The PR successfully addresses issue #268 by making age_limits optional with NULL default, changing from if (!missing(age_limits)) to if (!is.null(age_limits)), and updating documentation.
Out of Scope Changes check ✅ Passed All changes are directly related to making age_limits optional and maintaining backward compatibility with deprecated arguments.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
R/pop_age.R (1)

73-134: “Return unchanged when age_limits is NULL” isn’t fully true due to early data.table coercion / keying.

Right now, data.table(pop) + setkeyv() run even when age_limits is NULL, which can change attributes (and can mutate by-reference if pop is already a data.table). If the contract is “unchanged”, early-return before coercion/keying.

Proposed diff
   if (
     !is.data.frame(pop) || !all(hasName(pop, c(pop_age_column, pop_column)))
   ) {
     cli::cli_abort(
       "Expecting {.arg pop} to be a data.frame with columns
       {.arg {pop_age_column}} and {.arg {pop_column}}."
     )
   }
 
+  if (is.null(age_limits)) {
+    return(pop)
+  }
+
   pop <- data.table(pop)
   setkeyv(pop, pop_age_column)
 
-  if (!is.null(age_limits)) {
-    age_limits <- sort(age_limits)
-    max_age <- max(pop[, pop_age_column, with = FALSE])
-    missing_ages <- setdiff(
-      age_limits[age_limits <= max_age],
-      pop[[pop_age_column]]
-    )
-    if (length(missing_ages) > 0) {
+  age_limits <- sort(age_limits)
+  max_age <- max(pop[, pop_age_column, with = FALSE])
+  missing_ages <- setdiff(
+    age_limits[age_limits <= max_age],
+    pop[[pop_age_column]]
+  )
+  if (length(missing_ages) > 0) {
       cli::cli_warn(
         c(
           "Not all age groups represented in population data (5-year age band).",
           # nolint start
           "i" = "Linearly estimating age group sizes from the 5-year bands."
           # nolint end
         )
       )
       ..original.upper.age.limit <- NULL
       pop <- pop[,
         ..original.upper.age.limit := c(pop[[pop_age_column]][-1], NA)
       ]
       pop <- pop[, ..original.lower.age.limit := get(pop_age_column)]
       all_ages <- data.frame(age_limits[
         age_limits <= max(pop[[pop_age_column]])
       ])
       colnames(all_ages) <- pop_age_column
       pop <- merge(pop, all_ages, all = TRUE, by = pop_age_column)
       pop <- pop[, ..segment := cumsum(!is.na(..original.lower.age.limit))]
       pop <- pop[,
         ..original.lower.age.limit := ..original.lower.age.limit[1],
         by = ..segment
       ]
       pop <- pop[,
         ..original.upper.age.limit := ..original.upper.age.limit[1],
         by = ..segment
       ]
       pop <- pop[, paste(pop_column) := get(pop_column)[1], by = ..segment]
       pop <- pop[, ..upper.age.limit := c(pop[[pop_age_column]][-1], NA)]
       pop[
         !is.na(..original.upper.age.limit),
         population := round(
           population *
             (..upper.age.limit - get(pop_age_column)) /
             (..original.upper.age.limit - ..original.lower.age.limit)
         )
       ]
       pop <- pop[, c(pop_age_column, pop_column), with = FALSE]
-    }
-
-    pop <- pop[get(pop_age_column) >= min(age_limits)]
-    pop <- pop[,
-      paste(pop_age_column) := reduce_agegroups(get(pop_age_column), age_limits)
-    ]
-    pop <- pop[, list(..population = sum(get(pop_column))), by = pop_age_column]
-    setnames(pop, "..population", pop_column)
   }
 
+  pop <- pop[get(pop_age_column) >= min(age_limits)]
+  pop <- pop[,
+    paste(pop_age_column) := reduce_agegroups(get(pop_age_column), age_limits)
+  ]
+  pop <- pop[, list(..population = sum(get(pop_column))), by = pop_age_column]
+  setnames(pop, "..population", pop_column)
+
   setkeyv(pop, pop_age_column)
   as.data.frame(pop)
 }
🧹 Nitpick comments (1)
tests/testthat/test-agegroups.r (1)

56-66: Good regression test for omitted/NULL age_limits.

Optional: add a small case with data.table::as.data.table(ages_it_2015) if you want to lock in “unchanged” behavior for data.table inputs too.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 185c535 and 6368ea4.

📒 Files selected for processing (3)
  • R/pop_age.R
  • man/pop_age.Rd
  • tests/testthat/test-agegroups.r
🧰 Additional context used
📓 Path-based instructions (3)
tests/**/*.{R,r}

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.{R,r}: Run devtools::test() to ensure all tests pass before committing
Add tests for bug fixes (regression tests) or new features where appropriate

Files:

  • tests/testthat/test-agegroups.r
**/*.{R,r}

📄 CodeRabbit inference engine (CLAUDE.md)

Run lintr::lint_package() to check for style issues and fix any in files you modified before committing

Files:

  • tests/testthat/test-agegroups.r
  • R/pop_age.R
R/**/*.{R,r}

📄 CodeRabbit inference engine (CLAUDE.md)

Run devtools::document() to regenerate documentation from roxygen comments before committing

Files:

  • R/pop_age.R
🧠 Learnings (6)
📓 Common learnings
Learnt from: sbfnk
Repo: epiforecasts/socialmixr PR: 248
File: R/contact-matrix-utils.R:206-216
Timestamp: 2025-12-12T19:08:46.874Z
Learning: In the socialmixr package, the add_age() helper function in R/contact-matrix-utils.R intentionally overwrites the age column (e.g., part_age, cnt_age) with values from the exact age column (e.g., part_age_exact, cnt_age_exact) when the exact column exists. This overwriting behavior is by design.
📚 Learning: 2025-12-12T19:08:46.874Z
Learnt from: sbfnk
Repo: epiforecasts/socialmixr PR: 248
File: R/contact-matrix-utils.R:206-216
Timestamp: 2025-12-12T19:08:46.874Z
Learning: In the socialmixr package, the add_age() helper function in R/contact-matrix-utils.R intentionally overwrites the age column (e.g., part_age, cnt_age) with values from the exact age column (e.g., part_age_exact, cnt_age_exact) when the exact column exists. This overwriting behavior is by design.

Applied to files:

  • man/pop_age.Rd
  • tests/testthat/test-agegroups.r
  • R/pop_age.R
📚 Learning: 2025-12-12T19:13:48.046Z
Learnt from: CR
Repo: epiforecasts/socialmixr PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-12T19:13:48.046Z
Learning: Applies to tests/**/*.{R,r} : Add tests for bug fixes (regression tests) or new features where appropriate

Applied to files:

  • tests/testthat/test-agegroups.r
📚 Learning: 2025-09-02T05:04:15.939Z
Learnt from: njtierney
Repo: epiforecasts/socialmixr PR: 196
File: R/contact-matrix-utils.R:7-13
Timestamp: 2025-09-02T05:04:15.939Z
Learning: In the socialmixr package, functions like est_part_age_mean create new columns (part_age) by imputing from other existing columns (part_age_exact, part_age_est_min, part_age_est_max). The condition checks the source columns, not the target column being created.

Applied to files:

  • R/pop_age.R
📚 Learning: 2026-01-07T10:52:15.874Z
Learnt from: sbfnk
Repo: epiforecasts/socialmixr PR: 259
File: R/load_survey.R:20-25
Timestamp: 2026-01-07T10:52:15.874Z
Learning: Use Zenodo concept DOIs (e.g., 10.5281/zenodo.1157918) in code examples and documentation so readers always access the latest dataset version without modifying code. Replace versioned or specific DOIs in examples (including R scripts like R/load_survey.R) with the concept DOI and include a brief note that it resolves to the latest version.

Applied to files:

  • R/pop_age.R
📚 Learning: 2026-01-09T19:13:19.631Z
Learnt from: sbfnk
Repo: epiforecasts/socialmixr PR: 266
File: tests/testthat/test-clean.R:197-208
Timestamp: 2026-01-09T19:13:19.631Z
Learning: In tests for the socialmixr package (e.g., any test files under tests/), ensure the clean() function preserves only columns present in the input data and does not add missing columns (such as country). Tests should explicitly verify that absent columns remain absent after cleaning, and that any changes to introduce missing columns are deliberate and intentional.

Applied to files:

  • R/pop_age.R
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: document
🔇 Additional comments (3)
R/pop_age.R (2)

7-8: Roxygen: optional age_limits is clearly documented.


28-37: The deprecated() function is already imported and does not need qualification.

deprecated is properly imported via importFrom(lifecycle,deprecated) in NAMESPACE and declared via roxygen imports in R/socialmixr-package.R, so the unqualified deprecated() calls will not error. While using lifecycle::deprecated() for explicitness is a stylistic choice, it is not required.

man/pop_age.Rd (1)

7-16: Docs are aligned with the new default (age_limits = NULL).

As per guidelines, ensure this came from devtools::document() (and run lintr::lint_package() + devtools::test()) before merge.

Also applies to: 21-23

Co-authored-by: sbfnk-bot <242615673+sbfnk-bot@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@R/pop_age.R`:
- Around line 81-126: The interpolation step uses a hardcoded population column
name; change the assignment at the population rounding (currently "population :=
round(...)") to use the dynamic pop_column variable instead. Specifically,
inside the block operating on pop (the data.table) where it filters with
!is.na(..original.upper.age.limit) and computes the fraction using
..upper.age.limit, get(pop_age_column), ..original.upper.age.limit and
..original.lower.age.limit, replace the literal population := ... with a dynamic
assignment like (pop_column) := round(get(pop_column) * (..upper.age.limit -
get(pop_age_column)) / (..original.upper.age.limit -
..original.lower.age.limit)) so the code honors custom pop_column names.

@sbfnk sbfnk added this pull request to the merge queue Jan 19, 2026
Merged via the queue into main with commit a32ecd1 Jan 19, 2026
14 checks passed
@sbfnk sbfnk deleted the fix-age-limits-optional branch January 19, 2026 12:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pop_age() age_limits parameter has inconsistent optional/required status

1 participant