fix: resolve transitive BLQ imputation dependencies for half.life chain (#1057)#1338
Conversation
…in (pharmaverse#1057) rm_impute_obs_params() previously used a single-level dependency check: parameters whose Depends did not directly reference an AUC param had their BLQ imputation removed. This missed the transitive chain half.life -> lambda.z -> aucinf.obs, causing standalone half.life to be calculated on raw data while AUCIFO internally used a different (imputed) half.life value. New .resolve_upstream_deps() helper recursively traces upstream dependencies (2 levels) from AUC parameters to identify all params in the calculation chain. half.life and lambda.z now correctly retain BLQ imputation when any AUC-dependent parameter is requested. Also adds the impute column guard from pharmaverse#1266 to prevent the "PKNCA_impute_method_FALSE" error when start_impute is FALSE. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
test_blq_1057.csv |
…verse#1057) Replace the reverse traversal (.resolve_upstream_deps) with a forward approach using a reverse dependency table (.build_rev_deps + .walk_forward_deps). Now follows the natural data-flow direction: for each parameter, checks whether any of its consumers (params that list it in Depends) are in the AUC chain. This makes the logic more intuitive — "half.life feeds lambda.z, which feeds aucinf.obs, therefore half.life keeps imputation." Extracted .walk_forward_deps() as a separate helper to reduce cyclomatic complexity. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Missing tests for dependency resolution helpers
Suggested tests in describe(".build_rev_deps", {
it("builds reverse dependency map from Depends column", {
meta <- data.frame(
PKNCA = c("aucinf.obs", "lambda.z", "half.life", "cmax"),
Depends = c("lambda.z, clast.obs", "half.life", "tmax, tlast", NA),
stringsAsFactors = FALSE
)
rev <- .build_rev_deps(meta)
expect_true("aucinf.obs" %in% rev[["lambda.z"]])
expect_true("lambda.z" %in% rev[["half.life"]])
expect_null(rev[["cmax"]])
})
})
describe(".walk_forward_deps", {
it("resolves half.life via lambda.z -> aucinf.obs chain", {
rev <- list(
half.life = "lambda.z",
lambda.z = "aucinf.obs",
tmax = "half.life"
)
needs <- .walk_forward_deps("aucinf.obs", rev, max_depth = 2)
expect_true("lambda.z" %in% needs)
expect_true("half.life" %in% needs)
# tmax should NOT be included (depth 3)
expect_false("tmax" %in% needs)
})
})
describe("rm_impute_obs_params", {
it("retains imputation for half.life when aucinf.obs is requested", {
# Verify half.life is NOT in params_not_to_impute
# (integration test with actual metadata_nca_parameters)
})
}) |
Hardcoded
|
|
… helpers (pharmaverse#1057) - Remove CLAUDE.md from project .gitignore (per reviewer feedback) - Add 4 unit tests for .build_rev_deps() and .walk_forward_deps() covering reverse dep map construction, empty Depends handling, transitive chain resolution (half.life -> lambda.z -> aucinf.obs), and max_depth boundary enforcement Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ward_deps (pharmaverse#1057) - Replace hardcoded max_depth=2 with explicit obs_params exclusion set (cmax, tmax, tlast). More robust - won't silently break if future parameters introduce deeper dependency chains. - Add rm_impute_obs_params integration test verifying half.life retains imputation when aucinf.obs is requested. - Ensure .gitignore has trailing newline. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
wangzhengdna-lang
left a comment
There was a problem hiding this comment.
@Shaakon35 All three addressed:
1. Missing tests — Fixed
Tests for .build_rev_deps() and .walk_forward_deps() were already added in the previous commit. Now also added an integration test for rm_impute_obs_params() verifying that half.life retains BLQ imputation when aucinf.obs is requested.
2. Hardcoded max_depth = 2 — Replaced with explicit exclusion set
Adopted Option A: replaced the depth limit with an explicit obs_params parameter (default: c("cmax", "tmax", "tlast")). The walk now runs until no more params are found, stopping only at the listed observational params. This is self-documenting and won't silently break if future params introduce deeper dependency chains.
3. .gitignore — Fixed
CLAUDE.md was already removed in the previous commit. Added the missing trailing newline to prevent diff artifacts.
All 124 intervals tests pass.
Issue
Closes #1057
Description
rm_impute_obs_params()incorrectly removes BLQ imputation fromhalf.lifeandlambda.zwhen they are calculated as standalone parameters, causing inconsistency with the same values used internally by AUCINF (AUCIFO/AUCIFP).Root cause
The dependency check was limited to one level — it checked a parameter's direct Depends column but missed the transitive chain:
Fix
Added
.build_rev_deps()and.walk_forward_deps()helpers that recursively trace upstream dependencies from AUC parameters, limited to 2 levels to avoid reaching purely observational leaf params (cmax, tmax, tlast).Definition of Done
How to test
Requires manual verification with PK data:
Files changed
R/intervals.R.build_rev_deps()and.walk_forward_deps()helpers; refactoredrm_impute_obs_params()with transitive dependency resolutiontests/testthat/test-intervals.R.build_rev_deps()and.walk_forward_deps()DESCRIPTIONNEWS.mdContributor checklist
.build_rev_deps()and.walk_forward_deps()have 4 dedicated tests@noRddocs for both helpers.scsschange was done, rundata-raw/compile_css.R— N/Adata-raw/test_suggests_hidden.R— N/ANotes to reviewer
metadata_nca_parameters$Dependswhich already defines "I need X" relationships. No new metadata is introduced.Closes #1057