`pk.calc.aucabove()` (and so `aucabove.predose.all` and `aucabove.trough.all`) is biased at threshold crossings and with lin-up/log-down
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 45/100
Research direction
Start by reading pk.calc.aucabove() and pk.calc.time_above() to compare how they handle threshold crossings and interpolation. Add tests for the listed linear and log-down cases, including a profile with several crossings; done means the results match the stated exact areas and numerical integration of the interpolated curve, while preserving the NA exclude behavior. Add a NEWS entry describing the changed results.
Written by the indexing model from the issue text.
Description
Summary
pk.calc.aucabove() computes pk.calc.auc(conc = pmax(conc - conc_above, 0), ...). That causes two problems.
- Crossings are not interpolated. A segment that goes from above to below the threshold (or the reverse) is integrated as a trapezoid down to 0 at the far observation time. The crossing time is never found, so the area is overestimated. In a symmetric crossing segment the error is a factor of 2.
pk.calc.time_above()does interpolate crossings, so the two parameters are inconsistent with each other on the same data. - Log-down is applied to the shifted curve. With
auc.method = "lin up/log down", the log trapezoid is applied toC - threshold, which assumesC - thresholddeclines mono-exponentially. The model is thatCdeclines mono-exponentially, so the result differs even when both points are above the threshold.
Reprex
library(PKNCA)
# Falling crossing; exact area above 10 is 0.5 (linear)
pk.calc.aucabove(conc = c(12, 8), time = c(0, 1), conc_above = 10, method = "linear")
#> 1
# Rising crossing; exact 0.5
pk.calc.aucabove(conc = c(8, 12), time = c(0, 1), conc_above = 10, method = "linear")
#> 1
# time_above interpolates the same crossing correctly
pk.calc.time_above(conc = c(12, 8), time = c(0, 1), conc_above = 10, method = "linear")
#> 0.5
# lin up/log down, falling crossing; exact (C = 12*exp(-a*t), a = log(1.5)) is 0.436
pk.calc.aucabove(conc = c(12, 8), time = c(0, 1), conc_above = 10, method = "lin up/log down")
#> 1
# lin up/log down, both points above threshold; exact integral of (20*exp(-a*t) - 10)
# with a = log(20/15) is 7.380
pk.calc.aucabove(conc = c(20, 15), time = c(0, 1), conc_above = 10, method = "lin up/log down")
#> 7.213
Proposed fix
Integrate segment by segment on the original concentration scale:
- Find the crossing times with the same interpolation
pk.calc.time_above()uses (linear, or log for log-down segments). Sharing a helper would keep the two parameters consistent. - Over each above-threshold sub-segment, integrate
C(t) − threshold:- linear: trapezoid of
C − threshold; - log-down:
(C1 − C2)/(ln C1 − ln C2) × Δt − threshold × Δt.
- linear: trapezoid of
- Keep the current behaviour of
NAwith anexcludereason whenconc_aboveisNA. - This will change existing
aucabove.*results wherever a profile crosses its predose or trough value. A NEWS entry should say so.
Tests to add
- Rising and falling linear crossings (0.5).
- Log-down crossing (0.436).
- Log-down segment entirely above the threshold (7.380).
- A profile with several crossings, compared against numerical integration of the interpolated curve.
- Dominant language
- R
- Stars
- 86
- Forks
- 32
- Avg merge
- 14h 17m
- Merged PRs (30d)
- 50
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from humanpred/pknca
-
`pk.calc.time_above()` with lin-up/log-down silently drops segments that end at a negative valueOpen
Difficulty 2/5 1-3 hours Newbie friendliness 79/100
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 67/100
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 58/100
Maintainers usually reply within 1 day
-
Feature request: SDTM input functionsPossibly taken @Gero1999 claimed this 227 days ago. Open
Difficulty 4/5 3-5 days Newbie friendliness 20/100
humanpred/pknca#516 · 2 comments ·
Maintainers usually reply within 1 day
-
Join COSAOpen
Difficulty 5/5 Over a week Newbie friendliness 10/100
humanpred/pknca#393 · 2 comments ·
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 71/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
OHDSI/PatientLevelPrediction#688 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
rfordatascience/tidytuesday#1098 ·