tests/testthat/test-ggrmsMD.R

library(testthat)
library(rms)

## ---- CORE INPUT CHECKS ----

# Test that ggrmsMD errors if a non-rms object is supplied
test_that("Error if non-rms object is supplied", {
        data <- simulated_rmsMD_data()
        lmfit <- lm(lengthstay ~ age + bmi, data = data)
        expect_error(
                ggrmsMD(lmfit, data),
                "modelfit is not from an rms model"
        )
})

# Test that ggrmsMD errors if there are no RCS variables in the model
test_that("Error if no RCS variables in model", {
        data <- simulated_rmsMD_data()
        dd <- datadist(data)
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit <- rms::ols(lengthstay ~ age + bmi, data = data)
        expect_error(
                ggrmsMD(fit, data),
                "No variables specified, and no RCS variables in model"
        )
})

# Test error if variable in `var` argument is not numeric
test_that("Error if var specified is not numeric", {
        data <- simulated_rmsMD_data()
        dd <- datadist(data)
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit <- rms::ols(lengthstay ~ rcs(age, 4) + rcs(bmi, 4), data = data)
        expect_error(
                suppressWarnings(
                        ggrmsMD(fit, data, var = "sex")
                ),
                "All variables being plotted must be numeric."
        )
})

# Test warning if variable in `var` argument is not modelled as RCS
test_that("Warning if var specified is not in model as RCS", {
        data <- simulated_rmsMD_data()
        dd <- datadist(data)
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit <- rms::ols(lengthstay ~ rcs(age, 4), data = data)
        expect_warning(
                try(ggrmsMD(fit, data, var = "bmi"), silent = TRUE),
                "Some selected variables were not modelled as RCS"
        )
})

## ---- BASIC OUTPUT TYPES ----

# Test that ggrmsMD returns a ggplot object for a single RCS variable
test_that("Returns ggplot for a single RCS variable (OLS)", {
        data <- simulated_rmsMD_data()
        dd <- datadist(data)
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit <- rms::ols(lengthstay ~ rcs(age, 4), data = data)
        p <- ggrmsMD(fit, data)
        expect_s3_class(p, "ggplot")
})

# Test that ggrmsMD returns a list of ggplots for multiple RCS variables
test_that("Returns list of ggplots for multiple RCS variables", {
        data <- simulated_rmsMD_data()
        dd <- datadist(data)
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit <- rms::ols(lengthstay ~ rcs(age, 4) + rcs(bmi, 4), data = data)
        plots <- ggrmsMD(fit, data, combined = FALSE)
        expect_type(plots, "list")
        expect_s3_class(plots[[1]], "ggplot")
        expect_equal(length(plots), 2)
})

# Test that ggrmsMD returns cowplot::plot_grid when combined = TRUE
test_that("Returns cowplot::plot_grid when combined = TRUE", {
        skip_if_not_installed("cowplot")
        data <- simulated_rmsMD_data()
        dd <- datadist(data)
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit <- rms::ols(lengthstay ~ rcs(age, 4) + rcs(bmi, 4), data = data)
        combined_plot <- ggrmsMD(fit, data, combined = TRUE)
        expect_true(any(class(combined_plot) %in% c("gg", "gtable")))
})

## ---- DIFFERENT MODEL TYPES ----

# Test that ggrmsMD works for lrm (logistic regression) models (OR)
test_that("Works for lrm models (odds ratio)", {
        data <- simulated_rmsMD_data()
        data$bin_outcome <- as.factor(data$lengthstay > median(data$lengthstay))
        dd <- datadist(data)
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit <- rms::lrm(bin_outcome ~ rcs(age, 4), data = data)
        p <- ggrmsMD(fit, data)
        expect_s3_class(p, "ggplot")
})

# Test that ggrmsMD works for lrm models with lrm_prob = TRUE (predicted probability)
test_that("Works for lrm models (predicted probability)", {
        data <- simulated_rmsMD_data()
        data$bin_outcome <- as.factor(data$lengthstay > median(data$lengthstay))
        dd <- datadist(data)
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit <- rms::lrm(bin_outcome ~ rcs(age, 4), data = data)
        p <- ggrmsMD(fit, data, lrm_prob = TRUE)
        expect_s3_class(p, "ggplot")
        expect_match(p$labels$y, "Predicted probability")
})

# Test that ggrmsMD works for cph (Cox) models
test_that("Works for cph models", {
        data <- simulated_rmsMD_data()
        # Fake a time-to-event outcome just for test (lengthstay as time, majorcomplication as event)
        dd <- datadist(data)
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit <- rms::cph(Surv(lengthstay, majorcomplication) ~ rcs(age, 4), data = data, x = TRUE, y = TRUE)
        p <- ggrmsMD(fit, data)
        expect_s3_class(p, "ggplot")
})

## ---- ARGUMENT/FEATURE TESTS ----

# Test custom axis labels and titles
test_that("Custom x/y labels and plot titles work", {
        data <- simulated_rmsMD_data()
        dd <- datadist(data)
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit <- rms::ols(lengthstay ~ rcs(age, 4), data = data)
        xlabs <- list(age = "Patient Age (years)")
        titles <- list(age = "Spline of Age")
        p <- ggrmsMD(fit, data, xlabs = xlabs, ylab = "Custom Y", titles = titles)
        expect_identical(p$labels$x, "Patient Age (years)")
        expect_identical(p$labels$y, "Custom Y")
        expect_identical(p$labels$title, "Spline of Age")
})

# Test custom limits and scaling
test_that("Custom y-axis and x-axis limits work", {
        data <- simulated_rmsMD_data()
        dd <- datadist(data)
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit <- rms::ols(lengthstay ~ rcs(age, 4), data = data)
        p <- ggrmsMD(fit, data, ylim = c(0, 50), xlims = list(age = c(20, 80)))
        # Test y-axis limits set in the coordinates
        expect_equal(p$coordinates$limits$y, c(0, 50))
        # Optionally, also test x-axis limits
        expect_equal(p$coordinates$limits$x, c(20, 80))
})

# Test log y and log x axes
test_that("log_y and log_x_vars options work without error", {
        data <- simulated_rmsMD_data()
        data$logage <- data$age + 25 # ensure >0
        dd <- datadist(data)
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit <- rms::ols(lengthstay ~ rcs(logage, 4), data = data)
        expect_s3_class(
                ggrmsMD(fit, data, log_y = TRUE, log_x_vars = "logage"),
                "ggplot"
        )
})

# Test shade_inferior argument
test_that("shade_inferior works for 'higher' and 'lower'", {
        data <- simulated_rmsMD_data()
        data$bin_outcome <- as.factor(data$lengthstay > median(data$lengthstay))
        dd <- datadist(data)
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit <- rms::lrm(bin_outcome ~ rcs(age, 4), data = data)
        expect_s3_class(
                ggrmsMD(fit, data, shade_inferior = "higher"),
                "ggplot"
        )
        expect_s3_class(
                ggrmsMD(fit, data, shade_inferior = "lower"),
                "ggplot"
        )
})

# Test omitting the no-effect line
test_that("noeffline = FALSE omits the dashed line", {
        data <- simulated_rmsMD_data()
        data$bin_outcome <- as.factor(data$lengthstay > median(data$lengthstay))
        dd <- datadist(data)
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit <- rms::lrm(bin_outcome ~ rcs(age, 4), data = data)
        p <- ggrmsMD(fit, data, noeffline = FALSE)
        # Just check it returns a plot, visual checks can be added if needed
        expect_s3_class(p, "ggplot")
})

# Test custom number of prediction points
test_that("np argument works for prediction points", {
        data <- simulated_rmsMD_data()
        dd <- datadist(data)
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit <- rms::ols(lengthstay ~ rcs(age, 6), data = data)
        p_10 <- ggrmsMD(fit, data, np = 10)
        expect_s3_class(p_10, "ggplot")
})

## ---- EDGE/ROBUSTNESS TESTS ----

# Test using only a subset of RCS variables via var
test_that("Subset of RCS variables via var works", {
        data <- simulated_rmsMD_data()
        dd <- datadist(data)
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit <- rms::ols(lengthstay ~ rcs(age, 4) + rcs(bmi, 4), data = data)

        # Ask for just one RCS variable (e.g. "age")
        plot <- ggrmsMD(fit, data, var = "age", combined = FALSE)
        # Should return a single ggplot object, not a list
        expect_s3_class(plot, "ggplot")

        # If you request both, should return a list of two ggplots
        plotlist <- ggrmsMD(fit, data, var = c("age", "bmi"), combined = FALSE)
        expect_true(is.list(plotlist))
        expect_equal(length(plotlist), 2)
        expect_s3_class(plotlist[[1]], "ggplot")
        expect_s3_class(plotlist[[2]], "ggplot")
})

# Test that missing data does not cause unexpected crash
test_that("Function handles missing data gracefully", {
        data <- simulated_rmsMD_data()
        data$age[1:10] <- NA
        dd <- datadist(na.omit(data))
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit <- rms::ols(lengthstay ~ rcs(age, 4), data = na.omit(data))
        p <- ggrmsMD(fit, na.omit(data))
        expect_s3_class(p, "ggplot")
})

# Test repeated calls without resetting datadist
test_that("Multiple calls to ggrmsMD work without resetting datadist", {
        data <- simulated_rmsMD_data()
        dd <- datadist(data)
        assign("dd", dd, envir = .GlobalEnv)
        options(datadist = "dd")
        fit1 <- rms::ols(lengthstay ~ rcs(age, 4), data = data)
        fit2 <- rms::ols(lengthstay ~ rcs(bmi, 4), data = data)
        p1 <- ggrmsMD(fit1, data)
        p2 <- ggrmsMD(fit2, data)
        expect_s3_class(p1, "ggplot")
        expect_s3_class(p2, "ggplot")
})

Try the rmsMD package in your browser

Any scripts or data that you put into this service are public.

rmsMD documentation built on June 18, 2025, 1:08 a.m.