Skip to content

fix: associate PTA results with the requested regimen when ids are text - #391

Open
Siel wants to merge 2 commits into
mainfrom
fix/pta-regimen-order
Open

Siel wants to merge 2 commits into
mainfrom
fix/pta-regimen-order

Conversation

@Siel

@Siel Siel commented Sep 15, 2026

Copy link
Copy Markdown
Member

Fixes the mis-association reported by Jim Rhodes: with multi-digit simulation ids in a multi-regimen PM_sim, PM_pta rows described the wrong regimen.

What happened

Subject ids are text whenever a data file labels subjects with anything other than plain integers, and Pmetrics ordered and grouped data by that text. Sorting text is byte order, so regimens came out as

1, 10, 11, 12, 2, 3, 4, ...

PM_pta numbers its rows by regimen position (reg_num), so row 4 described id 12 rather than id 4 — exactly the pattern reported (rows 4, 5, 6 matching ids 12, 13, 14). Nothing in the PDI calculation was wrong, and prop_success == mean(success) held; only the association was.

Three places decided that order:

Location What fixed it
PM_data standardization group_by(id, …) orders text ids as text; the standardized data is now arranged by natural id order
PM_sim arrange(.id, …) sorted the id string in two places (getSim and the prediction-time expansion), and .id was ranked with dense_rank(id)
PM_pta dense_rank(id) on text ids ranks lexicographically

dense_rank(id) in PM_pta came from #341 ("fix: mis-sorting during PTA"). That fix addressed a sorting problem by converting ids to ranks, but the rank of a text id is lexicographic, so it did not cover ids of different lengths. This is an incomplete fix rather than a later regression.

The change

R/id_order.R adds one internal helper:

pm_id_rank(id)   # numeric ids sort numerically, anything else keeps byte order

It returns a rank, so equal ids stay together and it can be used directly inside arrange()/group_by() pipelines. It replaces id as the sort key at every site that decides regimen order. Ids that are already numeric are unaffected; ids like "P01", "P02" keep their existing order.

Verification

A regression test builds 12 regimens with text ids ("1" … "12") and log-spaced doses spanning the target boundary, then compares each multi-regimen PM_pta row against PM_pta run on that regimen alone — so it tests the association without reimplementing the PDI calculation. It also asserts the simulated ids appear in numeric order, and guards against a vacuous pass if every regimen returned the same vector.

Without the fix the test fails with the reported pattern:

`actual[1:7]`:   "1" "10" "11" "12" "2" "3" "4"
`expected[1:4]`: "1"                 "2" "3" "4"

With it, all 12 rows describe their own regimen. Full testthat suite: no failures.

Scope

Only sites that decide regimen order were changed. Other arrange(id, …) calls order rows within a subject (for example the crossing interpolation inside pta_time, and the NCA/validation paths), so they do not affect which regimen a result belongs to; pm_id_rank() is available if any of them needs the same treatment later.

For analyses already run

PM_pta results are unaffected when ids are numeric, or when a single regimen is analysed at a time. For text ids with more than one regimen, the row order is the lexicographic order of the ids, so results should be re-associated by id (the label column, when supplied, is assigned positionally and follows the same order). The reporter's v3.0.9 FDA analysis predates #341, which introduced this ranking path, and warrants the mapping check they describe.

Subject ids are text whenever a data file labels subjects with anything other
than plain integers, and Pmetrics ordered and grouped data by that text. Text
sorts by bytes, so a 12-regimen simulation came out as 1, 10, 11, 12, 2, 3, ...
PM_pta numbers its rows by regimen position, so row 4 described the regimen with
id 12 rather than id 4. The PDI calculation and `prop_success == mean(success)`
were correct throughout; only the association was wrong.

`pm_id_rank()` (new, internal) returns an integer rank per id: numeric ids sort
numerically, anything else keeps byte order, and equal ids share a rank. It
replaces `id` as the sort key at every site that decides regimen order:

- PM_data standardization, where `group_by(id, ...)` orders text ids as text
- PM_sim, where `.id` was ranked with `dense_rank(id)` and two `arrange(.id, ...)`
  calls sorted the id string
- PM_pta, where `dense_rank(id)` reported the same ranking

`dense_rank(id)` in PM_pta came from #341 ("fix: mis-sorting during PTA"), which
converted ids to ranks to fix a sorting problem; the rank of a text id is
lexicographic, so ids of different lengths were not covered. This is an
incomplete earlier fix rather than a later regression.

Ids that are already numeric are unaffected, and ids such as "P01"/"P02" keep
the order they had.

The regression test builds 12 regimens with text ids and log-spaced doses
spanning the target boundary, then compares each multi-regimen PM_pta row with
PM_pta run on that regimen alone, so it tests the association without
reimplementing the PDI calculation. Without this fix it fails with the reported
pattern ("1" "10" "11" "12" "2" ...).

Full testthat suite: no failures.
@mhovd

mhovd commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

All CI seems to be failing, could you update the PR to fix it?

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.71429% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.81%. Comparing base (ef474a1) to head (21db088).

Files with missing lines Patch % Lines
R/id_order.R 81.81% 2 Missing ⚠️
R/PM_data.R 80.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #391      +/-   ##
==========================================
+ Coverage   64.77%   64.81%   +0.03%     
==========================================
  Files          52       53       +1     
  Lines       22964    22979      +15     
==========================================
+ Hits        14876    14894      +18     
+ Misses       8088     8085       -3     
Files with missing lines Coverage Δ
R/PM_pta.R 76.07% <100.00%> (+0.84%) ⬆️
R/PM_sim.R 67.84% <100.00%> (+0.04%) ⬆️
R/PM_data.R 60.38% <80.00%> (-0.12%) ⬇️
R/id_order.R 81.81% <81.81%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mhovd
mhovd requested a balanced review from Copilot September 16, 2026 09:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

This branch has not been deployed

No deployments
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.

4 participants