tests/testthat/test-roc.test.R

library(pROC)
data(aSAH)

context("roc.test")

# define variables shared among multiple tests here
roc.test_env <- environment()

test_that("roc.test works", {
	roc.test_env$t1 <- roc.test(r.wfns, r.s100b)
	roc.test_env$t2 <- roc.test(r.wfns, r.ndka)
	roc.test_env$t3 <- roc.test(r.ndka, r.s100b)
	expect_is(t1, "htest")
	expect_is(t2, "htest")
	expect_is(t3, "htest")
})


test_that("DeLong roc.test works when curves are identical", {
	t4 <- roc.test(r.wfns, r.wfns)
	expect_is(t4, "htest")
	expect_equal(t4$p.value, 1)
	expect_equal(t4$statistic, c(Z=0))
})


test_that("roc.test statistic and p are as expected with defaults", {
	expect_equal(t1$statistic, c(Z=2.20898359144091))
	expect_equal(t1$p.value, 0.0271757822291882)
	expect_equal(t1$conf.int[[1]], 0.0104061769564846)
	expect_equal(t1$conf.int[[2]], 0.174214419249478)
	expect_match(t1$method, "DeLong")
	expect_match(t1$method, "correlated")
	expect_identical(t1$alternative, "two.sided")
	expect_identical(attr(t1$conf.int, "conf.level"), 0.95)
	
	expect_equal(t2$statistic, c(Z=2.79777591868904))
	expect_equal(t2$p.value, 0.00514557970691098)
	expect_equal(t2$conf.int[[1]], 0.0634011709339876)
	expect_equal(t2$conf.int[[2]], 0.3600405634833566)
	expect_match(t2$method, "DeLong")
	expect_match(t2$method, "correlated")
	expect_identical(t2$alternative, "two.sided")
	expect_identical(attr(t2$conf.int, "conf.level"), 0.95)
	
	expect_equal(t3$statistic, c(Z=-1.39077002573558))
	expect_equal(t3$p.value, 0.164295175223054)
	expect_equal(t3$conf.int[[1]], -0.2876917446341914)
	expect_equal(t3$conf.int[[2]], 0.0488706064228094)
	expect_match(t3$method, "DeLong")
	expect_match(t3$method, "correlated")
	expect_identical(t3$alternative, "two.sided")
	expect_identical(attr(t3$conf.int, "conf.level"), 0.95)
})

test_that("two.sided roc.test produces identical p values when roc curves are reversed", {
	t1b <- roc.test(r.s100b, r.wfns)
	expect_equal(t1b$p.value, t1$p.value)
	expect_equal(t1b$statistic, -t1$statistic)
	
	t2b <- roc.test(r.ndka, r.wfns)
	expect_equal(t2b$p.value, t2$p.value)
	expect_equal(t2b$statistic, -t2$statistic)
	
	t3b <- roc.test(r.s100b, r.ndka)
	expect_equal(t3b$p.value, t3$p.value)
	expect_equal(t3b$statistic, -t3$statistic)
})

test_that("unpaired roc.test works", {
	# Warns about pairing
	expect_warning(roc.test_env$t1up <- roc.test(r.wfns, r.s100b, paired = FALSE))
	expect_warning(roc.test_env$t2up <- roc.test(r.wfns, r.ndka, paired = FALSE))
	expect_warning(roc.test_env$t3up <- roc.test(r.ndka, r.s100b, paired = FALSE))
})

test_that("unpaired roc.test statistic and p are as expected", {
	expect_equal(t1up$statistic, c(D=1.43490640926908))
	expect_equal(t1up$p.value, 0.152825378808796)
	expect_match(t1up$method, "DeLong")
	expect_identical(t1up$alternative, "two.sided")
	
	expect_equal(t2up$statistic, c(D=3.10125096778969))
	expect_equal(t2up$p.value, 0.00220950791756457)
	expect_match(t2up$method, "DeLong")
	expect_identical(t2up$alternative, "two.sided")
	
	expect_equal(t3up$statistic, c(D=-1.55995743389685))
	expect_equal(t3up$p.value, 0.120192832430845)
	expect_match(t3up$method, "DeLong")
	expect_identical(t3up$alternative, "two.sided")
})

test_that("unpaired two.sided roc.test produces identical p values when roc curves are reversed", {
	expect_warning(t1upb <- roc.test(r.s100b, r.wfns, paired = FALSE))
	expect_equal(t1upb$p.value, t1up$p.value)
	expect_equal(t1upb$statistic, -t1up$statistic)
	
	expect_warning(t2upb <- roc.test(r.ndka, r.wfns, paired = FALSE))
	expect_equal(t2upb$p.value, t2up$p.value)
	expect_equal(t2upb$statistic, -t2up$statistic)
	
	expect_warning(t3upb <- roc.test(r.s100b, r.ndka, paired = FALSE))
	expect_equal(t3upb$p.value, t3up$p.value)
	expect_equal(t3upb$statistic, -t3up$statistic)
})


test_that("one-sided roc.test work and produce expected results", {
	t1gt <- roc.test(r.wfns, r.s100b, alternative = "greater")
	t1lt <- roc.test(r.wfns, r.s100b, alternative = "less")
	
	expect_equal(t1gt$statistic, t1$statistic)
	expect_equal(t1lt$statistic, t1$statistic)
	
	expect_equal(t1gt$p.value, 0.0135878911145941)
	expect_equal(t1lt$p.value, 0.986412108885406)
	
	expect_match(t1gt$method, "DeLong")
	expect_match(t1gt$method, "correlated")
	expect_identical(t1gt$alternative, "greater")
	expect_match(t1lt$method, "DeLong")
	expect_match(t1lt$method, "correlated")
	expect_identical(t1lt$alternative, "less")
})

test_that("unpaired one-sided roc.test work and produce expected results", {
	expect_warning(t1upgt <- roc.test(r.wfns, r.s100b, alternative = "greater", paired = FALSE))
	expect_warning(t1uplt <- roc.test(r.wfns, r.s100b, alternative = "less", paired = FALSE))
	
	expect_equal(t1upgt$statistic, t1up$statistic)
	expect_equal(t1uplt$statistic, t1up$statistic)
	
	expect_equal(t1upgt$p.value, 0.076412689404398)
	expect_equal(t1uplt$p.value, 0.923587310595602)
	
	expect_match(t1upgt$method, "DeLong")
	expect_identical(t1upgt$alternative, "greater")
	expect_match(t1uplt$method, "DeLong")
	expect_identical(t1uplt$alternative, "less")
})

test_that("roc.formula works", {
	expect_silent(t1c <- roc.test(aSAH$outcome ~ aSAH$wfns + aSAH$s100b, quiet = TRUE)) # make sure silent is passed
	expect_equal(t1c$statistic, t1$statistic)
	expect_equal(t1c$p.value, t1$p.value)
	expect_match(t1$method, "DeLong")
	expect_match(t1$method, "correlated")
	expect_identical(t1$alternative, "two.sided")
				 
	expect_warning(t1upc <- roc.test(aSAH$outcome ~ aSAH$wfns + aSAH$s100b, quiet = TRUE, paired = FALSE))
	expect_equal(t1upc$statistic, t1up$statistic)
	expect_equal(t1upc$p.value, t1up$p.value)
	expect_match(t1upc$method, "DeLong")
	expect_identical(t1upc$alternative, "two.sided")
})


test_that("roc.formula supports subset and na.omit", {
	check.only.items <- c("p.value", "statistic")
	
	expect_identical(
		roc.test(outcome ~ wfns + ndka, data = aSAH, subset = (gender == "Female"), quiet = TRUE)[check.only.items],
		roc.test(aSAH$outcome[aSAH$gender == "Female"], aSAH$wfns[aSAH$gender == "Female"], aSAH$ndka[aSAH$gender == "Female"], quiet = TRUE)[check.only.items]
	)
	
	# Generate missing values
	aSAH.missing <- aSAH
	aSAH.missing$wfns[1:20] <- NA
	aSAH.missing$ndka[1:20] <- NA
	expect_warning(roctest1 <- roc.test(outcome ~ wfns + ndka, data = aSAH.missing, na.action = na.omit, quiet = TRUE), "na.omit")
	roctest2 <- roc.test(aSAH$outcome[21:113], aSAH$wfns[21:113], aSAH$ndka[21:113], quiet = TRUE)
	expect_identical(
		roctest1[check.only.items],
		roctest2[check.only.items]
	)
	#na.fail should fail
	expect_error(roc.test(outcome ~ wfns + ndka, data = aSAH.missing, na.action = na.fail, quiet = TRUE))
	#weights should fail too
	expect_error(roc.test(outcome ~ wfns + ndka, data = aSAH, weights = seq_len(nrow(aSAH))), regexp = "weights are not supported")
	
	# Both na.action and subset
	expect_warning(roctest1 <- roc.test(outcome ~ wfns + ndka, data = aSAH.missing, na.action = na.omit, subset = (gender == "Female"), quiet = TRUE), "na.omit")
	roctest2 <- roc.test(aSAH$outcome[21:113][aSAH[21:113,]$gender == "Female"], aSAH$wfns[21:113][aSAH[21:113,]$gender == "Female"], aSAH$ndka[21:113][aSAH[21:113,]$gender == "Female"], quiet = TRUE)
	expect_identical(
		roctest1[check.only.items],
		roctest2[check.only.items]
	)
})

test_that("paired tests don't work on unpaired curves", {
	# Make an unpaired ROC curve
	up.r.ndka <- roc(controls = aSAH$ndka[aSAH$outcome == "Good"], cases = aSAH$ndka[aSAH$outcome == "Poor"], quiet = TRUE)
	# unpaired by default
	t4 <- roc.test(r.wfns, up.r.ndka)
	expect_false(grepl("correlated", t4$method))
	# Shoud be an error:
	expect_error(roc.test(r.wfns, up.r.ndka, paired = TRUE))

})

test_that("one-sided roc.test work with direction='>' and produce expected results", {
	r.mwfns <- roc(aSAH$outcome, -as.numeric(aSAH$wfns))
	r.ms100b <- roc(aSAH$outcome, -aSAH$s100b)
	## We already tested those before:
	#t1gt <- roc.test(r.wfns, r.s100b, alternative = "greater")
	#t1lt <- roc.test(r.wfns, r.s100b, alternative = "less")
	# Test with inverted direction
	m1gt <- roc.test(r.mwfns, r.ms100b, alternative = "greater")
	m1lt <- roc.test(r.mwfns, r.ms100b, alternative = "less")
	
	expect_equal(m1gt$statistic, t1$statistic)
	expect_equal(m1lt$statistic, t1$statistic)
	
	expect_equal(m1gt$p.value, 0.0135878911145941)
	expect_equal(m1lt$p.value, 0.986412108885406)
})

test_that_no_progress("paired roc.test works with bootstrap", {
	skip_slow()
	ht <- roc.test(r.wfns, r.s100b, method = "bootstrap", boot.n = 12, paired = TRUE)
	expect_bootstrap_htest(ht)
	expect_equal(ht$alternative, "two.sided")
	expect_equal(ht$method, "Bootstrap test for two correlated ROC curves")
	expect_equal(unname(ht$parameter), c(12, 1))
})

test_that_no_progress("unpaired roc.test works with bootstrap", {
	skip_slow()
	expect_warning(ht <- roc.test(r.s100b, r.wfns, method = "bootstrap", boot.n = 12, paired = FALSE), "paired")
	expect_bootstrap_htest(ht)
	expect_equal(ht$alternative, "two.sided")
	expect_equal(ht$method, "Bootstrap test for two ROC curves")
	expect_equal(unname(ht$parameter), c(12, 1))
})

test_that_no_progress("paired, non stratified roc.test works with bootstrap", {
	skip_slow()
	ht <- roc.test(r.s100b, r.wfns, method = "bootstrap", boot.n = 12, paired = TRUE, boot.stratified = FALSE)
	expect_bootstrap_htest(ht)
	expect_equal(ht$alternative, "two.sided")
	expect_equal(ht$method, "Bootstrap test for two correlated ROC curves")
	expect_equal(unname(ht$parameter), c(12, 0))
})

test_that_no_progress("unpaired, non stratified roc.test works with bootstrap", {
	skip_slow()
	expect_warning(ht <- roc.test(r.s100b, r.wfns, method = "bootstrap", boot.n = 12, paired = FALSE, boot.stratified = FALSE), "paired")
	expect_bootstrap_htest(ht)
	expect_equal(ht$alternative, "two.sided")
	expect_equal(ht$method, "Bootstrap test for two ROC curves")
	expect_equal(unname(ht$parameter), c(12, 0))
})

test_that_no_progress("bootstrap roc.test works with mixed roc, auc and smooth.roc objects", {
	skip_slow()
	for (roc1 in list(r.s100b, auc(r.s100b), smooth(r.s100b), r.s100b.partial2, r.s100b.partial2$auc)) {
		for (roc2 in list(r.wfns, auc(r.wfns), smooth(r.wfns), r.wfns.partial1, r.wfns.partial1$auc)) {
			n <- round(runif(1, 3, 9)) # keep boot.n small
			stratified <- sample(c(TRUE, FALSE), 1)
			paired <- sample(c(TRUE, FALSE), 1)
			alternative = sample(c("two.sided", "less", "greater"), 1)
			suppressWarnings( # All sorts of warnings are expected
				ht <- roc.test(roc1, roc2, method = "bootstrap", 
						   boot.n = n, paired = paired, boot.stratified = stratified,
						   alternative = alternative))
			expect_bootstrap_htest(ht)
			expect_equal(ht$alternative, alternative)
			if (paired) {
				expect_equal(ht$method, "Bootstrap test for two correlated ROC curves")
			}
			else {
				expect_equal(ht$method, "Bootstrap test for two ROC curves")
			}
			expect_equal(unname(ht$parameter), c(n, as.integer(stratified)))
		}
	}
})

test_that_no_progress("se/sp roc.test works with mixed roc, auc and smooth.roc objects", {
	skip_slow()
	for (roc1 in list(r.s100b, auc(r.s100b), smooth(r.s100b), r.s100b.partial2, r.s100b.partial2$auc)) {
		for (roc2 in list(r.wfns, auc(r.wfns), smooth(r.wfns), r.wfns.partial1, r.wfns.partial1$auc)) {
			for (method in c("sensitivity", "specificity")) {
				n <- round(runif(1, 3, 9)) # keep boot.n small
				stratified <- sample(c(TRUE, FALSE), 1)
				paired <- sample(c(TRUE, FALSE), 1)
				alternative = sample(c("two.sided", "less", "greater"), 1)
				suppressWarnings( # All sorts of warnings are expected
					ht <- roc.test(roc1, roc2, method = method, 
							   sensitivity = 0.8,
							   specificity = 0.8,
							   boot.n = n, paired = paired, boot.stratified = stratified,
							   alternative = alternative))
				expect_bootstrap_htest(ht)
				expect_equal(ht$alternative, alternative)
				if (paired) {
					expect_equal(ht$method, sprintf("%s test for two correlated ROC curves", tools::toTitleCase(method)))
				}
				else {
					expect_equal(ht$method, sprintf("%s test for two ROC curves", tools::toTitleCase(method)))
				}
				expect_equal(unname(ht$parameter), c(n, as.integer(stratified)))
			}
		}
	}
})

Try the pROC package in your browser

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

pROC documentation built on Nov. 2, 2023, 6:05 p.m.