Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RXR-550: bring 48-hour criteria to KDIGO staging definitions #23

Merged
merged 8 commits into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 42 additions & 3 deletions R/calc_aki_stage.R
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,7 @@ calc_aki_stage <- function (
dat$stage[dat$baseline_scr_diff >= 0.5 | dat$scr >= 4.0] <- 3

} else if (method == "kdigo") {
dat$stage[dat$scr / baseline_scr >= 1.5 | dat$baseline_scr_diff >= 0.3] <- 1
dat$stage[dat$scr / baseline_scr >= 2 ] <- 2
dat$stage[dat$scr / baseline_scr >= 3 | dat$scr >= 4 | (dat$egfr < 35 & age < 18)] <- 3
dat$stage <- kdigo_stage(dat = dat, baseline_scr = baseline_scr, age = age)
}

## get max class, convert to character class:
Expand Down Expand Up @@ -200,3 +198,44 @@ calc_aki_stage <- function (

return(obj)
}

#' Calculate AKI stage according to KDIGO criteria
#'
#' @param dat Data frame containing at least the following columns:
#' * `scr`: serum creatinine
#' * `t`: creatinine sample times in hours
#' * `baseline_scr_diff`: difference between baseline scr and scr at current
#' timepoint
#' * `egfr`: eGFR at timepoint
#' @param baseline_scr Baseline serum creatinine value (numeric)
#' @param age Patient age
#' @md
#'
kdigo_stage <- function(dat, baseline_scr, age) {
stage <- rep(NA, nrow(dat))
for (i in seq_along(dat$t)) {
current_time <- dat$t[i]
last_48h <- which(dat$t < current_time & dat$t > (current_time - 48))
scr <- dat$scr[i]
scr_last_48h <- dat$scr[last_48h]
# An AKI has occurred if there's a rise by 0.3 mg/dl within 48 hours or if
# there's a rise to 1.5x baseline*. We only check for the 0.3 mg/dl rise if
# scr_last_48h contains at least one value, since otherwise we don't have
# any prior timepoints to compare to.
#
# *technically the rise to 1.5x baseline should be "known or presumed to
# have occurred within the prior 7 days", but that is a bit hard to pin
# down and raises a lot of edge cases, so we do not implement that logic
# here currently.
if ((scr / baseline_scr) %>=% 1.5 || (length(scr_last_48h) > 0 && (scr - min(scr_last_48h)) %>=% 0.3)) {
stage[i] <- 1
}
if ((scr / baseline_scr) %>=% 2) {
stage[i] <- 2
}
if ((scr / baseline_scr) %>=% 3 || scr %>=% 4 || isTRUE(dat$egfr[i] < 35 & age < 18)) {
stage[i] <- 3
}
}
stage
}
9 changes: 9 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,12 @@ remove_lt_gt <- function(x) {
return(as.numeric(x))
}
}

#' Greater-than-or-equal-to with a little room for floating point precision
#' issues
#'
#' @param x Numeric vector
#' @param y Numeric vector
`%>=%` <- function(x, y) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Thought: do we want to use this in non-clinPK package to address floating point precision issues? Could be useful to have this in irxtools

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought that might be useful, but irxtools is currently private and clinPK is public, so if clinPK is going to take on that dependency we'd need to make irxtools public as well. That seemed like a bigger discussion, maybe we can revisit it separately? It wouldn't be hard to update this later if we do decide to move it to a different package.

x > y | mapply(function(x, y) isTRUE(all.equal(x, y)), x, y)
}
18 changes: 18 additions & 0 deletions man/grapes-greater-than-equals-grapes.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions man/kdigo_stage.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

68 changes: 66 additions & 2 deletions tests/testthat/test_calc_aki_stage.R
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
test_that("AKI stage is calculated correctly", {
# egfr < 35 and age < 18
test0 <- calc_aki_stage(
scr = c(3),
t = c(0),
egfr = 30,
age = 15,
verbose = FALSE
)
# increase by >= 0.3 mg/dl from t=36 to t=48
test1 <- calc_aki_stage(
scr = c(0.7, 0.8, 1.2, 1.6, 1),
t = c(0, 24, 36, 48, 72),
egfr = c(60, 40, 30, 36, 50),
method = "KDIGO",
verbose = FALSE
)
# increase by >= 0.3 mg/dl from t=24 to t=36
test2 <- calc_aki_stage(
scr = c(0.5, 0.7, 1.2, 1.6, 1.3),
t = c(0, 24, 36, 48, 72),
egfr = c(60, 40, 30, 36, 50),
age = 17,
verbose = FALSE
)
# increase by >= 0.3 mg/dl from t=24 to t=36
test2a <- calc_aki_stage(
scr = c(0.5, 0.7, 1.2, 1.6, 1.3),
t = c(0, 24, 36, 48, 72),
egfr = c(60, 40, 30, 36, 50),
age = 21,
verbose = FALSE
)
# increase by >= 0.3 mg/dl from t=24 to t=36
test2b <- calc_aki_stage(
scr = c(0.5, 0.7, 1.2, 1.6, 1.3),
t = c(0, 24, 36, 48, 72),
Expand All @@ -36,6 +41,7 @@ test_that("AKI stage is calculated correctly", {
force_numeric = TRUE,
verbose = FALSE
)
# increase to 2x baseline
test3 <- calc_aki_stage(
scr = 1.9,
t = 72,
Expand All @@ -45,14 +51,48 @@ test_that("AKI stage is calculated correctly", {
return_obj = FALSE,
verbose = FALSE
)
# increase to 1.5x baseline, even though absolute increase is <0.3 mg/dl
test4 <- calc_aki_stage(
scr = 0.45,
t = 72,
baseline_scr = 0.3,
egfr = 40,
age = 40,
return_obj = FALSE,
verbose = FALSE
)
# increase to 3x baseline
test5 <- calc_aki_stage(
scr = 6,
t = 48,
baseline_scr = 2,
egfr = 40,
age = 40,
return_obj = FALSE,
verbose = FALSE
)
# increase by >=0.3 mg/dl BUT we don't know how fast
test6 <- calc_aki_stage(
scr = 1.5,
t = 0,
baseline_scr = 1.1,
egfr = 40,
age = 40,
return_obj = FALSE,
verbose = FALSE
)

expect_equal(test0$stage, "stage 3")
expect_equal(test1$stage, "stage 1")
expect_equal(test2$stage, "stage 3")
expect_equal(test2$time_max_stage, 36)
expect_equal(test2a$stage, "stage 1")
expect_equal(test2a$time_max_stage, 48)
expect_equal(test2a$time_max_stage, 36)
expect_equal(test2b, 1)
expect_equal(test3, "stage 2")
expect_equal(test4, "stage 1")
expect_equal(test5, "stage 3")
expect_true(is.na(test6))
})


Expand All @@ -69,7 +109,7 @@ test_that("AKI stage is calculated correctly with other methods", {
verbose = FALSE
)
test4 <- calc_aki_stage(
scr = c(.6, .7, .9, .7),
scr = c(.6, .7, .8, .7),
times = 1:4,
sex = "male",
age = 50,
Expand Down Expand Up @@ -226,3 +266,27 @@ test_that("Times are sorted", {
)
expect_true(is.na(akis$stage))
})

test_that("age is not required if egfr is provided", {
res <- calc_aki_stage(
scr = 5,
times = 0,
egfr = 30,
baseline_egfr = 30,
method = "KDIGO",
verbose = FALSE
)
# Stage 3 because scr >= 4
expect_equal(res$stage, "stage 3")
})

test_that("kdigo_stage doesn't throw warning if no scr in last 48h", {
dat <- data.frame(
scr = c(1.5, 1.5, 1.9),
t = c(-1, 0, 55),
baseline_scr_diff = c(0, 0, 0.4),
egfr = c(100, 100, 60)
)

expect_warning(kdigo_stage(dat, 1.5, 50), NA)
})
21 changes: 21 additions & 0 deletions tests/testthat/test_utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,24 @@ test_that("remove_lt_gt() returns original values if passed numeric vector", {
test_that("remove_lt_gt() leaves negative numbers alone", {
expect_equal(remove_lt_gt("-<0.6"), -0.6)
})

test_that("%>=% operator works", {
expect_true((0.7 - 0.4) %>=% 0.3)
})

test_that("%>=% operator works on vectors", {
res <- c(1, 2, 3, 4) %>=% c(0, 3, 1, 5)
expect_equal(res, c(TRUE, FALSE, TRUE, FALSE))
})

test_that("%>=% operator handles NAs like >=", {
expect_true(is.na(5 %>=% NA))
expect_true(is.na(NA %>=% 6))
res <- c(1, NA) %>=% 0
expect_equal(res, c(TRUE, NA))
})

test_that("%>=% operator handles vectors of different lengths", {
expect_equal(2 %>=% c(1, 3), c(TRUE, FALSE))
expect_equal(c(1, 3) %>=% 2, c(FALSE, TRUE))
})