tests/testthat/test-multiclass.R

library(pROC)
data(aSAH)

context("multiclass-roc")

test_that("univariate multiclass roc/auc works", {
	expect_warning(uv.mr <- multiclass.roc(aSAH$gos6, aSAH$s100b), "2")
	expect_equal(class(uv.mr), "multiclass.roc")
	expect_equal(length(uv.mr$rocs), 6)
	expect_equal(as.numeric(auc(uv.mr)), 0.6539999352)
	expect_false(uv.mr$percent)
	expect_false(attributes(uv.mr$auc)$partial.auc)
	expect_false(attributes(uv.mr$auc)$percent)
})

test_that("univariate multiclass roc works with formula", {
	expect_warning(uv.mr <- multiclass.roc(gos6 ~ s100b, aSAH), "2")
	expect_equal(as.numeric(auc(uv.mr)), 0.6539999352)
	expect_warning(uv.mr <- multiclass.roc(aSAH$gos6 ~ aSAH$s100b), "2")
	expect_equal(as.numeric(auc(uv.mr)), 0.6539999352)
	
	expect_warning(uv.mr <- multiclass.roc(gos6 ~ s100b, aSAH, subset = (gender == "Female")), "2")
	expect_equal(length(uv.mr$response), sum(aSAH$gender == "Female"))
	expect_error(multiclass.roc(gos6 ~ s100b, aSAH, weights=age))
})

test_that("univariate multiclass roc/auc works with percent=TRUE", {
	expect_warning(uv.mr <- multiclass.roc(aSAH$gos6, aSAH$s100b, percent=TRUE), "2")
	uv.ma <- auc(uv.mr)
	expect_equal(as.numeric(uv.ma), 65.39999352)
	expect_equal(as.numeric(uv.mr$auc), 65.39999352)
	expect_true(uv.mr$percent)
	expect_true(attributes(uv.mr$auc)$percent)
	expect_true(attributes(uv.ma)$percent)
	expect_false(attributes(uv.mr$auc)$partial.auc)
	expect_false(attributes(uv.ma)$partial.auc)
})

test_that("univariate multiclass roc/auc works with partial.auc", {
	pauc.spec <- c(1, .9)
	expect_warning(uv.mr <- multiclass.roc(aSAH$gos6, aSAH$s100b, partial.auc=pauc.spec), "2")
	uv.ma <- auc(uv.mr, partial.auc=pauc.spec)
	expect_equal(as.numeric(uv.mr$auc), 0.0116176879)
	expect_equal(as.numeric(uv.ma), 0.0116176879)
	expect_equal(attributes(uv.mr$auc)$partial.auc, pauc.spec)
	expect_equal(attributes(uv.ma)$partial.auc, pauc.spec)
	# Calling AUC without partial.auc gives a full AUC, even if ROC was called with it
	expect_equal(as.numeric(auc(uv.mr)), 0.6539999352)

	# SE
	expect_warning(uv.mr <- multiclass.roc(aSAH$gos6, aSAH$s100b, partial.auc=pauc.spec, partial.auc.focus="se"), "2")
	uv.ma <- auc(uv.mr, partial.auc=pauc.spec, partial.auc.focus="se")
	expect_equal(as.numeric(uv.mr$auc), 0.02513286)
	expect_equal(as.numeric(uv.ma), 0.02513286)
	expect_equal(attributes(uv.mr$auc)$partial.auc.focus, "sensitivity")
	expect_equal(attributes(uv.ma)$partial.auc.focus, "sensitivity")
	
})

test_that("univariate multiclass roc/auc works with directions", {
	expect_warning(uv.mr <- multiclass.roc(aSAH$gos6, aSAH$s100b, direction="auto"), "2")
	expect_equal(sapply(uv.mr$rocs, "[[", "direction"), c("<", ">", ">", ">", ">", ">"))
	expect_equal(as.numeric(uv.mr$auc), 0.6539999352)
	
	expect_warning(uv.mr <- multiclass.roc(aSAH$gos6, aSAH$s100b, direction="<"), "2")
	expect_equal(sapply(uv.mr$rocs, "[[", "direction"), rep("<", 6))
	expect_equal(as.numeric(uv.mr$auc), 0.3487473175)
	
	expect_warning(uv.mr <- multiclass.roc(aSAH$gos6, aSAH$s100b, direction=">"), "2")
	expect_equal(sapply(uv.mr$rocs, "[[", "direction"), rep(">", 6))
	expect_equal(as.numeric(uv.mr$auc), 0.6512526825)
})

test_that("univariate multiclass handles missing levels", {
	expect_warning(multiclass.roc(aSAH$gos6, aSAH$s100b), "response level")
})

test_that("multivariate multiclass roc/auc works", {
	n <- c(100, 80, 150)
	responses <- factor(c(rep("X1", n[1]), rep("X2", n[2]), rep("X3", n[3])))
	# construct prediction matrix: one column per class
	set.seed(42)
	
	# Perfect separation
	preds <- lapply(n, function(x) runif(x, 0.8, 1))
	predictor <- as.matrix(data.frame("X1" = c(preds[[1]], runif(n[2] + n[3], 0, 0.3)),
									  "X2" = c(runif(n[1], 0.1, 0.4), preds[[2]], runif(n[3], 0, 0.2)),
									  "X3" = c(runif(n[1] + n[2], 0, 0.5), preds[[3]])))
	mv.mr <- multiclass.roc(responses, predictor)
	expect_equal(class(mv.mr), "mv.multiclass.roc")
	expect_equal(length(mv.mr$rocs), 3)
	expect_equal(unname(sapply(mv.mr$rocs, length)), rep(2, 3))
	expect_equal(as.numeric(auc(mv.mr)), 1)
	
	
	# Imperfect separation
	preds <- lapply(n, function(x) runif(x, 0.4, 0.6))
	predictor <- as.matrix(data.frame(
		"X1" = c(preds[[1]], runif(n[2] + n[3], 0, 0.7)),
		"X2" = c(runif(n[1], 0.1, 0.4), preds[[2]], runif(n[3], 0.2, 0.8)),
		"X3" = c(runif(n[1] + n[2], 0.3, 0.7), preds[[3]])
	))
	mv.mr <- multiclass.roc(responses, predictor)
	expect_equal(class(mv.mr), "mv.multiclass.roc")
	expect_equal(as.numeric(auc(mv.mr)), 0.6480791667)
	expect_false(mv.mr$percent)
	expect_false(attributes(mv.mr$auc)$partial.auc)
	expect_false(attributes(mv.mr$auc)$percent)
})



test_that("multivariate multiclass with formula works", {
	n <- c(100, 80, 150)
	responses <- factor(c(rep("X1", n[1]), rep("X2", n[2]), rep("X3", n[3])))
	# construct prediction matrix: one column per class
	set.seed(42)
	
	# Imperfect separation
	preds <- lapply(n, function(x) runif(x, 0.4, 0.6))
	predictor <- as.matrix(data.frame(
		"X1" = c(preds[[1]], runif(n[2] + n[3], 0, 0.7)),
		"X2" = c(runif(n[1], 0.1, 0.4), preds[[2]], runif(n[3], 0.2, 0.8)),
		"X3" = c(runif(n[1] + n[2], 0.3, 0.7), preds[[3]])
	))
	test_data <- cbind(as.data.frame(predictor), "response" = responses)
	mv.mr <- multiclass.roc(responses, predictor)
	mv.mr.f <- multiclass.roc(response ~ ., data=test_data)
	expect_equal(as.numeric(auc(mv.mr.f)), as.numeric(auc(mv.mr)))
	
	mv.mr.f <- multiclass.roc(response ~ X1 + X2 + X3, data=test_data)
	expect_equal(as.numeric(auc(mv.mr)), as.numeric(auc(mv.mr)))
	
	subset <- rbinom(sum(n), 1, .5) == 1
	mv.mr.f <- multiclass.roc(response ~ X1 + X2 + X3, data=test_data, subset = subset)
	expect_equal(length(mv.mr.f$response), sum(subset))
})


test_that("multivariate multiclass roc/auc works with percent=TRUE", {
	n <- c(100, 80, 150)
	responses <- factor(c(rep("X1", n[1]), rep("X2", n[2]), rep("X3", n[3])))
	# construct prediction matrix: one column per class
	set.seed(42)
	
	# Perfect separation
	preds <- lapply(n, function(x) runif(x, 0.8, 1))
	predictor <- as.matrix(data.frame("X1" = c(preds[[1]], runif(n[2] + n[3], 0, 0.3)),
									  "X2" = c(runif(n[1], 0.1, 0.4), preds[[2]], runif(n[3], 0, 0.2)),
									  "X3" = c(runif(n[1] + n[2], 0, 0.5), preds[[3]])))
	mv.mr <- multiclass.roc(responses, predictor, percent=TRUE)
	expect_equal(as.numeric(auc(mv.mr)), 100)
	
	# Imperfect separation
	preds <- lapply(n, function(x) runif(x, 0.4, 0.6))
	predictor <- as.matrix(data.frame(
		"X1" = c(preds[[1]], runif(n[2] + n[3], 0, 0.7)),
		"X2" = c(runif(n[1], 0.1, 0.4), preds[[2]], runif(n[3], 0.2, 0.8)),
		"X3" = c(runif(n[1] + n[2], 0.3, 0.7), preds[[3]])
	))
	mv.mr <- multiclass.roc(responses, predictor, percent=TRUE)
	expect_equal(as.numeric(auc(mv.mr)), 64.80791667)
	expect_true(mv.mr$percent)
	expect_false(attributes(mv.mr$auc)$partial.auc)
	expect_true(attributes(mv.mr$auc)$percent)
})


test_that("multivariate multiclass roc/auc works with partial.auc", {
	pauc.spec <- c(1, .9)
	n <- c(100, 80, 150)
	responses <- factor(c(rep("X1", n[1]), rep("X2", n[2]), rep("X3", n[3])))
	# construct prediction matrix: one column per class
	set.seed(42)
	
	# Perfect separation
	preds <- lapply(n, function(x) runif(x, 0.8, 1))
	predictor <- as.matrix(data.frame("X1" = c(preds[[1]], runif(n[2] + n[3], 0, 0.3)),
									  "X2" = c(runif(n[1], 0.1, 0.4), preds[[2]], runif(n[3], 0, 0.2)),
									  "X3" = c(runif(n[1] + n[2], 0, 0.5), preds[[3]])))
	mv.mr <- multiclass.roc(responses, predictor, partial.auc=c(1, .9))
	expect_equal(as.numeric(mv.mr$auc), .1)
	expect_equal(as.numeric(auc(mv.mr)), 1)
	expect_equal(as.numeric(auc(mv.mr, partial.auc=c(1, .9))), .1)
	
	# Imperfect separation
	preds <- lapply(n, function(x) runif(x, 0.4, 0.6))
	predictor <- as.matrix(data.frame(
		"X1" = c(preds[[1]], runif(n[2] + n[3], 0, 0.7)),
		"X2" = c(runif(n[1], 0.1, 0.4), preds[[2]], runif(n[3], 0.2, 0.8)),
		"X3" = c(runif(n[1] + n[2], 0.3, 0.7), preds[[3]])
	))
	mv.mr <- multiclass.roc(responses, predictor, partial.auc=c(1, .9))
	expect_equal(as.numeric(mv.mr$auc), 0.0529250000)
	expect_equal(as.numeric(auc(mv.mr)), 0.6480791667)
	expect_equal(as.numeric(auc(mv.mr, partial.auc=c(1, .9))), 0.0529250000)
	expect_false(mv.mr$percent)
	expect_equal(attributes(mv.mr$auc)$partial.auc, pauc.spec)
	expect_false(attributes(mv.mr$auc)$percent)
})


test_that("multivariate multiclass roc/auc works with direction", {
	n <- c(100, 80, 150)
	responses <- factor(c(rep("X1", n[1]), rep("X2", n[2]), rep("X3", n[3])))
	# construct prediction matrix: one column per class
	set.seed(42)
	
	# Perfect separation
	preds <- lapply(n, function(x) runif(x, 0.8, 1))
	predictor <- as.matrix(data.frame("X1" = c(preds[[1]], runif(n[2] + n[3], 0, 0.3)),
									  "X2" = c(runif(n[1], 0.1, 0.4), preds[[2]], runif(n[3], 0, 0.2)),
									  "X3" = c(runif(n[1] + n[2], 0, 0.5), preds[[3]])))
	
	expect_error(multiclass.roc(responses, predictor, direction = "auto"))
	
	mr.mv.1 <- multiclass.roc(responses, predictor, direction = "<")
	mr.mv.2 <- multiclass.roc(responses, predictor, direction = ">")
	expect_equal(as.numeric(mr.mv.1$auc), 0)
	expect_equal(as.numeric(mr.mv.2$auc), 1)
	
	for (i in 1:3) {
		for (j in 1:2) {
			expect_equal(mr.mv.1$rocs[[i]][[j]]$direction, "<")
			expect_equal(mr.mv.2$rocs[[i]][[j]]$direction, ">")
		}
	}
})

test_that("multivariate behavior with missing levels/columns", {
	n <- c(10, 10, 10)
	responses <- factor(c(rep("X1", n[1]), rep("X2", n[2]), rep("X3", n[3])))
	# construct prediction matrix: one column per class
	set.seed(42)
	
	# Perfect separation
	preds <- lapply(n, function(x) runif(x, 0.8, 1))
	predictor <- as.matrix(data.frame("X1" = c(preds[[1]], runif(n[2] + n[3], 0, 0.3)),
									  "X2" = c(runif(n[1], 0.1, 0.4), preds[[2]], runif(n[3], 0, 0.2)),
									  "X3" = c(runif(n[1] + n[2], 0, 0.5), preds[[3]])))
	# Wrong number of predictor rows
	expect_error(multiclass.roc(responses[1:20], predictor), "agree")
	
	# Column in predictor not in response warns:
	expect_warning(multiclass.roc(as.character(responses[1:20]), predictor[1:20,]), "X3")
	
	# Level with no obervation warns:
	expect_warning(multiclass.roc(responses[1:20], predictor[1:20,1:2]), "X3")
	
	# Removed both level and column should be silent
	expect_silent(multiclass.roc(as.character(responses[1:20]), predictor[1:20,1:2]))
	
	# Single column is an error
	expect_error(multiclass.roc(responses, predictor[,1, drop=F]))
	
	# Wrong column names
	pr2 <- predictor
	colnames(pr2) <- c("Y1", "Y2", "Y3")
	expect_error(multiclass.roc(responses, pr2))
	colnames(pr2) <- c("Y1", "X2", "X3")
	expect_warning(multiclass.roc(as.character(responses[11:30]), pr2[11:30,]), "Y1")
})

test_that("Invalid CI functions fail cleanly", {
	expect_warning(uv.mr <- multiclass.roc(aSAH$gos6, aSAH$s100b), "2")
	expect_error(ci.se(uv.mr), "not available for multiclass ROC curves")
	expect_error(ci.se(uv.mr$auc), "not available for multiclass ROC curves")
	expect_error(ci.sp(uv.mr), "not available for multiclass ROC curves")
	expect_error(ci.sp(uv.mr$auc), "not available for multiclass ROC curves")
	expect_error(ci.coords(uv.mr), "not available for multiclass ROC curves")
	expect_error(ci.coords(uv.mr$auc), "not available for multiclass ROC curves")
	expect_error(ci.thresholds(uv.mr), "not available for multiclass ROC curves")
	expect_error(ci.thresholds(uv.mr$auc), "not available for multiclass ROC curves")
})

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.