# Do some basic tests first, then test more fine-tuned/aggressively via snapshots test_that("Base model works, female", { expect_equal( estimate_risk_partial(quiet = TRUE), list( risk_est_10yr = dplyr::tibble( total_cvd = 0.147, ascvd = 0.092, heart_failure = 0.081, chd = 0.044, stroke = 0.054, model = "base", over_years = 10, input_problems = NA_character_ ), risk_est_30yr = dplyr::tibble( total_cvd = 0.53, ascvd = 0.354, heart_failure = 0.39, chd = 0.198, stroke = 0.221, model = "base", over_years = 30, input_problems = NA_character_ ) ) ) }) test_that("HbA1c model works, female", { expect_equal( estimate_risk_partial(hba1c = 9.2, quiet = TRUE), list( risk_est_10yr = dplyr::tibble( total_cvd = 0.165, ascvd = 0.103, heart_failure = 0.107, chd = 0.055, stroke = 0.053, model = "hba1c", over_years = 10, input_problems = NA_character_ ), risk_est_30yr = dplyr::tibble( total_cvd = 0.541, ascvd = 0.356, heart_failure = 0.449, chd = 0.219, stroke = 0.2, model = "hba1c", over_years = 30, input_problems = NA_character_ ) ) ) }) test_that("UACR model works, female", { expect_equal( estimate_risk_partial(uacr = 92, quiet = TRUE), list( risk_est_10yr = dplyr::tibble( total_cvd = 0.181, ascvd = 0.111, heart_failure = 0.105, chd = 0.055, stroke = 0.065, model = "uacr", over_years = 10, input_problems = NA_character_ ), risk_est_30yr = dplyr::tibble( total_cvd = 0.565, ascvd = 0.381, heart_failure = 0.437, chd = 0.22, stroke = 0.241, model = "uacr", over_years = 30, input_problems = NA_character_ ) ) ) }) test_that("SDI model works, female", { expect_equal( estimate_risk_partial(zip = "14738", quiet = TRUE), list( risk_est_10yr = dplyr::tibble( total_cvd = 0.127, ascvd = 0.08, heart_failure = 0.07, chd = 0.038, stroke = 0.047, model = "sdi", over_years = 10, input_problems = NA_character_ ), risk_est_30yr = dplyr::tibble( total_cvd = 0.485, ascvd = 0.322, heart_failure = 0.358, chd = 0.179, stroke = 0.202, model = "sdi", over_years = 30, input_problems = NA_character_ ) ) ) }) test_that("Full model works, female", { expect_equal( estimate_risk_partial(hba1c = 9.2, uacr = 92, zip = "14738", quiet = TRUE), list( risk_est_10yr = dplyr::tibble( total_cvd = 0.174, ascvd = 0.105, heart_failure = 0.114, chd = 0.056, stroke = 0.056, model = "full", over_years = 10, input_problems = NA_character_ ), risk_est_30yr = dplyr::tibble( total_cvd = 0.534, ascvd = 0.348, heart_failure = 0.443, chd = 0.213, stroke = 0.204, model = "full", over_years = 30, input_problems = NA_character_ ) ) ) }) test_that("Base model works, male", { expect_equal( estimate_risk_partial(sex = "male", quiet = TRUE), list( risk_est_10yr = dplyr::tibble( total_cvd = 0.163, ascvd = 0.102, heart_failure = 0.106, chd = 0.056, stroke = 0.052, model = "base", over_years = 10, input_problems = NA_character_ ), risk_est_30yr = dplyr::tibble( total_cvd = 0.514, ascvd = 0.349, heart_failure = 0.424, chd = 0.216, stroke = 0.197, model = "base", over_years = 30, input_problems = NA_character_ ) ) ) }) test_that("HbA1c model works, male", { expect_equal( estimate_risk_partial(sex = "male", hba1c = 9.2, quiet = TRUE), list( risk_est_10yr = dplyr::tibble( total_cvd = 0.187, ascvd = 0.112, heart_failure = 0.13, chd = 0.063, stroke = 0.056, model = "hba1c", over_years = 10, input_problems = NA_character_ ), risk_est_30yr = dplyr::tibble( total_cvd = 0.524, ascvd = 0.34, heart_failure = 0.457, chd = 0.211, stroke = 0.188, model = "hba1c", over_years = 30, input_problems = NA_character_ ) ) ) }) test_that("UACR model works, male", { expect_equal( estimate_risk_partial(sex = "male", uacr = 92, quiet = TRUE), list( risk_est_10yr = dplyr::tibble( total_cvd = 0.195, ascvd = 0.123, heart_failure = 0.13, chd = 0.066, stroke = 0.063, model = "uacr", over_years = 10, input_problems = NA_character_ ), risk_est_30yr = dplyr::tibble( total_cvd = 0.535, ascvd = 0.368, heart_failure = 0.448, chd = 0.227, stroke = 0.213, model = "uacr", over_years = 30, input_problems = NA_character_ ) ) ) }) test_that("SDI model works, male", { expect_equal( estimate_risk_partial(sex = "male", zip = "14738", quiet = TRUE), list( risk_est_10yr = dplyr::tibble( total_cvd = 0.143, ascvd = 0.088, heart_failure = 0.089, chd = 0.049, stroke = 0.043, model = "sdi", over_years = 10, input_problems = NA_character_ ), risk_est_30yr = dplyr::tibble( total_cvd = 0.48, ascvd = 0.317, heart_failure = 0.384, chd = 0.199, stroke = 0.171, model = "sdi", over_years = 30, input_problems = NA_character_ ) ) ) }) test_that("Full model works, male", { expect_equal( estimate_risk_partial(sex = "male", hba1c = 9.2, uacr = 92, zip = "14738", quiet = TRUE), list( risk_est_10yr = dplyr::tibble( total_cvd = 0.191, ascvd = 0.112, heart_failure = 0.131, chd = 0.068, stroke = 0.052, model = "full", over_years = 10, input_problems = NA_character_ ), risk_est_30yr = dplyr::tibble( total_cvd = 0.513, ascvd = 0.326, heart_failure = 0.438, chd = 0.216, stroke = 0.167, model = "full", over_years = 30, input_problems = NA_character_ ) ) ) }) test_that("Age validation works", { expect_snapshot(nested_lapply("age", 29, 80)) }) test_that("Age validation works, extra wrong" , { expect_snapshot(nested_lapply("age", test_vals = "wrong_extended")) }) # Because helper fxs test both sexes (and don't accept `sex` as an arg), # need to test this input a bit differently test_that("Sex validation works", { check_equations_partial_sans_sex <- function(sex, quiet) { res <- estimate_risk( age = 50, sex = sex, sbp = 160, bp_tx = TRUE, total_c = 200, hdl_c = 45, statin = FALSE, dm = TRUE, smoking = FALSE, egfr = 90, bmi = 35, hba1c = NULL, uacr = NULL, zip = NULL, model = NULL, time = "both", chol_unit = "mg/dL", quiet = quiet ) list( res_10yr = res[["risk_est_10yr"]] %>% dplyr::select(-input_problems), input_probs_10yr = res[["risk_est_10yr"]][["input_problems"]], res_30yr = res[["risk_est_30yr"]] %>% dplyr::select(-input_problems), input_probs_10yr = res[["risk_est_30yr"]][["input_problems"]] ) } # Chatty version expect_snapshot( lapply( test_vals_basic(), function(x) check_equations_partial_sans_sex(x, FALSE) ) ) # Quiet version expect_snapshot( lapply( test_vals_basic(), function(x) check_equations_partial_sans_sex(x, TRUE) ) ) # Chatty version, extra wrong expect_snapshot( lapply( test_vals_extended(), function(x) check_equations_partial_sans_sex(x, FALSE) ) ) # Quiet version, extra wrong expect_snapshot( lapply( test_vals_extended(), function(x) check_equations_partial_sans_sex(x, TRUE) ) ) }) test_that("SBP validation works", { expect_snapshot(nested_lapply("sbp", 89, 181)) }) test_that("SBP validation works, extra wrong", { expect_snapshot(nested_lapply("sbp", test_vals = "wrong_extended")) }) test_that("BP treatment validation works", { # Expect instance of testing 0 and FALSE (see `test_vals_basic()`) # to yield (identical) results given those are valid inputs in this case expect_snapshot(nested_lapply("bp_tx")) }) test_that("BP treatment validation works, extra wrong", { expect_snapshot(nested_lapply("bp_tx", test_vals = "wrong_extended")) }) test_that("Total cholesterol validation works", { expect_snapshot(nested_lapply("total_c", 129, 321, "mg/dL")) # For the following, expect additional problems with HDL-C given # default set to 45, but that's based on mg/dL, whereas the following # test uses mmol/L expect_snapshot(nested_lapply("total_c", 3.3, 8.3, "mmol/L")) }) test_that("Total cholesterol validation works, extra wrong", { expect_snapshot(nested_lapply("total_c", test_vals = "wrong_extended")) }) test_that("HDL-C validation works", { expect_snapshot(nested_lapply("hdl_c", 19, 101, "mg/dL")) # For the following, expect additional problems with total cholesterol given # default set to 200, but that's based on mg/dL, whereas the following # test uses mmol/L expect_snapshot(nested_lapply("hdl_c", 0.5, 2.6, "mmol/L")) }) test_that("HDL-C validation works, extra wrong", { expect_snapshot(nested_lapply("hdl_c", test_vals = "wrong_extended")) }) test_that("Statin validation works", { # Expect instance of testing 0 and FALSE (see `test_vals_basic()`) # to yield (identical) results given those are valid inputs in this case expect_snapshot(nested_lapply("statin")) }) test_that("Statin validation works, extra wrong", { expect_snapshot(nested_lapply("statin", test_vals = "wrong_extended")) }) test_that("Diabetes mellitus validation works", { # Expect instance of testing 0 and FALSE (see `test_vals_basic()`) # to yield (identical) results given those are valid inputs in this case expect_snapshot(nested_lapply("dm")) }) test_that("Diabetes mellitus validation works, extra wrong", { expect_snapshot(nested_lapply("dm", test_vals = "wrong_extended")) }) test_that("Smoking validation works", { # Expect instance of testing 0 and FALSE (see `test_vals_basic()`) # to yield (identical) results given those are valid inputs in this case expect_snapshot(nested_lapply("smoking")) }) test_that("Smoking validation works, extra wrong", { expect_snapshot(nested_lapply("smoking", test_vals = "wrong_extended")) }) test_that("eGFR validation works", { expect_snapshot(nested_lapply("egfr", 14, 141)) }) test_that("eGFR validation works, extra wrong", { expect_snapshot(nested_lapply("egfr", test_vals = "wrong_extended")) }) test_that("BMI validation works", { expect_snapshot(nested_lapply("bmi", 18.4, 40)) }) test_that("BMI validation works, extra wrong", { expect_snapshot(nested_lapply("bmi", test_vals = "wrong_extended")) }) # In contrast to when there are problems with required inputs, # expect problems with optional predictors to still yield results # (in this case, from the base model), with notification to the user # about the problems with the optional inputs # # Also, because optional variables are permitted to be empty, # any input that is functionally empty or missing -- such as `NULL`, # `numeric(0)`, `NA`, etc. -- will not be considered problematic and thus not # populate in the `input_problems` column test_that("HbA1c validation works", { expect_snapshot(nested_lapply("hba1c", 4.4, 15.1)) }) test_that("HbA1c validation works, extra wrong", { expect_snapshot(nested_lapply("hba1c", test_vals = "wrong_extended")) }) test_that("UACR validation works", { expect_snapshot(nested_lapply("uacr", 0.09, 25000.1)) }) test_that("UACR validation works, extra wrong", { expect_snapshot(nested_lapply("uacr", test_vals = "wrong_extended")) }) test_that("Zip code validation works", { expect_snapshot(nested_lapply("zip")) }) test_that("Zip code validation works, extra wrong", { expect_snapshot(nested_lapply("zip", test_vals = "wrong_extended")) }) test_that("Model validation works", { expect_snapshot(nested_lapply("model")) }) test_that("Model validation works, extra wrong", { expect_snapshot(nested_lapply("model", test_vals = "wrong_extended")) }) # Because helper fxs don't expect `time` as an arg, test `time` a bit differently test_that("Time validation works", { check_equations_partial_sans_sex_and_time <- function(sex, time, quiet) { res <- estimate_risk( age = 50, sex = sex, sbp = 160, bp_tx = TRUE, total_c = 200, hdl_c = 45, statin = FALSE, dm = TRUE, smoking = FALSE, egfr = 90, bmi = 35, hba1c = NULL, uacr = NULL, zip = NULL, model = NULL, time = time, chol_unit = "mg/dL", quiet = quiet ) # Because only one NA tibble will be returned, index accordingly # (vs. time-specific indexing, when two tibbles are returned, one # for each time horizon) list( res = res %>% dplyr::select(-input_problems), input_probs = res[["input_problems"]] ) } # Chatty versions, basic expect_snapshot( lapply( test_vals_basic(), function(x) check_equations_partial_sans_sex_and_time("f", x, FALSE) ) ) expect_snapshot( lapply( test_vals_basic(), function(x) check_equations_partial_sans_sex_and_time("m", x, FALSE) ) ) # Quiet versions, basic expect_snapshot( lapply( test_vals_basic(), function(x) check_equations_partial_sans_sex_and_time("f", x, TRUE) ) ) expect_snapshot( lapply( test_vals_basic(), function(x) check_equations_partial_sans_sex_and_time("m", x, TRUE) ) ) # Chatty versions, extended expect_snapshot( lapply( test_vals_extended(), function(x) check_equations_partial_sans_sex_and_time("f", x, FALSE) ) ) expect_snapshot( lapply( test_vals_extended(), function(x) check_equations_partial_sans_sex_and_time("m", x, FALSE) ) ) # Quiet versions, basic expect_snapshot( lapply( test_vals_extended(), function(x) check_equations_partial_sans_sex_and_time("f", x, TRUE) ) ) expect_snapshot( lapply( test_vals_extended(), function(x) check_equations_partial_sans_sex_and_time("m", x, TRUE) ) ) }) test_that("Cholesterol unit validation works", { expect_snapshot(nested_lapply("chol_unit")) }) test_that("Cholesterol unit validation works, extra wrong", { expect_snapshot(nested_lapply("chol_unit", test_vals = "wrong_extended")) }) test_that("Preparation of terms works - Basic", { dat <- make_prep_dat() expect_snapshot(prep_terms(dat, "base")) expect_snapshot(prep_terms(dat, "hba1c")) expect_snapshot(prep_terms(dat, "uacr")) expect_snapshot(prep_terms(dat, "sdi")) expect_snapshot(prep_terms(dat, "full")) }) test_that("Preparation of terms works - No BP tx", { dat <- make_prep_dat(bp_tx = FALSE) expect_snapshot(prep_terms(dat, "base")) expect_snapshot(prep_terms(dat, "hba1c")) expect_snapshot(prep_terms(dat, "uacr")) expect_snapshot(prep_terms(dat, "sdi")) expect_snapshot(prep_terms(dat, "full")) }) test_that("Preparation of terms works - No statin", { dat <- make_prep_dat(statin = FALSE) expect_snapshot(prep_terms(dat, "base")) expect_snapshot(prep_terms(dat, "hba1c")) expect_snapshot(prep_terms(dat, "uacr")) expect_snapshot(prep_terms(dat, "sdi")) expect_snapshot(prep_terms(dat, "full")) }) test_that("Preparation of terms works - No DM", { dat <- make_prep_dat(dm = FALSE) expect_snapshot(prep_terms(dat, "base")) expect_snapshot(prep_terms(dat, "hba1c")) expect_snapshot(prep_terms(dat, "uacr")) expect_snapshot(prep_terms(dat, "sdi")) expect_snapshot(prep_terms(dat, "full")) }) test_that("Preparation of terms works - Missing optional predictors", { # Test missing optional predictors dat <- make_prep_dat(dm = TRUE, hba1c = NA, uacr = NA, zip = NA) expect_snapshot(prep_terms(dat, "hba1c")) expect_snapshot(prep_terms(dat, "uacr")) expect_snapshot(prep_terms(dat, "sdi")) expect_snapshot(prep_terms(dat, "full")) }) test_that("Preparation of terms works - Other SDI categories", { # Test zip where SDI from 4-6 dat <- make_prep_dat(zip = "44221") expect_snapshot(prep_terms(dat, "sdi")) expect_snapshot(prep_terms(dat, "full")) # Test zip where SDI from 1-3 dat[["zip"]] <- "01005" expect_snapshot(prep_terms(dat, "sdi")) expect_snapshot(prep_terms(dat, "full")) }) test_that("Base model 10-year risks give expected results", { expect_snapshot( check_equations( age = 50, total_c = 200, hdl_c = 45, sbp = 160, # Deliberately use variations of TRUE/1 and FALSE/0 dm = TRUE, smoking = 0, bmi = 35, egfr = 90, bp_tx = 1, statin = FALSE, time = "10yr" ) ) }) test_that("UACR model 10-year risks give expected results", { expect_snapshot( check_equations( age = 50, total_c = 200, hdl_c = 45, sbp = 160, # Deliberately use variations of TRUE/1 and FALSE/0 dm = 1, smoking = FALSE, bmi = 35, egfr = 90, bp_tx = TRUE, statin = 0, uacr = 40, time = "10yr" ) ) }) test_that("HbA1c model 10-year risks give expected results", { expect_snapshot( check_equations( age = 50, total_c = 200, hdl_c = 45, sbp = 160, # Deliberately use variations of TRUE/1 and FALSE/0 dm = 1, smoking = FALSE, bmi = 35, egfr = 90, bp_tx = TRUE, statin = 0, hba1c = 7.5, time = "10yr" ) ) }) test_that("Zip model 10-year risks give expected results & SDI lookup works", { sdi_10yr_partial <- function(zip) { check_equations( age = 50, total_c = 200, hdl_c = 45, sbp = 160, # Deliberately use variations of TRUE/1 and FALSE/0 dm = TRUE, smoking = FALSE, bmi = 35, egfr = 90, bp_tx = TRUE, statin = FALSE, zip = zip, time = "10yr" ) } # SDI of 3 expect_snapshot(get_sdi("03883")) expect_snapshot(sdi_10yr_partial(zip = "03883")) # SDI of 5 expect_snapshot(get_sdi("49544")) expect_snapshot(sdi_10yr_partial(zip = "49544")) # SDI of 10 expect_snapshot(get_sdi("49507")) expect_snapshot(sdi_10yr_partial(zip = "49507")) }) test_that("Full model 10-year risks give expected results", { expect_snapshot( check_equations_partial( age = 75, statin = TRUE, dm = 0, smoking = 1, hba1c = 7.5, uacr = 40, zip = "49507", time = "10yr" ) ) }) test_that("Time as character vs. numeric works: Base model, 10-year", { chr_version_1 <- check_equations_partial( age = 67, statin = TRUE, dm = 0, smoking = 1, time = "10yr", quiet = TRUE ) chr_version_2 <- check_equations_partial( age = 67, statin = TRUE, dm = 0, smoking = 1, time = "10", quiet = TRUE ) num_version <- check_equations_partial( age = 67, statin = TRUE, dm = 0, smoking = 1, time = 10, quiet = TRUE ) expect_identical(chr_version_1, num_version) expect_identical(chr_version_1, chr_version_2) expect_identical("base", chr_version_1[["model"]][["female"]]) expect_identical("base", chr_version_1[["model"]][["male"]]) }) test_that("Time as character vs. numeric works: HbA1c model, 10-year", { chr_version_1 <- check_equations_partial( age = 61, statin = TRUE, smoking = 1, hba1c = 9.9, time = "10yr", quiet = TRUE ) chr_version_2 <- check_equations_partial( age = 61, statin = TRUE, smoking = 1, hba1c = 9.9, time = "10", quiet = TRUE ) num_version <- check_equations_partial( age = 61, statin = TRUE, smoking = 1, hba1c = 9.9, time = 10, quiet = TRUE ) expect_identical(chr_version_1, num_version) expect_identical(chr_version_1, chr_version_2) expect_identical("hba1c", chr_version_1[["model"]][["female"]]) expect_identical("hba1c", chr_version_1[["model"]][["male"]]) }) test_that("Time as character vs. numeric works: UACR model, 10-year", { chr_version_1 <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, uacr = 40, time = "10yr", quiet = TRUE ) chr_version_2 <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, uacr = 40, time = "10", quiet = TRUE ) num_version <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, uacr = 40, time = 10, quiet = TRUE ) expect_identical(chr_version_1, num_version) expect_identical(chr_version_1, chr_version_2) expect_identical("uacr", chr_version_1[["model"]][["female"]]) expect_identical("uacr", chr_version_1[["model"]][["male"]]) }) test_that("Time as character vs. numeric works: SDI model, 10-year", { chr_version_1 <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, zip = "49507", time = "10yr", quiet = TRUE ) chr_version_2 <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, zip = "49507", time = "10", quiet = TRUE ) num_version <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, zip = "49507", time = 10, quiet = TRUE ) expect_identical(chr_version_1, num_version) expect_identical(chr_version_1, chr_version_2) expect_identical("sdi", chr_version_1[["model"]][["female"]]) expect_identical("sdi", chr_version_1[["model"]][["male"]]) }) test_that("Time as character vs. numeric works: Full model, 10-year", { chr_version_1 <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, hba1c = 7.5, uacr = 40, zip = "49507", time = "10yr", quiet = TRUE ) chr_version_2 <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, hba1c = 7.5, uacr = 40, zip = "49507", time = "10", quiet = TRUE ) num_version <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, hba1c = 7.5, uacr = 40, zip = "49507", time = 10, quiet = TRUE ) expect_identical(chr_version_1, num_version) expect_identical(chr_version_1, chr_version_2) expect_identical("full", chr_version_1[["model"]][["female"]]) expect_identical("full", chr_version_1[["model"]][["male"]]) }) test_that("Base model 30-year risks give expected results", { expect_snapshot( check_equations( age = 50, total_c = 200, hdl_c = 45, sbp = 160, # Deliberately use variations of TRUE/1 and FALSE/0 dm = TRUE, smoking = 0, bmi = 35, egfr = 90, bp_tx = 1, statin = FALSE, time = "30yr" ) ) }) test_that("UACR model 30-year risks give expected results", { expect_snapshot( check_equations( age = 50, total_c = 200, hdl_c = 45, sbp = 160, # Deliberately use variations of TRUE/1 and FALSE/0 dm = 1, smoking = FALSE, bmi = 35, egfr = 90, bp_tx = TRUE, statin = 0, uacr = 40, time = "30yr" ) ) }) test_that("HbA1c model 30-year risks give expected results", { expect_snapshot( check_equations( age = 50, total_c = 200, hdl_c = 45, sbp = 160, # Deliberately use variations of TRUE/1 and FALSE/0 dm = 1, smoking = FALSE, bmi = 35, egfr = 90, bp_tx = TRUE, statin = 0, hba1c = 7.5, time = "30yr" ) ) }) test_that("Zip model 30-year risks give expected results & SDI lookup works", { sdi_30yr_partial <- function(zip) { check_equations( age = 50, total_c = 200, hdl_c = 45, sbp = 160, # Deliberately use variations of TRUE/1 and FALSE/0 dm = TRUE, smoking = FALSE, bmi = 35, egfr = 90, bp_tx = TRUE, statin = FALSE, zip = zip, time = "30yr" ) } # SDI of 3 expect_snapshot(get_sdi("03883")) expect_snapshot(sdi_30yr_partial(zip = "03883")) # SDI of 5 expect_snapshot(get_sdi("49544")) expect_snapshot(sdi_30yr_partial(zip = "49544")) # SDI of 10 expect_snapshot(get_sdi("49507")) expect_snapshot(sdi_30yr_partial(zip = "49507")) }) test_that("Full model 30-year risks give expected results", { expect_snapshot( check_equations_partial( age = 75, statin = TRUE, dm = 0, smoking = 1, hba1c = 7.5, uacr = 40, zip = "49507", time = "30yr" ) ) }) test_that("Time as character vs. numeric works: Base model, 30-year", { chr_version_1 <- check_equations_partial( age = 67, statin = TRUE, dm = 0, smoking = 1, time = "30yr", quiet = TRUE ) chr_version_2 <- check_equations_partial( age = 67, statin = TRUE, dm = 0, smoking = 1, time = "30", quiet = TRUE ) num_version <- check_equations_partial( age = 67, statin = TRUE, dm = 0, smoking = 1, time = 30, quiet = TRUE ) expect_identical(chr_version_1, num_version) expect_identical(chr_version_1, chr_version_2) expect_identical("base", chr_version_1[["model"]][["female"]]) expect_identical("base", chr_version_1[["model"]][["male"]]) }) test_that("Time as character vs. numeric works: HbA1c model, 30-year", { chr_version_1 <- check_equations_partial( age = 61, statin = TRUE, smoking = 1, hba1c = 9.9, time = "30yr", quiet = TRUE ) chr_version_2 <- check_equations_partial( age = 61, statin = TRUE, smoking = 1, hba1c = 9.9, time = "30", quiet = TRUE ) num_version <- check_equations_partial( age = 61, statin = TRUE, smoking = 1, hba1c = 9.9, time = 30, quiet = TRUE ) expect_identical(chr_version_1, num_version) expect_identical(chr_version_1, chr_version_2) expect_identical("hba1c", chr_version_1[["model"]][["female"]]) expect_identical("hba1c", chr_version_1[["model"]][["male"]]) }) test_that("Time as character vs. numeric works: UACR model, 30-year", { chr_version_1 <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, uacr = 40, time = "30yr", quiet = TRUE ) chr_version_2 <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, uacr = 40, time = "30", quiet = TRUE ) num_version <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, uacr = 40, time = 30, quiet = TRUE ) expect_identical(chr_version_1, num_version) expect_identical(chr_version_1, chr_version_2) expect_identical("uacr", chr_version_1[["model"]][["female"]]) expect_identical("uacr", chr_version_1[["model"]][["male"]]) }) test_that("Time as character vs. numeric works: SDI model, 30-year", { chr_version_1 <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, zip = "49507", time = "30yr", quiet = TRUE ) chr_version_2 <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, zip = "49507", time = "30", quiet = TRUE ) num_version <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, zip = "49507", time = 30, quiet = TRUE ) expect_identical(chr_version_1, num_version) expect_identical(chr_version_1, chr_version_2) expect_identical("sdi", chr_version_1[["model"]][["female"]]) expect_identical("sdi", chr_version_1[["model"]][["male"]]) }) test_that("Time as character vs. numeric works: Full model, 30-year", { chr_version_1 <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, hba1c = 7.5, uacr = 40, zip = "49507", time = "30yr", quiet = TRUE ) chr_version_2 <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, hba1c = 7.5, uacr = 40, zip = "49507", time = "30", quiet = TRUE ) num_version <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, hba1c = 7.5, uacr = 40, zip = "49507", time = 30, quiet = TRUE ) expect_identical(chr_version_1, num_version) expect_identical(chr_version_1, chr_version_2) expect_identical("full", chr_version_1[["model"]][["female"]]) expect_identical("full", chr_version_1[["model"]][["male"]]) }) test_that("Cholesterol unit abbreviation works", { mg_full <- check_equations_partial(chol_unit = "mg/dL", quiet = TRUE) mg_abbrev <- check_equations_partial(chol_unit = "mg", quiet = TRUE) mmol_abbrev <- check_equations_partial( chol_unit = "mmol", total_c = 4, hdl_c = 1, quiet = TRUE ) mmol_full <- check_equations_partial( chol_unit = "mmol/L", total_c = 4, hdl_c = 1, quiet = TRUE ) expect_identical(mg_full, mg_abbrev) expect_identical(mmol_abbrev, mmol_full) }) test_that("Optional predictors are handled correctly, one optional predictor is valid", { # ... in terms of model selection and results # UACR model shold run uacr_okay_hba1c_not_zip_null <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, uacr = 40, hba1c = 75, zip = NULL, quiet = TRUE ) uacr_okay_hba1c_null_zip_not <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, uacr = 40, hba1c = NULL, zip = "99999", quiet = TRUE ) uacr_okay_others_not <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, uacr = 40, hba1c = 75, zip = "99999", quiet = TRUE ) uacr_model_by_itself <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, uacr = 40, quiet = TRUE ) # Review input problems expect_snapshot( list( uacr_okay_hba1c_not_zip_null = uacr_okay_hba1c_not_zip_null[["input_problems"]], uacr_okay_hba1c_null_zip_not = uacr_okay_hba1c_null_zip_not[["input_problems"]], uacr_okay_others_not = uacr_okay_others_not[["input_problems"]], uacr_model_by_itself = uacr_model_by_itself[["input_problems"]] ) ) # Set `input_problems` to NULL to avoid comparing it now uacr_okay_hba1c_not_zip_null[["input_problems"]] <- uacr_okay_hba1c_null_zip_not[["input_problems"]] <- uacr_okay_others_not[["input_problems"]] <- uacr_model_by_itself[["input_problems"]] <- NULL expect_identical(uacr_okay_hba1c_not_zip_null, uacr_model_by_itself) expect_identical(uacr_okay_hba1c_null_zip_not, uacr_model_by_itself) expect_identical(uacr_okay_others_not, uacr_model_by_itself) expect_identical(unique(uacr_okay_hba1c_not_zip_null[["model"]]), "uacr") # HbA1c model shold run hba1c_okay_uacr_not_zip_null <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, uacr = 4000000, hba1c = 7.5, zip = NULL, quiet = TRUE ) hba1c_okay_uacr_null_zip_not <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, uacr = NULL, hba1c = 7.5, zip = "99999", quiet = TRUE ) hba1c_okay_others_not <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, uacr = 4000000, hba1c = 7.5, zip = "99999", quiet = TRUE ) hba1c_model_by_itself <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, hba1c = 7.5, quiet = TRUE ) # Review input problems expect_snapshot( list( hba1c_okay_uacr_not_zip_null = hba1c_okay_uacr_not_zip_null[["input_problems"]], hba1c_okay_uacr_null_zip_not = hba1c_okay_uacr_null_zip_not[["input_problems"]], hba1c_okay_others_not = hba1c_okay_others_not[["input_problems"]], hba1c_model_by_itself = hba1c_model_by_itself[["input_problems"]] ) ) # Set `input_problems` to NULL to avoid comparing it now hba1c_okay_uacr_not_zip_null[["input_problems"]] <- hba1c_okay_uacr_null_zip_not[["input_problems"]] <- hba1c_okay_others_not[["input_problems"]] <- hba1c_model_by_itself[["input_problems"]] <- NULL expect_identical(hba1c_okay_uacr_not_zip_null, hba1c_model_by_itself) expect_identical(hba1c_okay_uacr_null_zip_not, hba1c_model_by_itself) expect_identical(hba1c_okay_others_not, hba1c_model_by_itself) expect_identical(unique(hba1c_okay_uacr_not_zip_null[["model"]]), "hba1c") # SDI model should run zip_okay_uacr_not_hba1c_null <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, uacr = 4000000, hba1c = NULL, zip = "49507", quiet = TRUE ) zip_okay_uacr_null_hba1c_not <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, uacr = NULL, hba1c = 75, zip = "49507", quiet = TRUE ) zip_okay_others_not <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, uacr = 4000000, hba1c = 75, zip = "49507", quiet = TRUE ) zip_by_itself <- check_equations_partial( age = 75, statin = TRUE, smoking = 1, zip = "49507", quiet = TRUE ) # Review input problems expect_snapshot( list( zip_okay_uacr_not_hba1c_null = zip_okay_uacr_not_hba1c_null[["input_problems"]], zip_okay_uacr_null_hba1c_not = zip_okay_uacr_null_hba1c_not[["input_problems"]], zip_okay_others_not = zip_okay_others_not[["input_problems"]], zip_by_itself = zip_by_itself[["input_problems"]] ) ) # Set `input_problems` to NULL to avoid comparing it now zip_okay_uacr_not_hba1c_null[["input_problems"]] <- zip_okay_uacr_null_hba1c_not[["input_problems"]] <- zip_okay_others_not[["input_problems"]] <- zip_by_itself[["input_problems"]] <- NULL expect_identical(zip_okay_uacr_not_hba1c_null, zip_by_itself) expect_identical(zip_okay_uacr_null_hba1c_not, zip_by_itself) expect_identical(zip_okay_others_not, zip_by_itself) expect_identical(unique(zip_okay_uacr_not_hba1c_null[["model"]]), "sdi") }) test_that("Optional predictors are handled correctly, two optional predictors are valid", { full_model_invalid_uacr <- check_equations_partial( age = 45, statin = TRUE, sbp = 148, uacr = 4000000, hba1c = 7.5, zip = "49507", quiet = TRUE ) full_model_uacr_null <- check_equations_partial( age = 45, statin = TRUE, sbp = 148, uacr = NULL, hba1c = 7.5, zip = "49507", quiet = TRUE ) # Review input problems expect_snapshot( list( full_model_invalid_uacr = full_model_invalid_uacr[["input_problems"]], full_model_uacr_null = full_model_uacr_null[["input_problems"]] ) ) # Set `input_problems` to NULL to avoid comparing it now full_model_invalid_uacr[["input_problems"]] <- full_model_uacr_null[["input_problems"]] <- NULL expect_identical(full_model_invalid_uacr, full_model_uacr_null) expect_identical(unique(full_model_invalid_uacr[["model"]]), "full") full_model_invalid_hba1c <- check_equations_partial( age = 45, statin = TRUE, sbp = 148, uacr = 40, hba1c = 75, zip = "49507", quiet = TRUE ) full_model_hba1c_null <- check_equations_partial( age = 45, statin = TRUE, sbp = 148, uacr = 40, hba1c = NULL, zip = "49507", quiet = TRUE ) # Review input problems expect_snapshot( list( full_model_invalid_hba1c = full_model_invalid_hba1c[["input_problems"]], full_model_hba1c_null = full_model_hba1c_null[["input_problems"]] ) ) # Set `input_problems` to NULL to avoid comparing it now full_model_invalid_hba1c[["input_problems"]] <- full_model_hba1c_null[["input_problems"]] <- NULL expect_identical(full_model_invalid_hba1c, full_model_hba1c_null) expect_identical(unique(full_model_invalid_hba1c[["model"]]), "full") full_model_invalid_zip <- check_equations_partial( age = 45, statin = TRUE, sbp = 148, uacr = 40, hba1c = 7.5, zip = "99999", quiet = TRUE ) full_model_zip_null <- check_equations_partial( age = 45, statin = TRUE, sbp = 148, uacr = 40, hba1c = 7.5, zip = NULL, quiet = TRUE ) # Review input problems expect_snapshot( list( full_model_invalid_zip = full_model_invalid_zip[["input_problems"]], full_model_zip_null = full_model_zip_null[["input_problems"]] ) ) # Set `input_problems` to NULL to avoid comparing it now full_model_invalid_zip[["input_problems"]] <- full_model_zip_null[["input_problems"]] <- NULL expect_identical(full_model_invalid_zip, full_model_zip_null) expect_identical(unique(full_model_invalid_zip[["model"]]), "full") }) test_that("Invalid UACR is same as no UACR, only optional is UACR", { invalid_uacr <- check_equations_partial(uacr = 4000000, quiet = TRUE) no_uacr <- check_equations_partial(uacr = NULL, quiet = TRUE) # Review input problems expect_snapshot( list( invalid_uacr = invalid_uacr[["input_problems"]], no_uacr = no_uacr[["input_problems"]] ) ) # Set `input_problems` to NULL to avoid comparing it now invalid_uacr[["input_problems"]] <- no_uacr[["input_problems"]] <- NULL expect_identical(invalid_uacr, no_uacr) expect_identical(unique(invalid_uacr[["model"]]), "base") }) test_that("Invalid HbA1c is same as no HbA1c, only optional is HbA1c", { invalid_hba1c <- check_equations_partial(hba1c = 75, quiet = TRUE) no_hba1c <- check_equations_partial(hba1c = NULL, quiet = TRUE) # Review input problems expect_snapshot( list( invalid_hba1c = invalid_hba1c[["input_problems"]], no_hba1c = no_hba1c[["input_problems"]] ) ) # Set `input_problems` to NULL to avoid comparing it now invalid_hba1c[["input_problems"]] <- no_hba1c[["input_problems"]] <- NULL expect_identical(invalid_hba1c, no_hba1c) expect_identical(unique(invalid_hba1c[["model"]]), "base") }) test_that("Invalid zip is same as no zip, only optional is zip", { invalid_zip <- check_equations_partial(zip = "99999", quiet = TRUE) no_zip <- check_equations_partial(zip = NULL, quiet = TRUE) # Review input problems expect_snapshot( list( invalid_zip = invalid_zip[["input_problems"]], no_zip = no_zip[["input_problems"]] ) ) # Set `input_problems` to NULL to avoid comparing it now invalid_zip[["input_problems"]] <- no_zip[["input_problems"]] <- NULL expect_identical(invalid_zip, no_zip) expect_identical(unique(invalid_zip[["model"]]), "base") }) test_that("Enforcing strict optional input validation works", { empty_table <- dplyr::tibble( total_cvd = NA_real_, ascvd = NA_real_, heart_failure = NA_real_, chd = NA_real_, stroke = NA_real_, model = "none", over_years = NA_integer_ ) empty_list_of_tables <- list( risk_est_10yr = empty_table, risk_est_30yr = empty_table ) remove_input_probs <- function(x) { lapply(x, function(x) dplyr::select(x, -input_problems)) } get_input_probs <- function(x) { unique(vapply(x, function(x) dplyr::pull(x, input_problems), character(1))) } # Basic invalid input ---- # UACR uacr <- estimate_risk_partial( uacr = 9000000, optional_strict = TRUE, quiet = TRUE ) uacr_input_problems <- get_input_probs(uacr) uacr_remove_input_problems <- remove_input_probs(uacr) # HbA1c hb1ac <- estimate_risk_partial( hba1c = 75, optional_strict = TRUE, quiet = TRUE ) hb1ac_input_problems <- get_input_probs(hb1ac) hb1ac_remove_input_problems <- remove_input_probs(hb1ac) # ZIP zip <- estimate_risk_partial( zip = "99999", optional_strict = TRUE, quiet = TRUE ) zip_input_problems <- get_input_probs(zip) zip_remove_input_problems <- remove_input_probs(zip) # UACR and HbA1c uacr_hba1c <- estimate_risk_partial( uacr = 9000000, hba1c = 75, optional_strict = TRUE, quiet = TRUE ) uacr_hba1c_input_problems <- get_input_probs(uacr_hba1c) uacr_hba1c_remove_input_problems <- remove_input_probs(uacr_hba1c) # UACR and ZIP uacr_zip <- estimate_risk_partial( uacr = 9000000, zip = "99999", optional_strict = TRUE, quiet = TRUE ) uacr_zip_input_problems <- get_input_probs(uacr_zip) uacr_zip_remove_input_problems <- remove_input_probs(uacr_zip) # HbA1c and ZIP hba1c_zip <- estimate_risk_partial( hba1c = 75, zip = "99999", optional_strict = TRUE, quiet = TRUE ) hba1c_zip_input_problems <- get_input_probs(hba1c_zip) hba1c_zip_remove_input_problems <- remove_input_probs(hba1c_zip) # UACR, HbA1c, and ZIP uacr_hba1c_zip <- estimate_risk_partial( uacr = 9000000, hba1c = 75, zip = "99999", optional_strict = TRUE, quiet = TRUE ) uacr_hba1c_zip_input_problems <- get_input_probs(uacr_hba1c_zip) uacr_hba1c_zip_remove_input_problems <- remove_input_probs(uacr_hba1c_zip) # More complex invalid input ---- # UACR uacr_complex <- estimate_risk_partial( uacr = list(a = list(b = data.frame(a = "apple", b = TRUE)), b = 5), optional_strict = TRUE, quiet = TRUE ) uacr_input_problems_complex <- get_input_probs(uacr_complex) uacr_remove_input_problems_complex <- remove_input_probs(uacr_complex) # HbA1c hb1ac_complex <- estimate_risk_partial( hba1c = matrix(12, 3, 4), optional_strict = TRUE, quiet = TRUE ) hb1ac_input_problems_complex <- get_input_probs(hb1ac_complex) hb1ac_remove_input_problems_complex <- remove_input_probs(hb1ac_complex) # ZIP zip_complex <- estimate_risk_partial( zip = c("01011", "22222", "99999"), optional_strict = TRUE, quiet = TRUE ) zip_input_problems_complex <- get_input_probs(zip_complex) zip_remove_input_problems_complex <- remove_input_probs(zip_complex) # UACR and HbA1c uacr_hba1c_complex <- estimate_risk_partial( uacr = list(a = list(b = data.frame(a = "apple", b = TRUE)), b = 5), hba1c = data.frame(a = 75, b = "potato"), optional_strict = TRUE, quiet = TRUE ) uacr_hba1c_input_problems_complex <- get_input_probs(uacr_hba1c_complex) uacr_hba1c_remove_input_problems_complex <- remove_input_probs(uacr_hba1c_complex) # UACR and ZIP uacr_zip_complex <- estimate_risk_partial( uacr = list(a = list(b = data.frame(a = "apple", b = TRUE)), b = 5), zip = c("01011", "22222", "99999"), optional_strict = TRUE, quiet = TRUE ) uacr_zip_input_problems_complex <- get_input_probs(uacr_zip_complex) uacr_zip_remove_input_problems_complex <- remove_input_probs(uacr_zip_complex) # HbA1c and ZIP hba1c_zip_complex <- estimate_risk_partial( hba1c = data.frame(a = 75, b = "potato"), zip = c("01011", "22222", "99999"), optional_strict = TRUE, quiet = TRUE ) hba1c_zip_input_problems_complex <- get_input_probs(hba1c_zip_complex) hba1c_zip_remove_input_problems_complex <- remove_input_probs(hba1c_zip_complex) # UACR, HbA1c, and ZIP uacr_hba1c_zip_complex <- estimate_risk_partial( uacr = list(a = list(b = data.frame(a = "apple", b = TRUE)), b = 5), hba1c = data.frame(a = 75, b = "potato"), zip = c("01011", "22222", "99999"), optional_strict = TRUE, quiet = TRUE ) uacr_hba1c_zip_input_problems_complex <- get_input_probs(uacr_hba1c_zip_complex) uacr_hba1c_zip_remove_input_problems_complex <- remove_input_probs(uacr_hba1c_zip_complex) # Review input problems expect_snapshot( list( uacr = uacr_input_problems, hb1ac = hb1ac_input_problems, zip = zip_input_problems, uacr_hba1c = uacr_hba1c_input_problems, uacr_zip = uacr_zip_input_problems, hba1c_zip = hba1c_zip_input_problems, uacr_hba1c_zip = uacr_hba1c_zip_input_problems, uacr_complex = uacr_input_problems_complex, hb1ac_complex = hb1ac_input_problems_complex, zip_complex = zip_input_problems_complex, uacr_hba1c_complex = uacr_hba1c_input_problems_complex, uacr_zip_complex = uacr_zip_input_problems_complex, hba1c_zip_complex = hba1c_zip_input_problems_complex, uacr_hba1c_zip_complex = uacr_hba1c_zip_input_problems_complex ) ) expect_equal(uacr_remove_input_problems, empty_list_of_tables) expect_equal(hb1ac_remove_input_problems, empty_list_of_tables) expect_equal(zip_remove_input_problems, empty_list_of_tables) expect_equal(uacr_hba1c_remove_input_problems, empty_list_of_tables) expect_equal(uacr_zip_remove_input_problems, empty_list_of_tables) expect_equal(hba1c_zip_remove_input_problems, empty_list_of_tables) expect_equal(uacr_hba1c_zip_remove_input_problems, empty_list_of_tables) expect_equal(uacr_remove_input_problems_complex, empty_list_of_tables) expect_equal(hb1ac_remove_input_problems_complex, empty_list_of_tables) expect_equal(zip_remove_input_problems_complex, empty_list_of_tables) expect_equal(uacr_hba1c_remove_input_problems_complex, empty_list_of_tables) expect_equal(uacr_zip_remove_input_problems_complex, empty_list_of_tables) expect_equal(hba1c_zip_remove_input_problems_complex, empty_list_of_tables) expect_equal(uacr_hba1c_zip_remove_input_problems_complex, empty_list_of_tables) }) test_that("Additional checks of results, for good measure", { # At this point, given how the package is written (e.g., to ensure # full concordance between coefficients and term prep), with tests # thus far, further testing is essentially superfluous, but more tests # never hurt anyone :-) expect_snapshot( check_equations_partial( age = 67, statin = FALSE, hba1c = 9 ) ) expect_snapshot( check_equations_partial( age = 67, statin = TRUE, uacr = 1000 ) ) expect_snapshot( check_equations_partial( age = 71, statin = TRUE, hba1c = 9, uacr = 1000 ) ) expect_snapshot( check_equations_partial( age = 71, statin = TRUE, hba1c = 9, uacr = 1000, zip = "49507" ) ) expect_snapshot( check_equations_partial( age = 71, sbp = 145, bp_tx = 0, hba1c = 6.7, uacr = 10, zip = NA ) ) res_v1 <- check_equations_partial( age = 35, sbp = 145, bp_tx = 1, uacr = 10, zip = NA, quiet = TRUE ) res_v2 <- check_equations_partial( age = 35, sbp = 145, bp_tx = 1, uacr = 10, quiet = TRUE ) identical_res <- identical(res_v1, res_v2) expect_snapshot(res_v1) expect_snapshot(res_v2) expect_true(identical_res) }) test_that("warning for 30-year risk with age > 59 works", { expect_warning( estimate_risk( age = 63, sex = "f", sbp = 120, bp_tx = FALSE, total_c = 200, hdl_c = 50, statin = FALSE, dm = FALSE, smoking = FALSE, egfr = 68, bmi = 22 ), "Estimating 30-year risk in people > 59 years of age is questionable" ) # Note setting `quiet = TRUE` to suppress the warning in these tests # to permit testing the output without {testthat} reporting a warning # during the tests expect_equal( estimate_risk( age = 63, sex = "f", sbp = 120, bp_tx = FALSE, total_c = 200, hdl_c = 50, statin = FALSE, dm = FALSE, smoking = FALSE, egfr = 68, bmi = 22, time = 30, quiet = TRUE )[["input_problems"]], "Warning: Estimating 30-year risk in people > 59 years of age is questionable" ) # Note setting `quiet = TRUE` (see above) expect_equal( estimate_risk( age = 63, sex = "f", sbp = 120, bp_tx = FALSE, total_c = 200, hdl_c = 50, statin = FALSE, dm = FALSE, smoking = FALSE, egfr = 68, bmi = 22, hba1c = 200, time = 30, quiet = TRUE )[["input_problems"]], paste0( "Warning: Estimating 30-year risk in people > 59 years of age is questionable; ", "`hba1c` entered as 200, but must be between 4.5 and 15 (so set to NULL)" ) ) }) test_that("arg `collapse` behaves as intended, return of NA tibbles behaves as intended", { res_collapsed <- estimate_risk( age = 50, sex = "female", sbp = 130, bp_tx = TRUE, total_c = 200, hdl_c = 45, statin = FALSE, dm = TRUE, smoking = FALSE, egfr = 90, bmi = 35, time = "30yr", model = list(other_models = "pce_both", race_eth = "Black"), quiet = TRUE, collapse = TRUE ) res_uncollapsed <- estimate_risk( age = 50, sex = "female", sbp = 130, bp_tx = TRUE, total_c = 200, hdl_c = 45, statin = FALSE, dm = TRUE, smoking = FALSE, egfr = 90, bmi = 35, time = "30yr", model = list(other_models = "pce_both", race_eth = "Black"), quiet = TRUE ) res_arg_collapse_strict_about_true <- estimate_risk( age = 50, sex = "female", sbp = 130, bp_tx = TRUE, total_c = 200, hdl_c = 45, statin = FALSE, dm = TRUE, smoking = FALSE, egfr = 90, bmi = 35, time = "30yr", model = list(other_models = "pce_both", race_eth = "Black"), quiet = TRUE, collapse = 1 ) expect_identical(res_uncollapsed, res_arg_collapse_strict_about_true) res_arg_collapse_strict_about_true <- estimate_risk( age = 50, sex = "female", sbp = 130, bp_tx = TRUE, total_c = 200, hdl_c = 45, statin = FALSE, dm = TRUE, smoking = FALSE, egfr = 90, bmi = 35, time = "30yr", model = list(other_models = "pce_both", race_eth = "Black"), quiet = TRUE, collapse = "yes, please" ) expect_identical(res_uncollapsed, res_arg_collapse_strict_about_true) # For `res_uncollapsed`, expect a list given `time = "30yr"` and the call # to `estimate_risk()` requests other models expect_type(res_uncollapsed, "list") # For `res_collapsed`, expect a data frame given `collapse = TRUE`, # and further expect that the number of rows is 3 (one for the 30-year # estimates from PREVENT, 2 for the 10-year estimates from the PCEs given # `other_models = "pce_both"`) expect_s3_class(res_collapsed, c("tbl_df", "tbl", "data.frame")) expect_equal(nrow(res_collapsed), 3) # Test for equality aside from structure expect_true( all.equal( do.call(rbind, res_uncollapsed), res_collapsed, check.attributes = FALSE ) ) # Test for `collapse` having no effect if `time` is "10yr" res_collapsed <- estimate_risk( age = 50, sex = "female", sbp = 130, bp_tx = TRUE, total_c = 200, hdl_c = 45, statin = FALSE, dm = TRUE, smoking = FALSE, egfr = 90, bmi = 35, time = "10yr", model = list(other_models = "pce_both", race_eth = "Black"), quiet = TRUE, collapse = TRUE ) res_uncollapsed <- estimate_risk( age = 50, sex = "female", sbp = 130, bp_tx = TRUE, total_c = 200, hdl_c = 45, statin = FALSE, dm = TRUE, smoking = FALSE, egfr = 90, bmi = 35, time = "10yr", model = list(other_models = "pce_both", race_eth = "Black"), quiet = TRUE, collapse = FALSE ) expect_identical(res_collapsed, res_uncollapsed) # ... or if `time` is "30yr" and they don't request other models res_collapsed <- estimate_risk( age = 50, sex = "female", sbp = 130, bp_tx = TRUE, total_c = 200, hdl_c = 45, statin = FALSE, dm = TRUE, smoking = FALSE, egfr = 90, bmi = 35, time = "30yr", quiet = TRUE, collapse = TRUE ) res_uncollapsed <- estimate_risk( age = 50, sex = "female", sbp = 130, bp_tx = TRUE, total_c = 200, hdl_c = 45, statin = FALSE, dm = TRUE, smoking = FALSE, egfr = 90, bmi = 35, time = "30yr", quiet = TRUE, collapse = FALSE ) expect_identical(res_collapsed, res_uncollapsed) # ... or if they use `use_dat` dat <- make_dat(10) res_collapsed <- estimate_risk(use_dat = dat, progress = FALSE, collapse = TRUE) res_uncollapsed <- estimate_risk(use_dat = dat, progress = FALSE, collapse = FALSE) expect_identical(res_collapsed, res_uncollapsed) # Even if results are errors, expect collapse to work # Both time frames, no PCEs, collapsed names_when_tbl <- c("total_cvd", "ascvd", "heart_failure", "chd", "stroke", "model", "over_years", "input_problems") names_when_list <- c("risk_est_10yr", "risk_est_30yr") # Both time frames, no PCEs, collapsed res_collapsed <- estimate_risk( age = -50, sex = list(TRUE, FALSE), sbp = 130, bp_tx = TRUE, total_c = 200, hdl_c = 45, statin = FALSE, dm = TRUE, smoking = FALSE, egfr = 90, bmi = 35, quiet = TRUE, collapse = TRUE ) expect_true(is.data.frame(res_collapsed)) expect_equal(nrow(res_collapsed), 2) # 10yr and 30yr expect_equal(count_nas_from_res(res_collapsed), 10) # 5 NAs per time frame expect_equal(names(res_collapsed), names_when_tbl) # Both time frames, no PCEs, uncollapsed res_uncollapsed <- estimate_risk( age = -50, sex = list(TRUE, FALSE), sbp = 130, bp_tx = TRUE, total_c = 200, hdl_c = 45, statin = FALSE, dm = TRUE, smoking = FALSE, egfr = 90, bmi = 35, quiet = TRUE, collapse = FALSE ) expect_false(is.data.frame(res_uncollapsed)) expect_true(is.list(res_uncollapsed)) expect_equal(length(res_uncollapsed), 2) # 10yr and 30yr expect_equal(names(res_uncollapsed), names_when_list) expect_equal(count_nas_from_res(res_uncollapsed[["risk_est_10yr"]]), 5) expect_equal(count_nas_from_res(res_uncollapsed[["risk_est_30yr"]]), 5) # Because only one time horizon, `collapse` has no impact # 10 years, no PCEs res_collapsed <- estimate_risk_partial( age = -50, sex = list(TRUE, FALSE), quiet = TRUE, time = 10, collapse = TRUE ) res_uncollapsed <- estimate_risk_partial( age = -50, sex = list(TRUE, FALSE), quiet = TRUE, time = 10, collapse = FALSE ) expect_identical(res_uncollapsed, res_collapsed) expect_equal(nrow(res_uncollapsed), 1) expect_equal(count_nas_from_res(res_uncollapsed), 5) expect_equal(names(res_uncollapsed), names_when_tbl) # 30 years, no PCEs res_collapsed <- estimate_risk_partial( age = -50, sex = list(TRUE, FALSE), quiet = TRUE, time = 30, collapse = TRUE ) res_uncollapsed <- estimate_risk_partial( age = -50, sex = list(TRUE, FALSE), quiet = TRUE, time = 30, collapse = FALSE ) expect_identical(res_uncollapsed, res_collapsed) expect_equal(nrow(res_uncollapsed), 1) expect_equal(count_nas_from_res(res_uncollapsed), 5) expect_equal(names(res_uncollapsed), names_when_tbl) # Still no difference here despite requesting PCEs, b/c one time horizon res_collapsed <- estimate_risk_partial( age = -50, sex = list(TRUE, FALSE), quiet = TRUE, time = 10, model = list(other_models = "pce_both", race_eth = "Black"), collapse = TRUE ) res_uncollapsed <- estimate_risk_partial( age = -50, sex = list(TRUE, FALSE), quiet = TRUE, time = 10, model = list(other_models = "pce_both", race_eth = "Black"), collapse = FALSE ) expect_identical(res_uncollapsed, res_collapsed) expect_equal(nrow(res_uncollapsed), 3) # 3 b/c 3 models expect_equal(count_nas_from_res(res_uncollapsed), 15) # 15 b/c 3 models expect_equal(names(res_uncollapsed), names_when_tbl) # Difference here, b/c despite requesting 30-year time horizon, also # requests PCEs, thus automatically adding a 10-year time horizon res_collapsed <- estimate_risk_partial( age = -50, sex = list(TRUE, FALSE), quiet = TRUE, time = 30, model = list(other_models = "pce_both", race_eth = "Black"), collapse = TRUE ) res_uncollapsed <- estimate_risk_partial( age = -50, sex = list(TRUE, FALSE), quiet = TRUE, time = 30, model = list(other_models = "pce_both", race_eth = "Black"), collapse = FALSE ) expect_false(identical(res_uncollapsed, res_collapsed)) expect_false(is.data.frame(res_uncollapsed)) expect_true(is.list(res_uncollapsed)) expect_equal(length(res_uncollapsed), 2) # 10yr (PCEs) and 30yr (PREVENT) expect_equal(nrow(res_uncollapsed[["risk_est_10yr"]]), 2) # both PCE models expect_equal(nrow(res_uncollapsed[["risk_est_30yr"]]), 1) # 30-year PREVENT model expect_equal(names(res_uncollapsed), names_when_list) expect_equal(count_nas_from_res(res_uncollapsed[["risk_est_10yr"]]), 10) expect_equal(count_nas_from_res(res_uncollapsed[["risk_est_30yr"]]), 5) expect_true(is.data.frame(res_collapsed)) expect_equal(nrow(res_collapsed), 3) # 3 b/c 3 models expect_identical(res_uncollapsed %>% dplyr::bind_rows(), res_collapsed) expect_equal(count_nas_from_res(res_collapsed), 15) # Difference here, b/c requesting both time horizons and PCEs res_collapsed <- estimate_risk_partial( age = -50, sex = list(TRUE, FALSE), quiet = TRUE, model = list(other_models = "pce_both", race_eth = "Black"), collapse = TRUE ) res_uncollapsed <- estimate_risk_partial( age = -50, sex = list(TRUE, FALSE), quiet = TRUE, model = list(other_models = "pce_both", race_eth = "Black"), collapse = FALSE ) expect_false(identical(res_uncollapsed, res_collapsed)) expect_false(is.data.frame(res_uncollapsed)) expect_true(is.list(res_uncollapsed)) expect_equal(length(res_uncollapsed), 2) # 10yr and 30yr expect_equal(names(res_uncollapsed), names_when_list) expect_equal(nrow(res_uncollapsed[["risk_est_10yr"]]), 3) # 10-year PREVENT and both PCE models expect_equal(nrow(res_uncollapsed[["risk_est_30yr"]]), 1) # 30-year PREVENT model expect_equal(count_nas_from_res(res_uncollapsed[["risk_est_10yr"]]), 15) expect_equal(count_nas_from_res(res_uncollapsed[["risk_est_30yr"]]), 5) expect_true(is.data.frame(res_collapsed)) expect_equal(nrow(res_collapsed), 4) # 4 b/c 4 models expect_identical(res_uncollapsed %>% dplyr::bind_rows(), res_collapsed) expect_equal(count_nas_from_res(res_collapsed), 20) }) test_that("args `use_dat` and `add_to_dat` behave as intended, no arg sub", { dat <- make_dat(100) res_manual_not_added_to_dat <- do_lapply_for_use_dat_add_to_dat(dat, add_to_dat = FALSE, quiet = TRUE) res_manual_added_to_dat <- add_to_dat(dat, res_manual_not_added_to_dat) res_auto_not_added_to_dat <- est_risk(use_dat = dat, add_to_dat = FALSE, progress = FALSE) res_auto_added_to_dat <- est_risk(use_dat = dat, add_to_dat = TRUE, progress = FALSE) expect_identical(res_manual_not_added_to_dat, res_auto_not_added_to_dat) expect_identical(res_manual_added_to_dat, res_auto_added_to_dat) }) # When `use_dat` is a data.frame, for args corresponding to a predictor var, # the arg can either be omitted (in which case, the fx assumes the arg name # corresponds to the col name in the data.frame, or the user can specify a # different col name). The following tests ensure the fx behaves as inteded in # these cases. test_that("args `use_dat` and `add_to_dat` behave as intended, `age` sub", { test_diff_col_name(quote(age), quote(trips_around_the_sun), "valid_sub") test_diff_col_name(quote(age), quote(trips_around_the_sun), "invalid_sub") }) test_that("args `use_dat` and `add_to_dat` behave as intended, `sex` sub", { test_diff_col_name(quote(sex), quote(foo), "valid_sub") test_diff_col_name(quote(sex), quote(foo), "invalid_sub") }) test_that("args `use_dat` and `add_to_dat` behave as intended, `sbp` sub", { test_diff_col_name(quote(sbp), quote(blood_pressure), "valid_sub") test_diff_col_name(quote(sbp), quote(blood_pressure), "invalid_sub") }) test_that("args `use_dat` and `add_to_dat` behave as intended, `bp_tx` sub", { test_diff_col_name(quote(bp_tx), quote(blood_pressure_treatment), "valid_sub") test_diff_col_name(quote(bp_tx), quote(blood_pressure_treatment), "invalid_sub") }) test_that("args `use_dat` and `add_to_dat` behave as intended, `total_c` sub", { test_diff_col_name(quote(total_c), quote(total_cholesterol), "valid_sub") test_diff_col_name(quote(total_c), quote(total_cholesterol), "invalid_sub") }) test_that("args `use_dat` and `add_to_dat` behave as intended, `hdl_c` sub", { test_diff_col_name(quote(hdl_c), quote(hdl_cholesterol), "valid_sub") test_diff_col_name(quote(hdl_c), quote(hdl_cholesterol), "invalid_sub") }) test_that("args `use_dat` and `add_to_dat` behave as intended, `statin` sub", { test_diff_col_name(quote(statin), quote(statin_use), "valid_sub") test_diff_col_name(quote(statin), quote(statin_use), "invalid_sub") }) test_that("args `use_dat` and `add_to_dat` behave as intended, `dm` sub", { test_diff_col_name(quote(dm), quote(diabetes), "valid_sub") test_diff_col_name(quote(dm), quote(diabetes), "invalid_sub") }) test_that("args `use_dat` and `add_to_dat` behave as intended, `smoking` sub", { test_diff_col_name(quote(smoking), quote(smoking_status), "valid_sub") test_diff_col_name(quote(smoking), quote(smoking_status), "invalid_sub") }) test_that("args `use_dat` and `add_to_dat` behave as intended, `egfr` sub", { test_diff_col_name( quote(egfr), quote(estimated_glomerular_filtration_rate), "valid_sub" ) test_diff_col_name( quote(egfr), quote(estimated_glomerular_filtration_rate), "invalid_sub" ) }) test_that("args `use_dat` and `add_to_dat` behave as intended, `bmi` sub", { test_diff_col_name(quote(bmi), quote(body_mass_index), "valid_sub") test_diff_col_name(quote(bmi), quote(body_mass_index), "invalid_sub") }) test_that("args `use_dat` and `add_to_dat` behave as intended, `hba1c` sub", { test_diff_col_name(quote(hba1c), quote(sugar), "valid_sub") test_diff_col_name(quote(hba1c), quote(sugar), "invalid_sub") }) test_that("args `use_dat` and `add_to_dat` behave as intended, `uacr` sub", { test_diff_col_name(quote(uacr), quote(protein), "valid_sub") test_diff_col_name(quote(uacr), quote(protein), "invalid_sub") }) test_that("args `use_dat` and `add_to_dat` behave as intended, `zip` sub", { test_diff_col_name(quote(zip), quote(where_are_you), "valid_sub") test_diff_col_name(quote(zip), quote(where_are_you), "invalid_sub") }) test_that("args `use_dat` and `add_to_dat` behave as intended, `time` var", { # Test `time` arg being dominant if both `time` col and `time` arg exist dat <- make_mini_dat("time") res <- est_risk(use_dat = dat, time = "10yr", progress = FALSE) expect_equal(unique(res[["over_years"]]), 10) # Test `time` col functionality res <- est_risk(use_dat = dat, progress = FALSE) for(i in seq_len(nrow(dat))) { expected_over_years <- with( dat[i, ], if(time == "both") c(10, 30) else gsub("yr", "", time) ) expect_equal( res %>% dplyr::filter(preventr_id == i) %>% dplyr::pull(over_years), as.integer(expected_over_years) ) } }) test_that("args `use_dat` and `add_to_dat` behave as intended, `model` var", { # Test `model` arg being dominant if both `model` col and `model` arg exist dat <- make_mini_dat("model") res <- est_risk(use_dat = dat, model = "base", progress = FALSE) expect_equal(unique(res[["model"]]), "base") # Test `model` col functionality res <- est_risk(use_dat = dat, progress = FALSE) for(i in seq_len(nrow(dat))) { # With generated data, the main model is not specified, thus selected # automatically based on availability of optional PREVENT predictor vars, # but the PCE models are randomly specified, so interrogate which PCE # model(s) are going to be requested expected <- with( dat[i, ], { model_input <- unlist(model) prevent_model <- select_model(hba1c, uacr, zip) # `isTRUE()` here in case `model_input` is >1 in length pce_model <- if(isTRUE(is.na(model_input))) { NULL } else { if(model_input[["other_models"]] == "pce_both") { c("pce_orig", "pce_rev") } else { model_input[["other_models"]] } } list(prevent_model = prevent_model, pce_model = pce_model) } ) expect_equal( res %>% dplyr::filter(preventr_id == i) %>% dplyr::pull(model), # Because default of `time` is "both", can anticipate the model ordering # will be PREVENT models (for 10-year), PCE models (for 10-year), and # PREVENT models (for 30-year) c( expected[["prevent_model"]], expected[["pce_model"]], expected[["prevent_model"]] ) ) } }) test_that("args `use_dat` and `add_to_dat` behave as intended, `chol_unit` var", { dat <- make_mini_dat("chol_unit") res <- est_risk(use_dat = dat, chol_unit = "mg/dL", progress = FALSE) # If the above worked, there should be no NA values in the risk estimations # returned, because even though some of the input data will have total_c and # hdl_c values intended for mg/dL despite having col value "mmol/L" for # `chol_unit`, passing `chol_unit = "mg/dL"` should functionally override the # col values for `chol_unit` expect_equal(count_nas_from_res(res), 0) expect_equal(unique(res[["input_problems"]]), NA_character_) # Now, don't pass `chol_unit` as an arg, and there should be # 10 * sum(dat[["chol_unit"]] == "mmol/L") NA values in the risk estimations, # b/c for each instance of "mmol/L" in the `chol_unit` col, there will be # 2 rows with NAs in the return, given the default for `time` is "both", # and each row will have 5 outcomes, so 2 * 5 = 10 res <- est_risk(use_dat = dat, quiet = TRUE) expect_equal(count_nas_from_res(res), 10 * sum(dat[["chol_unit"]] == "mmol/L")) for(i in seq_len(nrow(res))) { with( res[i, ], if(chol_unit == "mmol/L") { expect_true(grepl("`total_c` entered as", input_problems)) expect_true(grepl("`hdl_c` entered as", input_problems)) } else { expect_equal(input_problems, NA_character_) } ) } }) test_that("args `use_dat` and `add_to_dat` behave as intended, `optional_strict` and `quiet` vars", { # `optional_strict` ---- dat <- make_mini_dat("optional_strict") # Edit some HbA1cs to be invalid row_subset <- c(1, 3, 5, 7, 9) dat[["hba1c"]][row_subset] <- 8675309 res <- est_risk(use_dat = dat, time = "both", optional_strict = TRUE, progress = FALSE) for(i in seq_len(nrow(res))) { with( res[i, ], { outcomes_in_res <- list( total_cvd = total_cvd, ascvd = ascvd, heart_failure = heart_failure, chd = chd, stroke = stroke ) if(isTRUE(hba1c == 8675309)) { expect_equal(count_nas_from_res(outcomes_in_res), 5) expect_equal(input_problems, "`hba1c` entered as 8675309, but must be between 4.5 and 15") } else { expect_equal(count_nas_from_res(outcomes_in_res), 0) expect_equal(input_problems, NA_character_) } } ) } # Now change `optional_strict` to `FALSE`, and there should be no NA values # for risk estimation, but `input_problems` column should still alert re: # invalid HbA1c values, this time also noting the HbA1c values were set to NULL res <- est_risk(use_dat = dat, time = "both", optional_strict = FALSE, progress = FALSE) for(i in seq_len(nrow(res))) { with( res[i, ], { expect_equal( count_nas_from_res( list( total_cvd = total_cvd, ascvd = ascvd, heart_failure = heart_failure, chd = chd, stroke = stroke ) ), 0 ) if(isTRUE(hba1c == 8675309)) { # Note slight change in text here (addition of " (so set to NULL)"), # which is expected when an optional predictor var has probs but # `optional_strict = FALSE` expect_equal(input_problems, "`hba1c` entered as 8675309, but must be between 4.5 and 15 (so set to NULL)") } else { expect_equal(input_problems, NA_character_) } } ) } # `quiet` ---- # Re-use `dat` from testing of `optional_strict` # Note the below assignment of `quiet` vals will mean `quiet = TRUE` # for the invalid HbA1c values assigned when testing `optional_strict` # (i.e., `quiet = TRUE` for rows 1, 3, 5, 7, 9) expect_no_message(est_risk(use_dat = dat, progress = FALSE)) expect_message( est_risk(use_dat = make_dat(4), quiet = FALSE, progress = FALSE), "PREVENT estimates are from" ) dat <- dat %>% dplyr::mutate(quiet = rep(c(TRUE, FALSE), 5)) for(i in seq_len(nrow(dat))) { if(isTRUE(dat[i, "hba1c"] == 8675309)) { # Test subset of `dat` where col `quiet` is TRUE # setting `quiet = FALSE` should result in messages about `hba1c` values # and the PREVENT estimates despite `quiet = TRUE` in the `dat` subset expect_message( est_risk(use_dat = dat[i, ], quiet = FALSE, progress = FALSE), "`hba1c` entered as 8675309" ) # And if `optional_strict = FALSE`, there should be a message about the # PREVENT estimates (b/c if `optional_strict = TRUE`, the invalid HbA1c # will prevent estimates from being calculated) if(!dat[i, "optional_strict"]) { expect_message( est_risk(use_dat = dat[i, ], quiet = FALSE, progress = FALSE), "PREVENT estimates are from" ) } # And if not overriding with `quiet = FALSE`, there should be no messages, # b/c the col value is TRUE expect_no_message(est_risk(use_dat = dat[i, ], progress = FALSE)) } else { # Test subset of `dat` where col `quiet` is FALSE # omitting the `quiet` arg should result in messages about the PREVENT # estimates (but not about the `hba1c` values, because these rows in `dat` # were not altered in testing of `optional_strict` to have invalid HbA1c vals expect_message( est_risk(use_dat = dat[i, ], progress = FALSE), "PREVENT estimates are from" ) expect_no_message( est_risk(use_dat = dat[i, ], quiet = TRUE, progress = FALSE) ) } } }) test_that("further testing of use of data frame", { # This set of tests is based on the vignette "using-data-frame", and is in # many ways identical to the content therein, but there are differences. For # example, it takes a different approach to make and edit the data being used # (though relevant data are identical), changes demonstrations into explicit # tests, and sometimes slightly tweaks the code, such as to make things quiet # and to use `%>%` and `.` instead of `|>` and `_`. As such, commentary herein # may be relatively sparse, aside from where commentary from the vignette was # left in situ and/or possibly adding comments if something changed from the # vignette, perhaps especially if the change might be subtle. Otherwise, see # the vignette if needed. dat <- make_dat(10) %>% # Specifying `age`, `sex`, `egfr`, and `bmi` manually while letting # other parameters vary to facilitate later aspects of this # testing (to show identical results from approaches below) dplyr::mutate( age = c(40, 55, 45, 51, 52, 58, 57, 36, 49, 47), sex = rep(c("female", "male"), 5), egfr = c(73, 71, 80, 73, 77, 70, 86, 89, 78, 68), bmi = c(37.4, 32.9, 37.5, 28.6, 37.5, 36.0, 36.7, 28.6, 18.7, 38.6) ) # Showing you can pass a different name for the column containing the # predictor res <- est_risk(use_dat = dat, progress = FALSE) dat_age_rename <- dat %>% dplyr::rename(years_old = age) res_age_rename_sym <- est_risk( use_dat = dat_age_rename, age = years_old, progress = FALSE ) res_age_rename_chr <- est_risk( use_dat = dat_age_rename, age = "years_old", progress = FALSE ) # Will be `FALSE` because names of `age` columns differ expect_false(identical(res, res_age_rename_sym)) expect_false(identical(res, res_age_rename_chr)) # But this will be `TRUE` expect_identical(res_age_rename_sym, res_age_rename_chr) # And if we change the names res_age_rename_sym <- res_age_rename_sym %>% dplyr::rename(age = years_old) res_age_rename_chr <- res_age_rename_chr %>% dplyr::rename(age = years_old) # Everything is identical expect_identical(res, res_age_rename_sym) expect_identical(res, res_age_rename_chr) # Now adding time and model data dat_time_model <- dat %>% dplyr::mutate( time = sample(c("10yr", "30yr", rep("both", 2)), 10, replace = TRUE), model = sample(c("base", "hba1c", "uacr", "sdi", "full"), 10, replace = TRUE) ) # Confirming variation in the columns expect_gt(length(dat_time_model[["time"]]), 1) expect_gt(length(dat_time_model[["model"]]), 1) # Added `quiet = TRUE` (vignette does not specify) res_time_model_in_dat <- est_risk(use_dat = dat_time_model, progress = FALSE) # Added `quiet = TRUE` (vignette does not specify) # Note specification of arguments `model` and `time` in the function call, # which will override anything in the columns of the data frame passed to # `use_dat` res_time_and_model_in_call <- est_risk( use_dat = dat_time_model, time = 10, model = "base", progress = FALSE ) expect_equal(unique(res_time_and_model_in_call[["over_years"]]), 10) expect_equal(unique(res_time_and_model_in_call[["model"]]), "base") res_time_and_model_in_call <- est_risk( use_dat = dat_time_model |> dplyr::mutate(model = "base"), model = NULL, progress = FALSE ) expect_equal(unique(res_time_and_model_in_call[["model_input"]]), "base") expect_gt(length(unique(res_time_and_model_in_call[["model"]])), 1) # Showing identicality of `add_to_dat = TRUE` and `add_to_dat = FALSE` followed # by a join res_without_dat <- est_risk( use_dat = dat_time_model, add_to_dat = FALSE, progress = FALSE ) res_with_dat <- est_risk(use_dat = dat_time_model, progress = FALSE) # Now, let's check identicality of `res_with_dat` with a version we # recreate using `dat` and `res_without_dat` dat_for_join <- dat_time_model %>% # Add `preventer_id` column ... dplyr::mutate(preventr_id = seq_len(nrow(dat_time_model))) %>% # ... and move it to be the first column in the data frame. dplyr::relocate(preventr_id) # Do left join res_with_dat_manual_join <- dat_for_join %>% dplyr::left_join( res_without_dat, by = "preventr_id", # Because both data frames will have a column named `model`, I'll provide # suffixes to distinguish them. The suffixes below will result in the # column `model` in `dat_for_join` being renamed to `model_input` and # column `model` in the data frame `res_without_dat` retaining the same # name. suffix = c("_input", "") ) expect_identical(res_with_dat, res_with_dat_manual_join) # Type stability dat_tbl <- dat %>% dplyr::mutate(quiet = TRUE) dat_df <- as.data.frame(dat_tbl) res_tbl <- est_risk(use_dat = dat_tbl, progress = FALSE) # Return: tibble res_df <- est_risk(use_dat = dat_df, progress = FALSE) # Return: data frame expect_identical(class(dat_tbl), class(res_tbl)) expect_identical(class(dat_df), class(res_df)) # Other than the attributes, these are all equal (of course) expect_true(all.equal(res_tbl, res_df, check.attributes = FALSE)) if(requireNamespace("data.table", quietly = TRUE)) { dat_dt <- data.table::as.data.table(dat_tbl) res_dt <- est_risk(use_dat = dat_dt, progress = FALSE) # Return: data.table expect_identical(class(dat_dt), class(res_dt)) expect_true(all.equal(res_tbl, res_dt, check.attributes = FALSE)) } dat_with_pce_requests <- dat_time_model %>% dplyr::mutate( model = lapply( seq_len(nrow(dat)), function(x) { if(x %% 2 == 0) { NA } else { list( main_model = sample(c("base", "hba1c", "uacr", "sdi", "full"), 1), other_models = sample(c("pce_both", "pce_rev", "pce_orig"), 1), race_eth = sample(c("Black", "White", "Other"), 1) ) } } ) ) res_with_pce_requests <- est_risk( use_dat = dat_with_pce_requests, progress = FALSE ) identical_cols <- vapply( seq_len(nrow(dat_with_pce_requests)), function(x) { n_row <- res_with_pce_requests |> dplyr::filter(preventr_id == x) |> nrow() identical( rep(dat_with_pce_requests[["model"]][x], n_row), res_with_pce_requests |> dplyr::filter(preventr_id == x) |> dplyr::pull(model_input) ) }, logical(1) ) expect_true(all(identical_cols)) # Omitted the remaining `res_with_pce_requests` and `dat_with_calls_basic` # portions (nothing of much value to test there) dat_with_cr_cm_kg <- dat_with_pce_requests %>% dplyr::mutate( # Let's use values for `cr` (in mg/dL), `cm`, and `kg` that would yield # the values originally entered directly for `egfr` and `bmi` to demonstrate # identical results when using the direct values for eGFR and BMI vs. using # calls to the convenience functions. This is why the `dat` starts by # specifying values for `age`, `sex`, `egfr`, and `bmi`. cr = c(1, 1.2, 0.9, 1.2, 0.9, 1.2, 0.8, 1.1, 0.9, 1.3), cm = c(199, 182, 184, 197, 189, 187, 191, 163, 199, 171), kg = c(148, 109, 127, 111, 134, 126, 134, 76, 74, 113), # Now, we'll create new columns for calls for eGFR and BMI (and remember, # `dat_with_pce_requests` will already have columns for `egfr` and `bmi`). egfr_call = lapply( seq_len(nrow(dat_with_pce_requests)), function(x) { call("calc_egfr", cr = cr[[x]]) } ), bmi_call = lapply( seq_len(nrow(dat_with_pce_requests)), function(x) { call("calc_bmi", height = cm[[x]], weight = kg[[x]], units = "metric") } ) ) res_with_calls <- est_risk( use_dat = dat_with_cr_cm_kg, # Instruct `est_risk()` to use the call columns, else it will default to # grabbing values from `egfr` and `bmi`, which have direct values in them egfr = "egfr_call", # Again, can pass column names as a character string ... bmi = bmi_call, # ... or as a symbol progress = FALSE ) res_without_calls <- est_risk( use_dat = dat_with_cr_cm_kg, # If you don't specify the call columns, `est_risk()` will default to using # the columns `egfr` and `bmi`, which have the original, direct values for # eGFR and BMI progress = FALSE ) expect_identical(res_with_calls, res_without_calls) # Show identicality of results using non-`est_risk()` approaches dat_with_cr_cm_kg <- dat_with_cr_cm_kg %>% dplyr::mutate(preventr_id = seq_len(nrow(dat))) %>% dplyr::relocate(preventr_id) # Test w/ `lapply()` first res_basic_lapply <- lapply( # For each row of `dat_with_cr_cm_kg`... seq_len(nrow(dat_with_cr_cm_kg)), function(x) { # ... run `est_risk()` on the values therein est_risk( age = dat_with_cr_cm_kg[["age"]][[x]], sex = dat_with_cr_cm_kg[["sex"]][[x]], sbp = dat_with_cr_cm_kg[["sbp"]][[x]], bp_tx = dat_with_cr_cm_kg[["bp_tx"]][[x]], total_c = dat_with_cr_cm_kg[["total_c"]][[x]], hdl_c = dat_with_cr_cm_kg[["hdl_c"]][[x]], statin = dat_with_cr_cm_kg[["statin"]][[x]], dm = dat_with_cr_cm_kg[["dm"]][[x]], smoking = dat_with_cr_cm_kg[["smoking"]][[x]], egfr = dat_with_cr_cm_kg[["egfr"]][[x]], bmi = dat_with_cr_cm_kg[["bmi"]][[x]], hba1c = dat_with_cr_cm_kg[["hba1c"]][[x]], uacr = dat_with_cr_cm_kg[["uacr"]][[x]], zip = dat_with_cr_cm_kg[["zip"]][[x]], model = dat_with_cr_cm_kg[["model"]][[x]], time = dat_with_cr_cm_kg[["time"]][[x]], quiet = TRUE ) %>% dplyr::bind_rows() %>% # Add column `preventr_id` to facilitate reassociation with the input # data frame dplyr::mutate(preventr_id = x) } ) %>% # Bind all the results from the `lapply()` call together to make a # single data frame dplyr::bind_rows() %>% # Finally, do a quick left join to match the results in `res` with their # # corresponding input row in `dat` dplyr::left_join( x = dat_with_cr_cm_kg, y = ., by = "preventr_id", # Because both data frames will have a column named `model`, we'll provide # suffixes to distinguish them. The suffixes below will cause the column # `model` in `dat_with_cr_cm_kg` to be renamed to `model_input` and column # `model` in the data frame from the pipe sequence (represented via `_`) # retaining the same name. suffix = c("_input", "") ) # If all has proceeded as it should've, `res_basic_lapply` should be identical # to `res_without_calls` (and thus also to `res_with_calls`) from the above # example (spoiler, it will be) expect_identical(res_basic_lapply, res_without_calls) # In what follows, I define a function that allows me to (1) alter the # behavior of both the `lapply()` call (by specifying what the first variable # in the call to `with()`) and (2) alter the arguments passed to the # `est_risk()` call inside the `lapply()` call. do_lapply_and_join <- function(dat, with_arg, ..., eval = TRUE) { dat <- substitute(dat) with_arg <- substitute(with_arg) dots <- eval(substitute(alist(...))) mini_cl <- bquote( { lapply( # For each row of `dat`... seq_len(nrow(.(dat))), function(x) { with( # With the data mask contained in `with_arg` ... .(with_arg), # ... run `est_risk()` with the arguments contained within `dots` est_risk(..(dots)) ) %>% # The vast majority of the following is nearly verbatim as the basic # `lapply()` example; it does not make any further use of # metaprogramming unless otherwise noted dplyr::bind_rows() %>% dplyr::mutate(preventr_id = x) } ) %>% dplyr::bind_rows() %>% dplyr::left_join( x = .(dat), # Note the use of `.(dat)` y = ., by = "preventr_id", suffix = c("_input", "") ) }, splice = TRUE # This tells `bquote()` to splice anything in `..()` ) if(eval) eval(mini_cl, parent.frame()) else mini_cl } # Let's start by showing results identical to `res_basic_lapply` res_aug_lapply <- do_lapply_and_join( dat = dat_with_cr_cm_kg, with_arg = dat_with_cr_cm_kg[x, ], age = age, sex = sex, sbp = sbp, bp_tx = bp_tx, total_c = total_c, hdl_c = hdl_c, statin = statin, dm = dm, smoking = smoking, egfr = egfr, bmi = bmi, hba1c = hba1c, uacr = uacr, zip = zip, # Because of the data mask passed via argument `with_arg`, the evaluation # environment will be row x of the data frame (where x is defined within the # `lapply()` call). Thus, `model` will still be a list column, so I need to # get that list item out of the list column before passing it to # `est_risk()`. # # For `model`, I could instead do `unlist()`, but given this vignette also # demonstrates list columns containing calls (where `unlist()` will not do), # I will use `[[1]]` here for consistency. Note I can be confident the list # item I need from the list column `model` is indeed the first (and only) # list item, and the list item I extract via `[[1]]` will then either be # `NA` or a list with list items `main_model`, `other_models`, and # `race_eth` given how I created `dat_with_cr_cm_kg`. model = model[[1]], time = time, quiet = TRUE ) expect_identical(res_aug_lapply, res_basic_lapply) res_aug_lapply_variant <- do_lapply_and_join( dat = dat_with_cr_cm_kg, with_arg = dat_with_cr_cm_kg, age = age[[x]], sex = sex[[x]], sbp = sbp[[x]], bp_tx = bp_tx[[x]], total_c = total_c[[x]], hdl_c = hdl_c[[x]], statin = statin[[x]], dm = dm[[x]], smoking = smoking[[x]], egfr = egfr[[x]], bmi = bmi[[x]], hba1c = hba1c[[x]], uacr = uacr[[x]], zip = zip[[x]], model = model[[x]], time = time[[x]], quiet = TRUE ) expect_identical(res_aug_lapply_variant, res_basic_lapply) res_aug_lapply_with_calls <- do_lapply_and_join( dat = dat_with_cr_cm_kg, with_arg = dat_with_cr_cm_kg[x, ], age = age, sex = sex, sbp = sbp, bp_tx = bp_tx, total_c = total_c, hdl_c = hdl_c, statin = statin, dm = dm, smoking = smoking, # If needed, review comment associated with generation of # `res_not_so_basic_lapply` to understand why arguments `egfr`, `bmi`, # and `model` are specified like this. egfr = egfr_call[[1]], bmi = bmi_call[[1]], hba1c = hba1c, uacr = uacr, zip = zip, model = model[[1]], time = time, quiet = TRUE ) expect_identical(res_aug_lapply_with_calls, res_basic_lapply) res_aug_lapply_with_calls_in_flight <- do_lapply_and_join( dat = dat_with_cr_cm_kg, with_arg = dat_with_cr_cm_kg[x, ], age = age, sex = sex, sbp = sbp, bp_tx = bp_tx, total_c = total_c, hdl_c = hdl_c, statin = statin, dm = dm, smoking = smoking, egfr = call("calc_egfr", cr = cr), bmi = call("calc_bmi", height = cm, weight = kg, units = "metric"), hba1c = hba1c, uacr = uacr, zip = zip, model = model[[1]], time = time, quiet = TRUE ) expect_identical(res_aug_lapply_with_calls_in_flight, res_basic_lapply) res_auto_opts_in_call <- est_risk( use_dat = dat_with_cr_cm_kg, model = "base", time = "10yr", progress = FALSE ) res_aug_lapply_opts_in_call <- do_lapply_and_join( dat = dat_with_cr_cm_kg, with_arg = dat_with_cr_cm_kg[x, ], age = age, sex = sex, sbp = sbp, bp_tx = bp_tx, total_c = total_c, hdl_c = hdl_c, statin = statin, dm = dm, smoking = smoking, egfr = egfr, bmi = bmi, hba1c = hba1c, uacr = uacr, zip = zip, model = "base", time = "10yr", quiet = TRUE ) expect_identical(res_auto_opts_in_call, res_aug_lapply_opts_in_call) # Now let's test with `Map()` do_map_and_join <- function(dat, ...) { dat <- dat %>% dplyr::mutate(preventr_id = seq_len(nrow(dat))) dots <- eval(substitute(alist(...))) res <- eval( bquote( # With the data mask introduced by `dat`, evaluate `Map()` with the # function `est_risk()` and the arguments contained in `dots`. # (In other words, call `est_risk()` with the arguments in dots for # each row of `dat`.) with(dat, Map(est_risk, ..(dots))), splice = TRUE ) ) # `res` from the above call to `Map()` will be a list, and the items in # the list may also be a list (e.g., a list of data frames), as such, we'll # need to iterate through `res` and bind the data frames together. We'll also # need to add the `preventr_id` column. for(i in seq_along(res)) { res[[i]] <- res[[i]] %>% dplyr::bind_rows() %>% dplyr::mutate(preventr_id = i) %>% dplyr::relocate(preventr_id) } # Now just do the left join, detailed previously in this vignette. dplyr::left_join( x = dat, y = dplyr::bind_rows(res), by = "preventr_id", suffix = c("_input", "") ) } res_map <- do_map_and_join( dat_with_cr_cm_kg, age = age, sex = sex, sbp = sbp, bp_tx = bp_tx, total_c = total_c, hdl_c = hdl_c, statin = statin, dm = dm, smoking = smoking, egfr = egfr, bmi = bmi, hba1c = hba1c, uacr = uacr, zip = zip, model = "base", time = "10yr", quiet = TRUE ) expect_identical(res_auto_opts_in_call, res_map) res_map_all_cols <- do_map_and_join( dat_with_cr_cm_kg, age = age, sex = sex, sbp = sbp, bp_tx = bp_tx, total_c = total_c, hdl_c = hdl_c, statin = statin, dm = dm, smoking = smoking, # Note I'm passing the call columns here, showing you can still use the # convenience functions (stored as calls in list columns) with `Map()` egfr = egfr_call, bmi = bmi_call, hba1c = hba1c, uacr = uacr, zip = zip, model = model, time = time, quiet = TRUE ) expect_identical(res_map_all_cols, res_basic_lapply) res_map_only_10yr_hba1c_not_quiet <- do_map_and_join( dat_with_cr_cm_kg, age = age, sex = sex, sbp = sbp, bp_tx = bp_tx, total_c = total_c, hdl_c = hdl_c, statin = statin, dm = dm, smoking = smoking, egfr = egfr_call, bmi = bmi_call, hba1c = hba1c, uacr = uacr, zip = zip, model = "hba1c", time = "10yr", quiet = FALSE ) # Despite `dat_with_cr_cm_kg` having column `model` in the data frame, # the `model` argument in the call to `est_risk()` (via `Map()`) gets priority. expect_gt(length(dat_with_cr_cm_kg[["model"]]), 1) expect_gt(length(dat_with_cr_cm_kg[["time"]]), 1) expect_equal(unique(res_map_only_10yr_hba1c_not_quiet[["over_years"]]), 10) expect_equal(unique(res_map_only_10yr_hba1c_not_quiet[["model"]]), "hba1c") # `purrr::pmap()` if(requireNamespace("purrr", quietly = TRUE)) { pmap_data_frame_approach <- dat_with_cr_cm_kg %>% # Remove columns not corresponding to an argument in `est_risk()` dplyr::select(-c(preventr_id, cr, cm, kg, egfr_call, bmi_call)) %>% purrr::pmap(est_risk) # Very similar to the `Map()` examples above, we'll need to bind the results # from `purrr::pmap()` together and do some other minor actions, so I've # converted that into a mini-function to avoid repetition in these examples. combine_pmap_res_and_join <- function(pmap_res, dat) { for(i in seq_along(pmap_res)) { pmap_res[[i]] <- pmap_res[[i]] %>% dplyr::bind_rows() %>% dplyr::mutate(preventr_id = i) %>% dplyr::relocate(preventr_id) } dplyr::left_join( x = dat, y = dplyr::bind_rows(pmap_res), by = "preventr_id", suffix = c("_input", "") ) } pmap_data_frame_approach <- combine_pmap_res_and_join(pmap_data_frame_approach, dat_with_cr_cm_kg) expect_identical(pmap_data_frame_approach, res_basic_lapply) pmap_list_approach <- purrr::pmap( with( dat_with_cr_cm_kg, list( age = age, sex = sex, sbp = sbp, bp_tx = bp_tx, total_c = total_c, hdl_c = hdl_c, statin = statin, dm = dm, smoking = smoking, egfr = egfr, bmi = bmi, hba1c = hba1c, uacr = uacr, zip = zip, model = model, time = time, # Note passing an explicitly-delineated list for argument `.l` allows us # to easily specify the `quiet` argument here quiet = TRUE ) ), est_risk ) pmap_list_approach <- combine_pmap_res_and_join(pmap_list_approach, dat_with_cr_cm_kg) expect_identical(pmap_list_approach, res_basic_lapply) pmap_list_approach_with_call <- purrr::pmap( with( dat_with_cr_cm_kg, list( age = age, sex = sex, sbp = sbp, bp_tx = bp_tx, total_c = total_c, hdl_c = hdl_c, statin = statin, dm = dm, smoking = smoking, egfr = egfr_call, bmi = bmi_call, hba1c = hba1c, uacr = uacr, zip = zip, model = model, time = time, quiet = TRUE ) ), est_risk ) pmap_list_approach_with_call <- combine_pmap_res_and_join(pmap_list_approach_with_call, dat_with_cr_cm_kg) expect_identical(pmap_list_approach_with_call, res_basic_lapply) } }) test_that("app() function works", { skip_on_cran() expect_null(app()) })