From 528bcedc4679f8423b95ffb7e9603e2522feb7e2 Mon Sep 17 00:00:00 2001 From: "Lin, Chanjuan {MDBJ~Shanghai}" Date: Mon, 27 Mar 2023 09:44:54 +0200 Subject: [PATCH 01/14] create prototype of bland altman analysis --- R/bland_altman.R | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 R/bland_altman.R diff --git a/R/bland_altman.R b/R/bland_altman.R new file mode 100644 index 0000000000..a6c1c49a4b --- /dev/null +++ b/R/bland_altman.R @@ -0,0 +1,41 @@ +s_bland_altman_cj <- function(x, y, conf_level = 0.95, group = NULL){ + + alpha <- 1 - conf_level + + ind <- complete.cases(x, y) # use only pairwise complete observations, and check if x and y have the same length + x <- x[ind] + y <- y[ind] + group <- group[ind] + + difference <- x - y # vector of differences + average <- (x + y) / 2 # vector of means + difference_mean <- mean(difference) # mean difference + difference_sd <- sd(difference) # SD of differences + al <- qnorm(1 - alpha / 2) * difference_sd + upper_agreement_limit <- difference_mean + al # agreement limits + lower_agreement_limit <- difference_mean - al + n <- length(difference) # number of 'observations' + + difference_se <- difference_sd / sqrt(n) # standard error of the mean + al_se <- difference_sd * sqrt(3) / sqrt(n) # standard error of the agreement limit + tvalue <- qt(1 - alpha / 2, n - 1) # t value for 95% CI calculation + difference_mean_ci <- difference_se * tvalue + al_ci <- al_se * tvalue + upper_agreement_limit_ci <- c(upper_agreement_limit - al_ci, upper_agreement_limit + al_ci) + lower_agreement_limit_ci <- c(lower_agreement_limit - al_ci, lower_agreement_limit + al_ci) + + + list( + difference_mean = difference_mean, + ci_mean = difference_mean + c(-1, 1) * difference_mean_ci, + difference_sd = difference_sd, + difference_se = difference_se, + upper_agreement_limit = upper_agreement_limit, + lower_agreement_limit = lower_agreement_limit, + agreement_limit_se = al_se, + upper_agreement_limit_ci = upper_agreement_limit_ci, + lower_agreement_limit_ci = lower_agreement_limit_ci, + t_value = tvalue, + n = n + ) +} From d776173e6175dfdfa8b8212bc3771550c851f2ca Mon Sep 17 00:00:00 2001 From: "Lin, Chanjuan {MDBJ~Shanghai}" Date: Mon, 27 Mar 2023 09:44:54 +0200 Subject: [PATCH 02/14] create prototype of bland altman analysis add the documentation --- R/bland_altman.R | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 R/bland_altman.R diff --git a/R/bland_altman.R b/R/bland_altman.R new file mode 100644 index 0000000000..5ac0ae220c --- /dev/null +++ b/R/bland_altman.R @@ -0,0 +1,57 @@ +#' Bland Altman analysis +#' +#' @description 'r lifecycle::badge("stable")' +#' +#' @inheritParams argument_convention +#' @param y ('numeric')\cr vector of numbers we want to analyze. +#' +#' @name s_bland_altman +#' +#' @examples +#' x = seq(1, 60, 5) +#' y = seq(5, 50, 4) +#' conf_level = 0.9 +#' s_bland_altman(x, y, conf_level = conf_level) + + +s_bland_altman_cj <- function(x, y, conf_level = 0.95, group = NULL){ + + alpha <- 1 - conf_level + + ind <- complete.cases(x, y) # use only pairwise complete observations, and check if x and y have the same length + x <- x[ind] + y <- y[ind] + group <- group[ind] + + difference <- x - y # vector of differences + average <- (x + y) / 2 # vector of means + difference_mean <- mean(difference) # mean difference + difference_sd <- sd(difference) # SD of differences + al <- qnorm(1 - alpha / 2) * difference_sd + upper_agreement_limit <- difference_mean + al # agreement limits + lower_agreement_limit <- difference_mean - al + n <- length(difference) # number of 'observations' + + difference_se <- difference_sd / sqrt(n) # standard error of the mean + al_se <- difference_sd * sqrt(3) / sqrt(n) # standard error of the agreement limit + tvalue <- qt(1 - alpha / 2, n - 1) # t value for 95% CI calculation + difference_mean_ci <- difference_se * tvalue + al_ci <- al_se * tvalue + upper_agreement_limit_ci <- c(upper_agreement_limit - al_ci, upper_agreement_limit + al_ci) + lower_agreement_limit_ci <- c(lower_agreement_limit - al_ci, lower_agreement_limit + al_ci) + + + list( + difference_mean = difference_mean, + ci_mean = difference_mean + c(-1, 1) * difference_mean_ci, + difference_sd = difference_sd, + difference_se = difference_se, + upper_agreement_limit = upper_agreement_limit, + lower_agreement_limit = lower_agreement_limit, + agreement_limit_se = al_se, + upper_agreement_limit_ci = upper_agreement_limit_ci, + lower_agreement_limit_ci = lower_agreement_limit_ci, + t_value = tvalue, + n = n + ) +} From 49cff5ac9c17b36419a6706a8e0a8778eedc934a Mon Sep 17 00:00:00 2001 From: "Lin, Chanjuan {MDBJ~Shanghai}" Date: Wed, 26 Apr 2023 10:28:23 +0200 Subject: [PATCH 03/14] update --- DESCRIPTION | 1 + R/bland_altman.R | 28 +++++++++++---- tests/testthat/test-bland-altman.R | 56 ++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 tests/testthat/test-bland-altman.R diff --git a/DESCRIPTION b/DESCRIPTION index d031c7ce6a..93bb21bcb9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -76,6 +76,7 @@ Collate: 'abnormal_by_worst_grade_worsen.R' 'analyze_vars_in_cols.R' 'argument_convention.R' + 'bland_altman.R' 'combination_function.R' 'summarize_variables.R' 'compare_variables.R' diff --git a/R/bland_altman.R b/R/bland_altman.R index 5ac0ae220c..692e3f034f 100644 --- a/R/bland_altman.R +++ b/R/bland_altman.R @@ -6,22 +6,38 @@ #' @param y ('numeric')\cr vector of numbers we want to analyze. #' #' @name s_bland_altman -#' +#' @details +#' https://doi.org/10.1016/S0140-6736(86)90837-8 +#' @references +#' @article{bland1986statistical, +# title={Statistical methods for assessing agreement between two methods of clinical measurement}, +# author={Bland, J Martin and Altman, DouglasG}, +# journal={The lancet}, +# volume={327}, +# number={8476}, +# pages={307--310}, +# year={1986}, +# publisher={Elsevier} +# } +# https://cran.r-project.org/web/packages/Rdpack/vignettes/Inserting_bibtex_references.pdf + #' @examples -#' x = seq(1, 60, 5) -#' y = seq(5, 50, 4) -#' conf_level = 0.9 +#' x <- seq(1, 60, 5) +#' y <- seq(5, 50, 4) +#' conf_level <- 0.9 #' s_bland_altman(x, y, conf_level = conf_level) -s_bland_altman_cj <- function(x, y, conf_level = 0.95, group = NULL){ +s_bland_altman <- function(x, y, conf_level = 0.95){ + checkmate::assert_numeric(x, min.len = 1, any.missing = TRUE) + checkmate::assert_numeric(y, len = length(x), any.missing = TRUE) + checkmate::assert_numeric(conf_level, lower = 0, upper = 1, any.missing = TRUE) alpha <- 1 - conf_level ind <- complete.cases(x, y) # use only pairwise complete observations, and check if x and y have the same length x <- x[ind] y <- y[ind] - group <- group[ind] difference <- x - y # vector of differences average <- (x + y) / 2 # vector of means diff --git a/tests/testthat/test-bland-altman.R b/tests/testthat/test-bland-altman.R new file mode 100644 index 0000000000..6daea62b8f --- /dev/null +++ b/tests/testthat/test-bland-altman.R @@ -0,0 +1,56 @@ +testthat::test_that("unequal length vector gives correct error", { + testthat::expect_error(s_bland_altman (x = 1:5, y = 1:6, 0.95)) +}) + +testthat::test_that("infeasible input gives correct error", { + testthat::expect_error(s_bland_altman (x = c('a', 'b', 'c'), y = 1:3, 0.95)) + testthat::expect_error(s_bland_altman (x = 1:3, y = 4:6, 2)) +}) + + +testthat::test_that("works correctly", { + set.seed(1) + x = rnorm(20) + y = rnorm(20) + res = s_bland_altman(x, y, 0.9) + expect = list( + difference_mean = mean(x) - mean(y), + ci_mean = c(-0.3414723, 0.7354631), + difference_sd = 1.392664, + difference_se = 0.3114091, + upper_agreement_limit = 2.487724, + lower_agreement_limit = -2.093733, + agreement_limit_se = 0.5393764, + upper_agreement_limit_ci = c(1.555070, 3.420377), + lower_agreement_limit_ci = c(-3.026386,-1.161079), + t_value = 1.729133, + n = 20L + ) + expect_identical(res, expect, tolerance = 1e-5) + + +}) + + +testthat::test_that("works correctly with NA element in either vectors", { + set.seed(1) + x = rnorm(20) + y = rnorm(20) + x = c(NA_real_, 2, x, NA_real_) + y = c(1, NA_real_, y, 2) + res = s_bland_altman(x, y, 0.9) + expect = list( + difference_mean = 0.1969954, + ci_mean = c(-0.3414723, 0.7354631), + difference_sd = 1.392664, + difference_se = 0.3114091, + upper_agreement_limit = 2.487724, + lower_agreement_limit = -2.093733, + agreement_limit_se = 0.5393764, + upper_agreement_limit_ci = c(1.555070, 3.420377), + lower_agreement_limit_ci = c(-3.026386,-1.161079), + t_value = 1.729133, + n = 20L + ) + expect_identical(res, expect) +}) From 7ad3a101ba9722e98274afbe026ea8dee838e017 Mon Sep 17 00:00:00 2001 From: "Lin, Chanjuan {MDBJ~Shanghai}" Date: Tue, 5 Dec 2023 07:41:43 +0100 Subject: [PATCH 04/14] use <- to replace = --- R/bland_altman.R | 8 +++++++- man/s_bland_altman.Rd | 30 ++++++++++++++++++++++++++++++ tests/testthat/test-bland-altman.R | 20 ++++++++++---------- 3 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 man/s_bland_altman.Rd diff --git a/R/bland_altman.R b/R/bland_altman.R index c3972675f3..6b419c7a20 100644 --- a/R/bland_altman.R +++ b/R/bland_altman.R @@ -39,6 +39,12 @@ s_bland_altman <- function(x, y, conf_level = 0.95){ ind <- complete.cases(x, y) # use only pairwise complete observations, and check if x and y have the same length x <- x[ind] y <- y[ind] + n <- length(n) # number of 'observations' + + if(n ==0){ + stop("there is no valid paired data") + } + difference <- x - y # vector of differences average <- (x + y) / 2 # vector of means difference_mean <- mean(difference) # mean difference @@ -46,7 +52,7 @@ s_bland_altman <- function(x, y, conf_level = 0.95){ al <- qnorm(1 - alpha / 2) * difference_sd upper_agreement_limit <- difference_mean + al # agreement limits lower_agreement_limit <- difference_mean - al - n <- length(difference) # number of 'observations' + difference_se <- difference_sd / sqrt(n) # standard error of the mean al_se <- difference_sd * sqrt(3) / sqrt(n) # standard error of the agreement limit diff --git a/man/s_bland_altman.Rd b/man/s_bland_altman.Rd new file mode 100644 index 0000000000..71db46adb9 --- /dev/null +++ b/man/s_bland_altman.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/bland_altman.R +\name{s_bland_altman} +\alias{s_bland_altman} +\title{Bland Altman analysis} +\usage{ +s_bland_altman(x, y, conf_level = 0.95) +} +\arguments{ +\item{x}{(\code{numeric})\cr vector of numbers we want to analyze.} + +\item{y}{('numeric')\cr vector of numbers we want to analyze.} + +\item{conf_level}{(\code{proportion})\cr confidence level of the interval.} +} +\description{ +'r lifecycle::badge("stable")' +} +\details{ +https://doi.org/10.1016/S0140-6736(86)90837-8 +} +\examples{ +x <- seq(1, 60, 5) +y <- seq(5, 50, 4) +conf_level <- 0.9 +s_bland_altman(x, y, conf_level = conf_level) +} +\references{ + +} diff --git a/tests/testthat/test-bland-altman.R b/tests/testthat/test-bland-altman.R index 6daea62b8f..5eb4e0c446 100644 --- a/tests/testthat/test-bland-altman.R +++ b/tests/testthat/test-bland-altman.R @@ -10,10 +10,10 @@ testthat::test_that("infeasible input gives correct error", { testthat::test_that("works correctly", { set.seed(1) - x = rnorm(20) - y = rnorm(20) - res = s_bland_altman(x, y, 0.9) - expect = list( + x <- rnorm(20) + y <- rnorm(20) + res <- s_bland_altman(x, y, 0.9) + expect <- list( difference_mean = mean(x) - mean(y), ci_mean = c(-0.3414723, 0.7354631), difference_sd = 1.392664, @@ -34,12 +34,12 @@ testthat::test_that("works correctly", { testthat::test_that("works correctly with NA element in either vectors", { set.seed(1) - x = rnorm(20) - y = rnorm(20) - x = c(NA_real_, 2, x, NA_real_) - y = c(1, NA_real_, y, 2) - res = s_bland_altman(x, y, 0.9) - expect = list( + x <- rnorm(20) + y <- rnorm(20) + x <- c(NA_real_, 2, x, NA_real_) + y <- c(1, NA_real_, y, 2) + res <- s_bland_altman(x, y, 0.9) + expect <- list( difference_mean = 0.1969954, ci_mean = c(-0.3414723, 0.7354631), difference_sd = 1.392664, From 736ed010aeea678a6e39e6f9d547a41f5d01735e Mon Sep 17 00:00:00 2001 From: "Lin, Chanjuan {MDBJ~Shanghai}" Date: Tue, 5 Dec 2023 09:07:55 +0100 Subject: [PATCH 05/14] modified a little bit per received comment: 1. use <- instead of = 2. remove redundant line break 3. add more clarity for parameter description 4. add more details for @description 5. move the bib information to REFERENCES.bib under tern --- NEWS.md | 1 + R/bland_altman.R | 20 +++----------------- inst/REFERENCES.bib | 20 ++++++++++++++++++++ tests/testthat/test-bland-altman.R | 2 -- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/NEWS.md b/NEWS.md index c74a03bd37..38d1ebdae0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -12,6 +12,7 @@ * Added summarize function version of `count_occurrences` analyze function, `summarize_occurrences`. * Added referential footnotes to `surv_time` for censored range observations, controlled via the `ref_fn_censor` parameter. * Added helper function `h_adlb_abnormal_by_worst_grade` to prepare `ADLB` data to use as input in `count_abnormal_by_worst_grade`. +* Added `s_bland_altman` function to assess agreement between two numerical vectors. ### Enhancements * Added `ref_group_coxph` parameter to `g_km` to specify the reference group used for pairwise Cox-PH calculations when `annot_coxph = TRUE`. diff --git a/R/bland_altman.R b/R/bland_altman.R index 6b419c7a20..18cb2d181d 100644 --- a/R/bland_altman.R +++ b/R/bland_altman.R @@ -1,26 +1,12 @@ - #' Bland Altman analysis #' -#' @description 'r lifecycle::badge("stable")' +#' @description 'r lifecycle::badge("experimental")' +#' This function uses bland altman method to assess the agreement between two numerical vectors. #' #' @inheritParams argument_convention -#' @param y ('numeric')\cr vector of numbers we want to analyze. +#' @param y ('numeric')\cr vector of numbers we want to analyze, which we want to compare with x. #' #' @name s_bland_altman -#' @details -#' https://doi.org/10.1016/S0140-6736(86)90837-8 -#' @references -#' @article{bland1986statistical, -# title={Statistical methods for assessing agreement between two methods of clinical measurement}, -# author={Bland, J Martin and Altman, DouglasG}, -# journal={The lancet}, -# volume={327}, -# number={8476}, -# pages={307--310}, -# year={1986}, -# publisher={Elsevier} -# } -# https://cran.r-project.org/web/packages/Rdpack/vignettes/Inserting_bibtex_references.pdf #' @examples #' x <- seq(1, 60, 5) diff --git a/inst/REFERENCES.bib b/inst/REFERENCES.bib index 879f39c540..2cfea6b9ae 100644 --- a/inst/REFERENCES.bib +++ b/inst/REFERENCES.bib @@ -51,3 +51,23 @@ @ARTICLE{Schouten1980-kd year = 1980, language = "en" } + +@ARTICLE{bland1986statistical, + title = "Statistical methods for assessing agreement between two methods of clinical measurement", + author = "Bland, J Martin and Altman, DouglasG", + abstract = "In clinical measurement comparison of a new measurement technique + with an established one is often needed to see whether they agree + sufficiently for the new to replace the old. Such investigations + are often analysed inappropriately, notably by using correlation + coefficients. The use of correlation is misleading. An alternative + approach, based on graphical techniques and simple calculations, + is described, together with the relation between this analysis and + the assessment of repeatability.", + journal = "The lancet", + publisher = "Elsevier", + volume ="327", + number ="8476", + pages ="307--310", + year ="1986", + language = "en" +} diff --git a/tests/testthat/test-bland-altman.R b/tests/testthat/test-bland-altman.R index 5eb4e0c446..7f9ddec0dd 100644 --- a/tests/testthat/test-bland-altman.R +++ b/tests/testthat/test-bland-altman.R @@ -27,8 +27,6 @@ testthat::test_that("works correctly", { n = 20L ) expect_identical(res, expect, tolerance = 1e-5) - - }) From 09c3319b6b4667fe9753cdeadf80dafbf71b199a Mon Sep 17 00:00:00 2001 From: shajoezhu Date: Fri, 12 Jan 2024 11:31:19 +0800 Subject: [PATCH 06/14] restyle files --- R/bland_altman.R | 24 +++++++++++------------- tests/testthat/test-bland-altman.R | 10 +++++----- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/R/bland_altman.R b/R/bland_altman.R index 18cb2d181d..fb775076a0 100644 --- a/R/bland_altman.R +++ b/R/bland_altman.R @@ -13,9 +13,7 @@ #' y <- seq(5, 50, 4) #' conf_level <- 0.9 #' s_bland_altman(x, y, conf_level = conf_level) - - -s_bland_altman <- function(x, y, conf_level = 0.95){ +s_bland_altman <- function(x, y, conf_level = 0.95) { checkmate::assert_numeric(x, min.len = 1, any.missing = TRUE) checkmate::assert_numeric(y, len = length(x), any.missing = TRUE) checkmate::assert_numeric(conf_level, lower = 0, upper = 1, any.missing = TRUE) @@ -25,24 +23,24 @@ s_bland_altman <- function(x, y, conf_level = 0.95){ ind <- complete.cases(x, y) # use only pairwise complete observations, and check if x and y have the same length x <- x[ind] y <- y[ind] - n <- length(n) # number of 'observations' + n <- length(n) # number of 'observations' - if(n ==0){ + if (n == 0) { stop("there is no valid paired data") } - difference <- x - y # vector of differences - average <- (x + y) / 2 # vector of means - difference_mean <- mean(difference) # mean difference - difference_sd <- sd(difference) # SD of differences + difference <- x - y # vector of differences + average <- (x + y) / 2 # vector of means + difference_mean <- mean(difference) # mean difference + difference_sd <- sd(difference) # SD of differences al <- qnorm(1 - alpha / 2) * difference_sd - upper_agreement_limit <- difference_mean + al # agreement limits + upper_agreement_limit <- difference_mean + al # agreement limits lower_agreement_limit <- difference_mean - al - difference_se <- difference_sd / sqrt(n) # standard error of the mean - al_se <- difference_sd * sqrt(3) / sqrt(n) # standard error of the agreement limit - tvalue <- qt(1 - alpha / 2, n - 1) # t value for 95% CI calculation + difference_se <- difference_sd / sqrt(n) # standard error of the mean + al_se <- difference_sd * sqrt(3) / sqrt(n) # standard error of the agreement limit + tvalue <- qt(1 - alpha / 2, n - 1) # t value for 95% CI calculation difference_mean_ci <- difference_se * tvalue al_ci <- al_se * tvalue upper_agreement_limit_ci <- c(upper_agreement_limit - al_ci, upper_agreement_limit + al_ci) diff --git a/tests/testthat/test-bland-altman.R b/tests/testthat/test-bland-altman.R index 7f9ddec0dd..02ada24512 100644 --- a/tests/testthat/test-bland-altman.R +++ b/tests/testthat/test-bland-altman.R @@ -1,10 +1,10 @@ testthat::test_that("unequal length vector gives correct error", { - testthat::expect_error(s_bland_altman (x = 1:5, y = 1:6, 0.95)) + testthat::expect_error(s_bland_altman(x = 1:5, y = 1:6, 0.95)) }) testthat::test_that("infeasible input gives correct error", { - testthat::expect_error(s_bland_altman (x = c('a', 'b', 'c'), y = 1:3, 0.95)) - testthat::expect_error(s_bland_altman (x = 1:3, y = 4:6, 2)) + testthat::expect_error(s_bland_altman(x = c("a", "b", "c"), y = 1:3, 0.95)) + testthat::expect_error(s_bland_altman(x = 1:3, y = 4:6, 2)) }) @@ -22,7 +22,7 @@ testthat::test_that("works correctly", { lower_agreement_limit = -2.093733, agreement_limit_se = 0.5393764, upper_agreement_limit_ci = c(1.555070, 3.420377), - lower_agreement_limit_ci = c(-3.026386,-1.161079), + lower_agreement_limit_ci = c(-3.026386, -1.161079), t_value = 1.729133, n = 20L ) @@ -46,7 +46,7 @@ testthat::test_that("works correctly with NA element in either vectors", { lower_agreement_limit = -2.093733, agreement_limit_se = 0.5393764, upper_agreement_limit_ci = c(1.555070, 3.420377), - lower_agreement_limit_ci = c(-3.026386,-1.161079), + lower_agreement_limit_ci = c(-3.026386, -1.161079), t_value = 1.729133, n = 20L ) From dccef420c2ceea2d6511958de368a57e7a6cf05b Mon Sep 17 00:00:00 2001 From: "Lin, Chanjuan {MDBJ~Shanghai}" Date: Thu, 18 Jan 2024 06:17:16 +0100 Subject: [PATCH 07/14] add bland altman plot --- R/bland_altman.R | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/R/bland_altman.R b/R/bland_altman.R index fb775076a0..c32c2732db 100644 --- a/R/bland_altman.R +++ b/R/bland_altman.R @@ -23,7 +23,7 @@ s_bland_altman <- function(x, y, conf_level = 0.95) { ind <- complete.cases(x, y) # use only pairwise complete observations, and check if x and y have the same length x <- x[ind] y <- y[ind] - n <- length(n) # number of 'observations' + n <- length(ind) # number of 'observations' if (n == 0) { stop("there is no valid paired data") @@ -61,3 +61,36 @@ s_bland_altman <- function(x, y, conf_level = 0.95) { n = n ) } + + +#' @inheritParams s_bland_altman +#' @rdname s_bland_altman +#' @examples +#' x <- seq(1, 60, 5) +#' y <- seq(5, 50, 4) +#' conf_level <- 0.9 +#' g_bland_altman(x, y, conf_level = conf_level) +#' +g_bland_altman <- function(x, y, conf_level = 0.95){ + result_tem <- s_bland_altman(x,y,conf_level = conf_level) + xpos <- max(result_tem$df$average) * 0.9 + min(result_tem$df$average) * 0.1 + yrange <- diff(range(result_tem$df$difference)) + + p <- ggplot(result_tem$df) + + geom_point(aes(x = average, y = difference), color = "blue") + + geom_hline(yintercept = result_tem$difference_mean, color = "blue", linetype = 1) + + geom_hline(yintercept = 0, color = "blue", linetype = 2) + + geom_hline(yintercept = result_tem$lower_agreement_limit, color = "red", linetype = 2) + + geom_hline(yintercept = result_tem$upper_agreement_limit, color = "red", linetype = 2) + + annotate("text", x = xpos, y = result_tem$lower_agreement_limit + 0.03 * yrange, label = "lower limits of agreement", color = "red") + + annotate("text", x = xpos, y = result_tem$upper_agreement_limit + 0.03 * yrange, label = "upper limits of agreement", color = "red") + + annotate("text", x = xpos, y = result_tem$difference_mean + 0.03 * yrange, label = "mean of difference between two measures", color = "blue") + + annotate("text", x = xpos, y = result_tem$lower_agreement_limit - 0.03 * yrange, label = sprintf("%.2f", result_tem$lower_agreement_limit), color = "red") + + annotate("text", x = xpos, y = result_tem$upper_agreement_limit - 0.03 * yrange, label = sprintf("%.2f", result_tem$upper_agreement_limit), color = "red") + + annotate("text", x = xpos, y = result_tem$difference_mean - 0.03 * yrange, label = sprintf("%.2f", result_tem$difference_meanm), color = "blue") + + xlab("average of two measures") + + ylab("difference between two measures") + + return(p) +} + From 6bf39fa7525104fbc179c2ae1adbf0ba2bcd28bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chanjuan=20Lin=20=EF=BC=88=E6=9E=97=E5=A9=B5=E5=A8=9F?= =?UTF-8?q?=EF=BC=89?= Date: Thu, 18 Jan 2024 05:38:56 +0000 Subject: [PATCH 08/14] add data.frame, which is lost by accident --- R/bland_altman.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/bland_altman.R b/R/bland_altman.R index c32c2732db..31ab1ade20 100644 --- a/R/bland_altman.R +++ b/R/bland_altman.R @@ -48,6 +48,7 @@ s_bland_altman <- function(x, y, conf_level = 0.95) { list( + df= data.frame(average, difference), difference_mean = difference_mean, ci_mean = difference_mean + c(-1, 1) * difference_mean_ci, difference_sd = difference_sd, From cd6dc468d8c55e5157db849ded317cadcfd64733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chanjuan=20Lin=20=EF=BC=88=E6=9E=97=E5=A9=B5=E5=A8=9F?= =?UTF-8?q?=EF=BC=89?= Date: Thu, 18 Jan 2024 05:42:42 +0000 Subject: [PATCH 09/14] render docs --- man/s_bland_altman.Rd | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/man/s_bland_altman.Rd b/man/s_bland_altman.Rd index 71db46adb9..5eba2ae478 100644 --- a/man/s_bland_altman.Rd +++ b/man/s_bland_altman.Rd @@ -2,29 +2,32 @@ % Please edit documentation in R/bland_altman.R \name{s_bland_altman} \alias{s_bland_altman} +\alias{g_bland_altman} \title{Bland Altman analysis} \usage{ s_bland_altman(x, y, conf_level = 0.95) + +g_bland_altman(x, y, conf_level = 0.95) } \arguments{ \item{x}{(\code{numeric})\cr vector of numbers we want to analyze.} -\item{y}{('numeric')\cr vector of numbers we want to analyze.} +\item{y}{('numeric')\cr vector of numbers we want to analyze, which we want to compare with x.} \item{conf_level}{(\code{proportion})\cr confidence level of the interval.} } \description{ -'r lifecycle::badge("stable")' -} -\details{ -https://doi.org/10.1016/S0140-6736(86)90837-8 +'r lifecycle::badge("experimental")' +This function uses bland altman method to assess the agreement between two numerical vectors. } \examples{ x <- seq(1, 60, 5) y <- seq(5, 50, 4) conf_level <- 0.9 s_bland_altman(x, y, conf_level = conf_level) -} -\references{ +x <- seq(1, 60, 5) +y <- seq(5, 50, 4) +conf_level <- 0.9 +g_bland_altman(x, y, conf_level = conf_level) } From 7b37dab236025d6a0dffdbc218f5c3a6fe7c2372 Mon Sep 17 00:00:00 2001 From: shajoezhu Date: Thu, 18 Jan 2024 17:31:01 +0800 Subject: [PATCH 10/14] update style --- R/bland_altman.R | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/R/bland_altman.R b/R/bland_altman.R index 31ab1ade20..92ef25357d 100644 --- a/R/bland_altman.R +++ b/R/bland_altman.R @@ -48,7 +48,7 @@ s_bland_altman <- function(x, y, conf_level = 0.95) { list( - df= data.frame(average, difference), + df = data.frame(average, difference), difference_mean = difference_mean, ci_mean = difference_mean + c(-1, 1) * difference_mean_ci, difference_sd = difference_sd, @@ -72,8 +72,8 @@ s_bland_altman <- function(x, y, conf_level = 0.95) { #' conf_level <- 0.9 #' g_bland_altman(x, y, conf_level = conf_level) #' -g_bland_altman <- function(x, y, conf_level = 0.95){ - result_tem <- s_bland_altman(x,y,conf_level = conf_level) +g_bland_altman <- function(x, y, conf_level = 0.95) { + result_tem <- s_bland_altman(x, y, conf_level = conf_level) xpos <- max(result_tem$df$average) * 0.9 + min(result_tem$df$average) * 0.1 yrange <- diff(range(result_tem$df$difference)) @@ -94,4 +94,3 @@ g_bland_altman <- function(x, y, conf_level = 0.95){ return(p) } - From d92a35fd00a54bc1835f8a03cc41c03926eaf711 Mon Sep 17 00:00:00 2001 From: shajoezhu Date: Fri, 19 Jan 2024 00:04:08 +0800 Subject: [PATCH 11/14] rename help file --- NAMESPACE | 2 + R/bland_altman.R | 48 +++++++++++++--------- man/{s_bland_altman.Rd => bland_altman.Rd} | 21 ++++++---- 3 files changed, 44 insertions(+), 27 deletions(-) rename man/{s_bland_altman.Rd => bland_altman.Rd} (58%) diff --git a/NAMESPACE b/NAMESPACE index 9780039cdd..0f88aa7207 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -124,6 +124,7 @@ export(format_fraction_fixed_dp) export(format_fraction_threshold) export(format_sigfig) export(format_xx) +export(g_bland_altman) export(g_forest) export(g_ipp) export(g_km) @@ -243,6 +244,7 @@ export(prop_wilson) export(reapply_varlabels) export(ref_group_position) export(rtable2gg) +export(s_bland_altman) export(s_compare) export(s_count_occurrences) export(s_count_occurrences_by_grade) diff --git a/R/bland_altman.R b/R/bland_altman.R index 92ef25357d..92373a6d78 100644 --- a/R/bland_altman.R +++ b/R/bland_altman.R @@ -1,18 +1,26 @@ #' Bland Altman analysis #' -#' @description 'r lifecycle::badge("experimental")' -#' This function uses bland altman method to assess the agreement between two numerical vectors. +#' @description `r lifecycle::badge("experimental")` +#' +#' Functions of bland altman method to assess the agreement between two numerical vectors. #' #' @inheritParams argument_convention #' @param y ('numeric')\cr vector of numbers we want to analyze, which we want to compare with x. #' -#' @name s_bland_altman - +#' @name bland_altman #' @examples #' x <- seq(1, 60, 5) #' y <- seq(5, 50, 4) #' conf_level <- 0.9 +#' # Derive statistics that are needed for Bland Altman plot #' s_bland_altman(x, y, conf_level = conf_level) +#' # Create a Bland Altman plot +#' g_bland_altman(x, y, conf_level = conf_level) +NULL + +#' @describeIn bland_altman +#' +#' @export s_bland_altman <- function(x, y, conf_level = 0.95) { checkmate::assert_numeric(x, min.len = 1, any.missing = TRUE) checkmate::assert_numeric(y, len = length(x), any.missing = TRUE) @@ -63,15 +71,9 @@ s_bland_altman <- function(x, y, conf_level = 0.95) { ) } - -#' @inheritParams s_bland_altman -#' @rdname s_bland_altman -#' @examples -#' x <- seq(1, 60, 5) -#' y <- seq(5, 50, 4) -#' conf_level <- 0.9 -#' g_bland_altman(x, y, conf_level = conf_level) +#' @describeIn bland_altman #' +#' @export g_bland_altman <- function(x, y, conf_level = 0.95) { result_tem <- s_bland_altman(x, y, conf_level = conf_level) xpos <- max(result_tem$df$average) * 0.9 + min(result_tem$df$average) * 0.1 @@ -83,14 +85,20 @@ g_bland_altman <- function(x, y, conf_level = 0.95) { geom_hline(yintercept = 0, color = "blue", linetype = 2) + geom_hline(yintercept = result_tem$lower_agreement_limit, color = "red", linetype = 2) + geom_hline(yintercept = result_tem$upper_agreement_limit, color = "red", linetype = 2) + - annotate("text", x = xpos, y = result_tem$lower_agreement_limit + 0.03 * yrange, label = "lower limits of agreement", color = "red") + - annotate("text", x = xpos, y = result_tem$upper_agreement_limit + 0.03 * yrange, label = "upper limits of agreement", color = "red") + - annotate("text", x = xpos, y = result_tem$difference_mean + 0.03 * yrange, label = "mean of difference between two measures", color = "blue") + - annotate("text", x = xpos, y = result_tem$lower_agreement_limit - 0.03 * yrange, label = sprintf("%.2f", result_tem$lower_agreement_limit), color = "red") + - annotate("text", x = xpos, y = result_tem$upper_agreement_limit - 0.03 * yrange, label = sprintf("%.2f", result_tem$upper_agreement_limit), color = "red") + - annotate("text", x = xpos, y = result_tem$difference_mean - 0.03 * yrange, label = sprintf("%.2f", result_tem$difference_meanm), color = "blue") + - xlab("average of two measures") + - ylab("difference between two measures") + annotate("text", x = xpos, y = result_tem$lower_agreement_limit + 0.03 * yrange, + label = "lower limits of agreement", color = "red") + + annotate("text", x = xpos, y = result_tem$upper_agreement_limit + 0.03 * yrange, + label = "upper limits of agreement", color = "red") + + annotate("text", x = xpos, y = result_tem$difference_mean + 0.03 * yrange, + label = "mean of difference between two measures", color = "blue") + + annotate("text", x = xpos, y = result_tem$lower_agreement_limit - 0.03 * yrange, + label = sprintf("%.2f", result_tem$lower_agreement_limit), color = "red") + + annotate("text", x = xpos, y = result_tem$upper_agreement_limit - 0.03 * yrange, + label = sprintf("%.2f", result_tem$upper_agreement_limit), color = "red") + + annotate("text", x = xpos, y = result_tem$difference_mean - 0.03 * yrange, + label = sprintf("%.2f", result_tem$difference_meanm), color = "blue") + + xlab("Average of two measures") + + ylab("Difference between two measures") return(p) } diff --git a/man/s_bland_altman.Rd b/man/bland_altman.Rd similarity index 58% rename from man/s_bland_altman.Rd rename to man/bland_altman.Rd index 5eba2ae478..a763b4b202 100644 --- a/man/s_bland_altman.Rd +++ b/man/bland_altman.Rd @@ -1,6 +1,7 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/bland_altman.R -\name{s_bland_altman} +\name{bland_altman} +\alias{bland_altman} \alias{s_bland_altman} \alias{g_bland_altman} \title{Bland Altman analysis} @@ -17,17 +18,23 @@ g_bland_altman(x, y, conf_level = 0.95) \item{conf_level}{(\code{proportion})\cr confidence level of the interval.} } \description{ -'r lifecycle::badge("experimental")' -This function uses bland altman method to assess the agreement between two numerical vectors. +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} + +Functions of bland altman method to assess the agreement between two numerical vectors. } +\section{Functions}{ +\itemize{ +\item \code{s_bland_altman()}: + +\item \code{g_bland_altman()}: + +}} \examples{ x <- seq(1, 60, 5) y <- seq(5, 50, 4) conf_level <- 0.9 +# Derive statistics that are needed for Bland Altman plot s_bland_altman(x, y, conf_level = conf_level) -x <- seq(1, 60, 5) -y <- seq(5, 50, 4) -conf_level <- 0.9 +# Create a Bland Altman plot g_bland_altman(x, y, conf_level = conf_level) - } From 242e298568ff001bceb249574636ae334359809d Mon Sep 17 00:00:00 2001 From: shajoezhu Date: Fri, 19 Jan 2024 00:41:17 +0800 Subject: [PATCH 12/14] update tests --- R/bland_altman.R | 2 +- tests/testthat/test-bland-altman.R | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/R/bland_altman.R b/R/bland_altman.R index 92373a6d78..a57356e6cc 100644 --- a/R/bland_altman.R +++ b/R/bland_altman.R @@ -31,7 +31,7 @@ s_bland_altman <- function(x, y, conf_level = 0.95) { ind <- complete.cases(x, y) # use only pairwise complete observations, and check if x and y have the same length x <- x[ind] y <- y[ind] - n <- length(ind) # number of 'observations' + n <- sum(ind) # number of 'observations' if (n == 0) { stop("there is no valid paired data") diff --git a/tests/testthat/test-bland-altman.R b/tests/testthat/test-bland-altman.R index 02ada24512..18cdb856bf 100644 --- a/tests/testthat/test-bland-altman.R +++ b/tests/testthat/test-bland-altman.R @@ -8,12 +8,15 @@ testthat::test_that("infeasible input gives correct error", { }) -testthat::test_that("works correctly", { +testthat::test_that("s_bland_altman works with two vectors", { set.seed(1) x <- rnorm(20) y <- rnorm(20) res <- s_bland_altman(x, y, 0.9) + average <- (x + y) / 2 + difference <- x - y expect <- list( + df = data.frame(average, difference), difference_mean = mean(x) - mean(y), ci_mean = c(-0.3414723, 0.7354631), difference_sd = 1.392664, @@ -30,14 +33,18 @@ testthat::test_that("works correctly", { }) -testthat::test_that("works correctly with NA element in either vectors", { +testthat::test_that("s_bland_altman works with two vectors with NA element in either vectors", { set.seed(1) x <- rnorm(20) y <- rnorm(20) x <- c(NA_real_, 2, x, NA_real_) y <- c(1, NA_real_, y, 2) res <- s_bland_altman(x, y, 0.9) + average <- (x + y) / 2 + difference <- x - y + df <- data.frame(na.omit(data.frame(average, difference)), row.names = NULL) expect <- list( + df = df, difference_mean = 0.1969954, ci_mean = c(-0.3414723, 0.7354631), difference_sd = 1.392664, @@ -50,5 +57,5 @@ testthat::test_that("works correctly with NA element in either vectors", { t_value = 1.729133, n = 20L ) - expect_identical(res, expect) + expect_identical(res, expect, tolerance = 1e-5) }) From 8429c6ae8c8c0ca0d7fb5e19340793ee0e09ccf1 Mon Sep 17 00:00:00 2001 From: shajoezhu Date: Fri, 19 Jan 2024 00:46:25 +0800 Subject: [PATCH 13/14] update word list --- inst/WORDLIST | 1 + 1 file changed, 1 insertion(+) diff --git a/inst/WORDLIST b/inst/WORDLIST index 5f67a2d233..3f22d828ef 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -37,3 +37,4 @@ subtables unformatted ungroup unstratified +altman From 678929a1f09905e75c2afb359944d7df616a3a03 Mon Sep 17 00:00:00 2001 From: shajoezhu Date: Fri, 19 Jan 2024 01:05:11 +0800 Subject: [PATCH 14/14] update namespace --- NAMESPACE | 4 ++++ R/package.R | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/NAMESPACE b/NAMESPACE index 0f88aa7207..61c8df366d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -318,7 +318,11 @@ importFrom(grid,widthDetails) importFrom(magrittr,"%>%") importFrom(methods,new) importFrom(rlang,.data) +importFrom(stats,complete.cases) importFrom(stats,pchisq) +importFrom(stats,qnorm) +importFrom(stats,qt) +importFrom(stats,sd) importFrom(stats,setNames) importFrom(survival,Surv) importFrom(survival,coxph) diff --git a/R/package.R b/R/package.R index 141412fe23..9fc47540c7 100644 --- a/R/package.R +++ b/R/package.R @@ -12,13 +12,15 @@ #' @importFrom Rdpack reprompt #' @importFrom rlang .data #' @importFrom survival coxph strata Surv -#' @importFrom stats pchisq setNames +#' @importFrom stats pchisq setNames complete.cases qnorm qt sd NULL # Resolve missing global definitions: utils::globalVariables(c( ".", "x", + "average", + "difference", "control_coxph", "control_incidence_rate", "control_analyze_vars",