fix: resolve interval parameter show raw PPTESTCDs instead of labels in NCA results #1305#1321
Conversation
pharmaverse#1305) Interval parameters (e.g. AUCINT_0-24) now display human-readable labels by stripping the range suffix before metadata lookup and formatting as "Label (start-end)". Applied to selector_label() and .build_ylabel(). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Actually @Shaakon35 I think is probably better to bet for "AUC from T1 to T2" so we prevent unnecessary hardcoding/bugs in the future. Otherwise the correct way would probably be "AUC from 0 to 24", "AUC from 24 to 48"... |
Fine for me. Please refine the PR by: and fix the label shown in the datatables:
|
…els (pharmaverse#1305) Change label format from "AUC from T1 to T2 (0-24)" to "AUC from 0 to 24" per reviewer feedback. Fix applies to: - selector_label() — parameter selector descriptions - .build_ylabel() — boxplot y-axis labels - add_label_attribute() — data table column headers (NCA Results, Descriptive Statistics) via rowwise gsub to avoid vectorized replacement issue Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…pharmaverse#1305) Add resolve_param_labels() helper to R/label_operators.R that parses column names (e.g. AUCINT_0-12[Hours*ug/mL]) and sets label attributes from metadata_nca_parameters, replacing T1/T2 with actual interval values. Call it from reactable_server() after apply_labels(). Also update tooltip to show the same readable label as the header. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Thanks for catching this. Two things fixed as your comments:
|
|
Thanks @wangzhengdna-lang Almost there,
Could you please fix the pickerinput to have the label of AUCINT_0-12 or 0-24 as:
|
…el (pharmaverse#1305) Three refinements per reviewer feedback: 1. define_cols(): Show raw column name (e.g. AUCINT_0-24[Hours*ug/mL]) as the table header, with the resolved label (e.g. AUC from 0 to 24) as the tooltip — swapped from the previous state. 2. selector_label(): Fix gsub vectorization bug where case_when + gsub used only the first start/end values for all interval parameters. Switched to rowwise() pattern so each parameter uses its own range. 3. Fix line-length lints from translate_terms() calls in label_operators.R and pivot_wider_pknca_results.R. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
@Shaakon35 Thanks for the detailed feedback. Now all issues are fixed: Picker in Descriptive Statistics — The Data table column headers — Fixed pre-existing line-length lints from |
6119aa5 to
ea53bcb
Compare
| } else { | ||
| PPTESTCD_cdisc | ||
| } | ||
| ) %>% |
There was a problem hiding this comment.
Issue 1: Duplicated add_label_attribute function
This function is now identical to the one in R/label_operators.R (line 202). Both copies received the same rowwise() + gsub T1/T2 replacement logic in this PR, which increases the maintenance burden — any future change needs to be applied in two places.
Proposed fix: Remove this copy entirely and call the exported version from label_operators.R. Since pivot_wider_pknca_results.R already calls it at line 167, just delete the local definition (lines 213–255) and rely on the exported one:
# In pivot_wider_pknca_results.R, line 167 already does:
pivoted_res <- add_label_attribute(pivoted_res, myres)
# This will resolve to the exported version in R/label_operators.R
# once the local duplicate is removed.The local copy is marked @noRd @keywords internal, so removing it won't break any public API.
| ), | ||
| PPTESTCD_cdisc = PPTESTCD_cdisc_raw | ||
| ) %>% | ||
| rowwise() %>% |
There was a problem hiding this comment.
Issue 2: rowwise() is slow — use vectorized ifelse() + gsub() instead
rowwise() evaluates each row independently, which is slow on large data frames. This same pattern appears in 3 locations in this PR (R/label_operators.R, R/pivot_wider_pknca_results.R, inst/shiny/functions/selector_label.R).
Since gsub() is already vectorized and start/end are column values, the whole rowwise() %>% mutate() %>% ungroup() block can be replaced with a single vectorized mutate():
Proposed fix (applies to all 3 locations):
# Replace lines 217-229 with:
mutate(
PPTESTCD_cdisc = ifelse(
type_interval == "manual",
gsub("T2", as.character(end),
gsub("T1", as.character(start), PPTESTCD_cdisc_raw)),
PPTESTCD_cdisc
)
) %>%No rowwise() or ungroup() needed. Same result, fewer lines, better performance.
For selector_label.R, the equivalent would be:
mutate(
desc = case_when(
is_interval & !is.na(PPTEST) ~
gsub("T2", as.character(end_dose),
gsub("T1", as.character(start_dose), PPTEST)),
!is.na(PPTEST) ~ PPTEST,
TRUE ~ PPTESTCD
)
) %>%|
@wangzhengdna-lang All good, it works for me :) Thanks for that! Just address my two comments and fix the 4 failing checks:
|
… NOTE This NSE column reference is used inside mutate() in add_label_attribute() (two copies: R/label_operators.R and R/pivot_wider_pknca_results.R). R CMD check sees it as an undefined global variable. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Shaakon35
left a comment
There was a problem hiding this comment.
@wangzhengdna-lang Can you take care of these two issues please?
#1321 (comment)
#1321 (comment)
wangzhengdna-lang
left a comment
There was a problem hiding this comment.
Thanks @Shaakon35 for the detailed review. Here's my analysis of the two issues:
Issue 1: Remove duplicate add_label_attribute — Accepted ✓
Good catch. Both copies are now identical after this PR, and maintaining two copies has been a documented pain point. Since pivot_wider_pknca_results.R:167 already calls add_label_attribute() by name, removing the local copy (lines 210–238) will automatically resolve to the exported version in label_operators.R. Will fix.
Issue 2: Replace rowwise() with vectorized ifelse() + gsub() — Cannot accept directly ✗
This is a known pitfall documented in the project's CLAUDE.md: gsub() inside mutate() only uses the first element of the replacement argument.
gsub("T1", as.character(start), PPTESTCD_cdisc_raw) — when start is a column vector like c(0, 12, 0, 24), gsub() only uses start[1] = 0 for ALL rows. So when myres$result contains multiple interval parameters with different start/end values (e.g., AUCINT_0-12 and AUCINT_0-24), the non-rowwise version produces:
| Parameter | start | end | Expected | Without rowwise |
|---|---|---|---|---|
| AUCINT_0-12 | 0 | 12 | "AUC from 0 to 12" | "AUC from 0 to 12" ✓ |
| AUCINT_0-24 | 0 | 24 | "AUC from 0 to 24" | "AUC from 0 to 12" ✗ |
AUCINT_0-24 incorrectly gets end[1] = 12.
Compromise: We can use group_by(start, end, type_interval) + vectorized gsub() inside a grouped mutate(). Since all rows in the same group share identical start/end, gsub() works correctly inside the group, and it's faster than rowwise():
group_by(start, end, type_interval) %>%
mutate(PPTESTCD_cdisc = if (type_interval[1] == "manual") {
label <- PPTESTCD_cdisc_raw
label <- gsub("T1", as.character(start[1]), label)
label <- gsub("T2", as.character(end[1]), label)
label
} else {
PPTESTCD_cdisc
}) %>%
ungroup()Would this approach work for you? If so, I'll apply it to all 3 locations.
Sounds good! :) |
|
The new Suggested test cases for
These can go in |
5 tests covering: interval column with unit suffix, interval column without unit suffix, non-interval column, existing label preservation, and unrecognized column fallback. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
wangzhengdna-lang
left a comment
There was a problem hiding this comment.
@Shaakon35 Good catch — added 5 unit tests for resolve_param_labels() covering all suggested cases:
- Interval with unit suffix (
AUCINT_0-12[hr*ng/mL]→ "AUC from 0 to 12") - Interval without unit suffix (
AUCINT_0-24→ "AUC from 0 to 24") - Non-interval column (
CMAX→ "Max Conc") - Existing label preserved (not overwritten)
- Unrecognized column (no label set, returns NULL)
All 34 label_operators tests pass.
|
@Shaakon35 Thanks for the detailed list While writing them, 4 additional edge cases noticed may not covered by the current tests. Wanted to check if you'd like these included as well before pushing:
Dose-aware interval parameter ( Multi-column mixed data frame — All current tests use single-column data frames. A 4-column test (interval + non-interval + unknown + existing-label) verifies the for-loop handles mixed column types in one pass without errors. Empty data frame (0 rows) — The function iterates over |
|
Yes, please include all 4. They cover real code paths:
|
|
@h5hoang Could you please have a review? :) |
wangzhengdna-lang
left a comment
There was a problem hiding this comment.
@Shaakon35 Merge conflicts resolved with latest main:
- DESCRIPTION: version bumped to 0.1.0.9178 (main had moved to 0.1.0.9177)
- NEWS.md: #1305 entry already present on main, no conflict
- All 1851 tests pass (0 fail, 0 warn)
PR should now be mergeable.
h5hoang
left a comment
There was a problem hiding this comment.
Thanks @wangzhengdna-lang, functionality looks good! Just two nitpicks before the merge, both are items from your earlier discussion with @Shaakon35 that were agreed but don't seem to have made it into the pushed code on your branch:
-
add_label_attributestill has both definitions present (inlabel_operators.Randpivot_wider_pknca_results.R). I think you could drop the pivot_wider_pknca_results.R copy and keep the exported one inlabel_operators.R? -
And for the
rowwise()togroup_by(start, end, type_interval)adoption, all three files still userowwise()(inlabel_operators.R,pivot_wider_pknca_results.R, andselector_label.R). Output is correct either way, so this one's optional / could be a follow-up, your call
This is on me for reviewing so late, but some housekeeping updates need to be made like rebumping the DESCRIPTION version 0.1.0.9178 before merge.
Otherwise LGTM 🙏!
…group_by (pharmaverse#1305) - Delete duplicate add_label_attribute from pivot_wider_pknca_results.R; the exported version in label_operators.R is the single source of truth - Replace rowwise() with group_by(start, end, type_interval) in label_operators.R and selector_label.R for better performance on large data frames while maintaining correctness
There was a problem hiding this comment.
@h5hoang All three addressed:
1. Duplicate add_label_attribute — Removed
Deleted the internal copy from pivot_wider_pknca_results.R. The exported version in label_operators.R is now the single source of truth. The call at pivot_wider_pknca_results.R:167 resolves to the exported one automatically.
2. rowwise() → group_by(start, end, type_interval) — Applied
Replaced in both label_operators.R and selector_label.R (the pivot_wider_pknca_results.R copy was removed). Since all rows within the same group share identical start/end values, gsub() inside grouped mutate() is correct and more efficient than rowwise().
3. Version — Already bumped
DESCRIPTION is at 0.1.0.9178 (rebased + bumped during the merge conflict resolution).
All 1851 tests pass.
|
@wangzheng Almost ready, could you just add the 4 units tests mentionned here: @Gero1999 How do we handle the merge with the fork of @wangzhengdna-lang |
|
@Shaakon35 no problem, the procedure is exactly the same as any other repo branch |





Issue
Closes #1305
Description
Interval parameters (e.g.,
AUCINT_0-24) displayed as raw PPTESTCDs in parameter selectors and boxplot y-axis labels instead of human-readable labels. The root cause:rename_interval_params()appends_start-endsuffixes to PPTESTCD, but downstream label lookup againstmetadata_nca_parametersonly has entries for base names (AUCINT), not the suffixed versions.Fix
Reused existing
parse_interval_parameter()to strip the range suffix before metadata lookup, then format the display as"{PPTEST} ({start}–{end})".Two locations fixed:
selector_label()(inst/shiny/functions/selector_label.R) — Primary fix. Themetadata_type == "parameter"branch now:parse_interval_parameter()to extract base namemetadata_nca_parameterson the base name"AUC from T1 to T2 (0–24)".build_ylabel()(R/flexible_violinboxplot.R) — Secondary fix. Boxplot/violin plot y-axis labels now resolve interval parameter labels before appending the unit string.Examples:
AUCINT_0-24→AUC from T1 to T2 (0–24) [hr*ng/mL]AUCINTda_0-24→AUC from T1 to T2, dose-aware (0–24) [hr*ng/mL]Definition of Done
How to test
devtools::load_all(); aNCA::run_app()AUCINT_0-24should show as"AUC from T1 to T2 (0–24)"in the dropdownAUCINT_0-24Contributor checklist
parse_interval_parameter()has existing tests intest-ratio_calculations.R.scsschange was done, rundata-raw/compile_css.R— N/Adata-raw/test_suggests_hidden.R— N/ANotes to reviewer
parse_interval_parameter()fromR/ratio_calculations.R— this function already correctly parses thePREFIX_start-endpattern and is tested intest-ratio_calculations.R. No new parsing logic introduced.selector_label()test intest-selector_label.Ruses a standalone simplified version of the function (not the Shiny module), so this change is not covered by that test file. Manual testing is required.is_intervalflag fromparse_interval_parameter()gates the new formatting path.