From 054e5edb470ae723f2a9cf0f70683042bfa1849e Mon Sep 17 00:00:00 2001 From: Jason Cory Brunson Date: Thu, 1 Oct 2026 10:18:29 -0400 Subject: [PATCH 1/2] provide conf_int predictions for clm engine + apply air --- NEWS.md | 7 +- R/decision_tree-data.R | 1 - R/decision_tree-rpartScore.R | 7 +- R/gen_additive_mod-data.R | 35 ++- R/ordinal_reg-VGAM.R | 96 ++++--- R/ordinal_reg-clm.R | 35 ++- R/ordinal_reg-data.R | 247 +++++++++--------- R/ordinal_reg-glmnetcr.R | 59 +++-- R/ordinal_reg-ordinalNet.R | 104 +++++--- R/param-engine-ordinalNet.R | 4 +- R/parsnip-utils.R | 93 ++++--- R/rand_forest-data.R | 51 ++-- R/rand_forest-ordinalForest.R | 7 +- R/rand_forest-orf.R | 7 +- README.md | 26 +- tests/spelling.R | 10 +- tests/testthat/helper-data.R | 2 +- .../testthat/test-decision_tree-rpartScore.R | 35 ++- tests/testthat/test-dials.R | 18 +- tests/testthat/test-gen_additive_mod-VGAM.R | 11 +- tests/testthat/test-ordinal_reg-VGAM.R | 57 ++-- tests/testthat/test-ordinal_reg-clm-wrapper.R | 3 +- tests/testthat/test-ordinal_reg-clm.R | 102 +++++++- tests/testthat/test-ordinal_reg-glmnetcr.R | 109 +++++--- tests/testthat/test-ordinal_reg-ordinalNet.R | 101 ++++--- tests/testthat/test-ordinal_reg-polr.R | 1 - tests/testthat/test-ordinal_reg-rms.R | 18 +- .../testthat/test-rand_forest-ordinalForest.R | 23 +- tests/testthat/test-rand_forest-orf.R | 33 ++- vignettes/template/build-examples.R | 27 +- 30 files changed, 842 insertions(+), 487 deletions(-) diff --git a/NEWS.md b/NEWS.md index e302aa6..ab51986 100644 --- a/NEWS.md +++ b/NEWS.md @@ -42,13 +42,16 @@ This version introduces source code and unit tests for new engines and dials: Coordinated with [parsnip PR #1384](https://github.com/tidymodels/parsnip/pull/1384). -### linear prediction type +### new prediction types -Linear predictions are enabled for the `clm`, `lrm`, `orm`, `vglm`, and `ordinalNet` ordinal regression engines and for the `vgam` generalized additive model engine. +Linear predictions via `predict_linear_pred()` are enabled for the `clm`, `lrm`, `orm`, `vglm`, and `ordinalNet` ordinal regression engines and for the `vgam` generalized additive model engine. They consistently return a single column of linear predictors (without threshold contributions). Coordinated with [parsnip PR #1391](https://github.com/tidymodels/parsnip/pull/1391). +Confidence interval predictions via `predict_confint()` are enabled for the `clm` engine. +They return pairs of columns containing pointwise intervals for each class probability. + ### threshold structure and parallel regression model arguments The `threshold_structure` model argument for `ordinal_reg()` controls what constraints, if any, are imposed on the ordered thresholds. diff --git a/R/decision_tree-data.R b/R/decision_tree-data.R index a0418c2..e00c648 100644 --- a/R/decision_tree-data.R +++ b/R/decision_tree-data.R @@ -12,7 +12,6 @@ # `rpartScore::rpartScore` components make_decision_tree_rpartScore <- function() { - parsnip::set_model_engine("decision_tree", "classification", "rpartScore") parsnip::set_dependency( "decision_tree", diff --git a/R/decision_tree-rpartScore.R b/R/decision_tree-rpartScore.R index eed57b8..4f245d2 100644 --- a/R/decision_tree-rpartScore.R +++ b/R/decision_tree-rpartScore.R @@ -32,8 +32,11 @@ rpartScore_wrapper <- function(formula, data, ...) { data[[lhs]] <- as.integer(data[[lhs]]) # execute call on modified inputs cl <- rlang::call2( - .fn = "rpartScore", .ns = "rpartScore", - formula = rlang::expr(formula), data = rlang::expr(data), ... + .fn = "rpartScore", + .ns = "rpartScore", + formula = rlang::expr(formula), + data = rlang::expr(data), + ... ) rlang::eval_tidy(cl) } diff --git a/R/gen_additive_mod-data.R b/R/gen_additive_mod-data.R index 7917897..875c0f0 100644 --- a/R/gen_additive_mod-data.R +++ b/R/gen_additive_mod-data.R @@ -12,7 +12,6 @@ # `VGAM::vgam` components make_gen_additive_mod_vgam <- function() { - parsnip::set_model_engine("gen_additive_mod", "classification", "vgam") parsnip::set_dependency( "gen_additive_mod", @@ -94,12 +93,11 @@ make_gen_additive_mod_vgam <- function() { pre = NULL, post = predict_VGAM_class_post, func = c(fun = "predict", pkg = "VGAM"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "response" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "response" + ) ) ) @@ -112,12 +110,11 @@ make_gen_additive_mod_vgam <- function() { pre = NULL, post = predict_VGAM_prob_post, func = c(fun = "predict", pkg = "VGAM"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "response" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "response" + ) ) ) @@ -133,15 +130,13 @@ make_gen_additive_mod_vgam <- function() { tibble::tibble(.pred_linear_pred = unname(beta_x)) }, func = c(fun = "predict", pkg = "VGAM"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "link" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "link" + ) ) ) - } # nocov end diff --git a/R/ordinal_reg-VGAM.R b/R/ordinal_reg-VGAM.R index 8aaf233..0b43264 100644 --- a/R/ordinal_reg-VGAM.R +++ b/R/ordinal_reg-VGAM.R @@ -122,13 +122,14 @@ #' }#VGAM>1.1-9 #' @export VGAM_vglm_wrapper <- function( - formula, data, - family = "cumulative", - link = "logitlink", - parallel = FALSE, - Thresh = NULL, - ..., - call = rlang::caller_env() + formula, + data, + family = "cumulative", + link = "logitlink", + parallel = FALSE, + Thresh = NULL, + ..., + call = rlang::caller_env() ) { rlang::check_installed("VGAM") VGAM_1_1_9 <- utils::packageVersion("VGAM") == package_version("1.1.9") @@ -149,18 +150,26 @@ VGAM_vglm_wrapper <- function( # execute nested call on modified inputs family_call <- if (VGAM_1_1_9) { rlang::call2( - .fn = family, .ns = "VGAM", - link = link, parallel = parallel, threshold = Thresh + .fn = family, + .ns = "VGAM", + link = link, + parallel = parallel, + threshold = Thresh ) } else { rlang::call2( - .fn = family, .ns = "VGAM", - link = link, parallel = parallel, Thresh = Thresh + .fn = family, + .ns = "VGAM", + link = link, + parallel = parallel, + Thresh = Thresh ) } cl <- rlang::call2( - .fn = "vglm", .ns = "VGAM", - formula = rlang::expr(formula), data = rlang::expr(data), + .fn = "vglm", + .ns = "VGAM", + formula = rlang::expr(formula), + data = rlang::expr(data), family = family_call, ... ) @@ -170,13 +179,14 @@ VGAM_vglm_wrapper <- function( #' @rdname VGAM_vglm_wrapper #' @export VGAM_vgam_wrapper <- function( - formula, data, - family = "cumulative", - link = "logitlink", - parallel = FALSE, - Thresh = NULL, - ..., - call = rlang::caller_env() + formula, + data, + family = "cumulative", + link = "logitlink", + parallel = FALSE, + Thresh = NULL, + ..., + call = rlang::caller_env() ) { rlang::check_installed("VGAM") VGAM_1_1_9 <- utils::packageVersion("VGAM") == package_version("1.1.9") @@ -194,18 +204,26 @@ VGAM_vgam_wrapper <- function( # execute nested call on modified inputs family_call <- if (VGAM_1_1_9) { rlang::call2( - .fn = family, .ns = "VGAM", - link = link, parallel = parallel, threshold = Thresh + .fn = family, + .ns = "VGAM", + link = link, + parallel = parallel, + threshold = Thresh ) } else { rlang::call2( - .fn = family, .ns = "VGAM", - link = link, parallel = parallel, Thresh = Thresh + .fn = family, + .ns = "VGAM", + link = link, + parallel = parallel, + Thresh = Thresh ) } cl <- rlang::call2( - .fn = "vgam", .ns = "VGAM", - formula = rlang::expr(formula), data = rlang::expr(data), + .fn = "vgam", + .ns = "VGAM", + formula = rlang::expr(formula), + data = rlang::expr(data), family = family_call, ... ) @@ -256,17 +274,26 @@ match_ordinal_family <- function(family, call = rlang::caller_env()) { } match_ordinal_link_VGAM <- function(link, call = rlang::caller_env()) { - if (! is.character(link)) { + if (!is.character(link)) { return(link) } check_string(link, arg = "ordinal_link", call = call) if ( # keep native values - ! link %in% c( - "logitlink", "probitlink", "logloglink", "clogloglink", "cauchitlink", - "foldsqrtlink", "logclink", "gordlink", "pordlink", "nbordlink" - ) + !link %in% + c( + "logitlink", + "probitlink", + "logloglink", + "clogloglink", + "cauchitlink", + "foldsqrtlink", + "logclink", + "gordlink", + "pordlink", + "nbordlink" + ) ) { # modify standardized values link <- rlang::arg_match0( @@ -294,10 +321,11 @@ match_ordinal_link_VGAM <- function(link, call = rlang::caller_env()) { } match_threshold_structure_VGAM <- function( - Thresh, v_1_1_9 = FALSE, + Thresh, + v_1_1_9 = FALSE, call = rlang::caller_env() ) { - if (! is.character(Thresh)) { + if (!is.character(Thresh)) { return(Thresh) } check_string(Thresh, arg = "threshold_structure", call = call) @@ -310,7 +338,7 @@ match_threshold_structure_VGAM <- function( if ( # keep native values - ! Thresh %in% native_vals + !Thresh %in% native_vals ) { # modify standardized values Thresh <- rlang::arg_match0( diff --git a/R/ordinal_reg-clm.R b/R/ordinal_reg-clm.R index 3b758df..2e7844f 100644 --- a/R/ordinal_reg-clm.R +++ b/R/ordinal_reg-clm.R @@ -60,13 +60,13 @@ clm_wrapper <- function( } args <- c(formulas, list(data = data)) - if (! is.null(weights)) { + if (!is.null(weights)) { args$weights <- weights } - if (! is.null(link)) { + if (!is.null(link)) { args$link <- match_ordinal_link_clm(link, call = call) } - if (! is.null(threshold)) { + if (!is.null(threshold)) { threshold <- switch( threshold, flexible = "flexible", @@ -85,10 +85,10 @@ clm_wrapper <- function( # printing the fit shows the formulas and engine arguments that were used. res$call <- rlang::call2( "clm", - !!! formulas, + !!!formulas, data = rlang::sym("data"), - !!! arg_exprs, - !!! dot_exprs, + !!!arg_exprs, + !!!dot_exprs, .ns = "ordinal" ) @@ -96,7 +96,7 @@ clm_wrapper <- function( } match_ordinal_link_clm <- function(link, call = rlang::caller_env()) { - if (! is.character(link)) { + if (!is.character(link)) { return(link) } check_string(link, arg = "ordinal_link", call = call) @@ -150,3 +150,24 @@ predict_clm_pre <- function(new_data, object) { } new_data } + +clm_conf_int_post <- function(x, object) { + lvl <- object$lvl + bounds <- vector("list", 2L * length(lvl)) + for (i in seq_along(lvl)) { + bounds[[2L * i - 1L]] <- unname(x$lwr[, i]) + bounds[[2L * i]] <- unname(x$upr[, i]) + } + names(bounds) <- c( + rbind(paste0(".pred_lower_", lvl), paste0(".pred_upper_", lvl)) + ) + res <- tibble::as_tibble(bounds) + + if (isTRUE(object$spec$method$pred$conf_int$extras$std_error)) { + errors <- lapply(seq_along(lvl), function(i) unname(x$se.fit[, i])) + names(errors) <- paste0(".std_error_", lvl) + res <- tibble::as_tibble(c(bounds, errors)) + } + + res +} diff --git a/R/ordinal_reg-data.R b/R/ordinal_reg-data.R index 9c6baad..c8df8b5 100644 --- a/R/ordinal_reg-data.R +++ b/R/ordinal_reg-data.R @@ -12,7 +12,6 @@ # `MASS::polr` components make_ordinal_reg_polr <- function() { - parsnip::set_model_engine("ordinal_reg", "classification", "polr") parsnip::set_dependency( "ordinal_reg", @@ -69,12 +68,11 @@ make_ordinal_reg_polr <- function() { pre = NULL, post = NULL, func = c(fun = "predict"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "class" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "class" + ) ) ) @@ -89,22 +87,19 @@ make_ordinal_reg_polr <- function() { tibble::as_tibble(x) }, func = c(fun = "predict"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "probs" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "probs" + ) ) ) - } # ------------------------------------------------------------------------------ # `VGAM::vglm` components make_ordinal_reg_vglm <- function() { - parsnip::set_model_engine("ordinal_reg", "classification", "vglm") parsnip::set_dependency( "ordinal_reg", @@ -185,12 +180,11 @@ make_ordinal_reg_vglm <- function() { pre = NULL, post = predict_VGAM_class_post, func = c(fun = "predictvglm", pkg = "VGAM"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "response" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "response" + ) ) ) @@ -203,12 +197,11 @@ make_ordinal_reg_vglm <- function() { pre = NULL, post = predict_VGAM_prob_post, func = c(fun = "predictvglm", pkg = "VGAM"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "response" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "response" + ) ) ) @@ -225,22 +218,19 @@ make_ordinal_reg_vglm <- function() { tibble::tibble(.pred_linear_pred = unname(beta_x)) }, func = c(fun = "predict", pkg = "VGAM"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "link" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "link" + ) ) ) - } # ------------------------------------------------------------------------------ # `ordinalNet::ordinalNet` components make_ordinal_reg_ordinalNet <- function() { - parsnip::set_model_engine("ordinal_reg", "classification", "ordinalNet") parsnip::set_dependency( "ordinal_reg", @@ -358,13 +348,12 @@ make_ordinal_reg_ordinalNet <- function() { ordered(object$lvl[x], object$lvl) }, func = c(fun = "predict_ordinalNet_wrapper"), - args = - list( - object = quote(object$fit), - newx = quote(new_data), - type = "class", - lambda = quote(object$spec$args$penalty) - ) + args = list( + object = quote(object$fit), + newx = quote(new_data), + type = "class", + lambda = quote(object$spec$args$penalty) + ) ) ) @@ -381,13 +370,12 @@ make_ordinal_reg_ordinalNet <- function() { x }, func = c(fun = "predict_ordinalNet_wrapper"), - args = - list( - object = quote(object$fit), - newx = quote(new_data), - type = "prob", - lambda = quote(object$spec$args$penalty) - ) + args = list( + object = quote(object$fit), + newx = quote(new_data), + type = "prob", + lambda = quote(object$spec$args$penalty) + ) ) ) @@ -405,22 +393,19 @@ make_ordinal_reg_ordinalNet <- function() { tibble::tibble(.pred_linear_pred = unname(beta_x)) }, func = c(fun = "predict"), - args = - list( - object = quote(object$fit), - newx = quote(new_data), - type = "link" - ) + args = list( + object = quote(object$fit), + newx = quote(new_data), + type = "link" + ) ) ) - } # ------------------------------------------------------------------------------ # `rms::lrm` and `rms::orm` components make_ordinal_reg_lrm <- function() { - # ---------------------------------------------------------------------------- # `rms::lrm` components @@ -482,12 +467,11 @@ make_ordinal_reg_lrm <- function() { ordered(object$lvl[x], object$lvl) }, func = c(pkg = "ordered", fun = "predict_lrm_wrapper"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "fitted.ind" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "fitted.ind" + ) ) ) @@ -504,12 +488,11 @@ make_ordinal_reg_lrm <- function() { x }, func = c(pkg = "ordered", fun = "predict_lrm_wrapper"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "fitted.ind" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "fitted.ind" + ) ) ) @@ -526,12 +509,11 @@ make_ordinal_reg_lrm <- function() { tibble::tibble(.pred_linear_pred = unname(beta_x)) }, func = c(fun = "predict"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "lp" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "lp" + ) ) ) @@ -604,12 +586,11 @@ make_ordinal_reg_lrm <- function() { ordered(object$lvl[x], object$lvl) }, func = c(pkg = "ordered", fun = "predict_lrm_wrapper"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "fitted.ind" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "fitted.ind" + ) ) ) @@ -626,12 +607,11 @@ make_ordinal_reg_lrm <- function() { x }, func = c(pkg = "ordered", fun = "predict_lrm_wrapper"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "fitted.ind" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "fitted.ind" + ) ) ) @@ -648,22 +628,19 @@ make_ordinal_reg_lrm <- function() { tibble::tibble(.pred_linear_pred = unname(beta_x)) }, func = c(pkg = "ordered", fun = "predict_lrm_wrapper"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "lp" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "lp" + ) ) ) - } # ------------------------------------------------------------------------------ # `glmnetcr::glmnetcr` components make_ordinal_reg_glmnetcr <- function() { - parsnip::set_model_engine("ordinal_reg", "classification", "glmnetcr") parsnip::set_dependency( "ordinal_reg", @@ -729,13 +706,12 @@ make_ordinal_reg_glmnetcr <- function() { ordered(x, levels = object$lvl) }, func = c(fun = "predict_glmnetcr_wrapper"), - args = - list( - object = quote(object$fit), - newx = quote(new_data), - type = "class", - lambda = quote(object$spec$args$penalty) - ) + args = list( + object = quote(object$fit), + newx = quote(new_data), + type = "class", + lambda = quote(object$spec$args$penalty) + ) ) ) @@ -752,23 +728,20 @@ make_ordinal_reg_glmnetcr <- function() { x }, func = c(fun = "predict_glmnetcr_wrapper"), - args = - list( - object = quote(object$fit), - newx = quote(new_data), - type = "prob", - lambda = quote(object$spec$args$penalty) - ) + args = list( + object = quote(object$fit), + newx = quote(new_data), + type = "prob", + lambda = quote(object$spec$args$penalty) + ) ) ) - } # ------------------------------------------------------------------------------ # `ordinal::clm` components make_ordinal_reg_clm <- function() { - parsnip::set_model_engine("ordinal_reg", "classification", "clm") parsnip::set_dependency( "ordinal_reg", @@ -843,12 +816,11 @@ make_ordinal_reg_clm <- function() { tibble::tibble(.pred_class = x$fit) }, func = c(fun = "predict"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "class" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "class" + ) ) ) @@ -865,12 +837,11 @@ make_ordinal_reg_clm <- function() { x }, func = c(fun = "predict"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "prob" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "prob" + ) ) ) @@ -887,15 +858,33 @@ make_ordinal_reg_clm <- function() { tibble::tibble(.pred_linear_pred = unname(beta_x)) }, func = c(fun = "predict"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "linear.predictor" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "linear.predictor" + ) ) ) + parsnip::set_pred( + model = "ordinal_reg", + eng = "clm", + mode = "classification", + type = "conf_int", + value = list( + pre = predict_clm_pre, + post = clm_conf_int_post, + func = c(fun = "predict"), + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "prob", + se.fit = TRUE, + interval = TRUE, + level = quote(level) + ) + ) + ) } # nocov end diff --git a/R/ordinal_reg-glmnetcr.R b/R/ordinal_reg-glmnetcr.R index de188a7..c23d284 100644 --- a/R/ordinal_reg-glmnetcr.R +++ b/R/ordinal_reg-glmnetcr.R @@ -83,7 +83,11 @@ #' ) #' @export predict_glmnetcr_wrapper <- function( - object, newx, type, lambda, criteria = c("bic", "aic") + object, + newx, + type, + lambda, + criteria = c("bic", "aic") ) { criteria <- match.arg(criteria) @@ -104,14 +108,17 @@ predict_glmnetcr_wrapper <- function( if (type == "class") { res[, 1L] } else { - res[, , 1L] + res[,, 1L] } } #' @rdname predict_glmnetcr_wrapper #' @export multi_predict_glmnetcr_wrapper <- function( - object, newx, type, lambda + object, + newx, + type, + lambda ) { type <- match.arg(type, c("prob", "class")) @@ -137,10 +144,10 @@ multi_predict_glmnetcr_wrapper <- function( # bracketing steps and rescaled to add to 1 probs_at <- function(lam, idx) { if (length(idx) == 1L) { - pred$probs[, , idx] + pred$probs[,, idx] } else { w <- (lam - lams[idx[1L]]) / (lams[idx[2L]] - lams[idx[1L]]) - probs <- (1 - w) * pred$probs[, , idx[1L]] + w * pred$probs[, , idx[2L]] + probs <- (1 - w) * pred$probs[,, idx[1L]] + w * pred$probs[,, idx[2L]] sweep(probs, 1L, rowSums(probs), "/") } } @@ -151,7 +158,7 @@ multi_predict_glmnetcr_wrapper <- function( if (type == "prob") { res <- array(NA_real_, c(n, ncol(pred$probs), n_pen)) for (k in seq_len(n_pen)) { - res[, , k] <- probs_at(lambda[k], s_idx[[k]]) + res[,, k] <- probs_at(lambda[k], s_idx[[k]]) } dimnames(res) <- list(NULL, colnames(pred$probs), NULL) } else { @@ -200,15 +207,15 @@ multi_predict_glmnetcr_wrapper <- function( #' model specification. #' @export predict._glmnetcr <- function( - object, - new_data, - type = NULL, - opts = list(), - penalty = NULL, - multi = FALSE, - ... + object, + new_data, + type = NULL, + opts = list(), + penalty = NULL, + multi = FALSE, + ... ) { - if (is.null(penalty) && ! is.null(object$spec$args$penalty)) { + if (is.null(penalty) && !is.null(object$spec$args$penalty)) { penalty <- object$spec$args$penalty } @@ -242,12 +249,12 @@ predict_classprob._glmnetcr <- function(object, new_data, ...) { #' used. #' @export multi_predict._glmnetcr <- function( - object, - new_data, - type = NULL, - opts = list(), - penalty = NULL, - ... + object, + new_data, + type = NULL, + opts = list(), + penalty = NULL, + ... ) { type <- check_pred_type(object, type) check_spec_pred_type(object, type) @@ -256,7 +263,7 @@ multi_predict._glmnetcr <- function( } if (is.null(penalty)) { - if (! is.null(object$spec$args$penalty)) { + if (!is.null(object$spec$args$penalty)) { penalty <- object$spec$args$penalty } else { penalty <- object$fit$lambda @@ -296,7 +303,10 @@ multi_predict._glmnetcr <- function( multi_predict_classprob_glmnetcr <- function(object, newx, penalty) { probs <- multi_predict_glmnetcr_wrapper( - object, newx, type = "prob", lambda = penalty + object, + newx, + type = "prob", + lambda = penalty ) lbls <- paste0(".pred_", dimnames(probs)[[2]]) nested <- lapply(seq_len(nrow(newx)), function(i) { @@ -308,7 +318,10 @@ multi_predict_classprob_glmnetcr <- function(object, newx, penalty) { multi_predict_class_glmnetcr <- function(object, newx, penalty, lvl) { classes <- multi_predict_glmnetcr_wrapper( - object, newx, type = "class", lambda = penalty + object, + newx, + type = "class", + lambda = penalty ) nested <- lapply(seq_len(nrow(newx)), function(i) { tibble::tibble( diff --git a/R/ordinal_reg-ordinalNet.R b/R/ordinal_reg-ordinalNet.R index f0b0ae3..f58069c 100644 --- a/R/ordinal_reg-ordinalNet.R +++ b/R/ordinal_reg-ordinalNet.R @@ -75,13 +75,17 @@ #' ) #' @export ordinalNet_wrapper <- function( - x, y, weights = NULL, - family = "cumulative", - link = "logit", - parallel_reg = NULL, - parallelTerms = TRUE, nonparallelTerms = FALSE, parallelPenaltyFactor = 1, - ..., - call = rlang::caller_env() + x, + y, + weights = NULL, + family = "cumulative", + link = "logit", + parallel_reg = NULL, + parallelTerms = TRUE, + nonparallelTerms = FALSE, + parallelPenaltyFactor = 1, + ..., + call = rlang::caller_env() ) { rlang::check_installed("ordinalNet") @@ -94,14 +98,14 @@ ordinalNet_wrapper <- function( } # throw error if penalty factor would go unused - if (! parallelTerms && parallelPenaltyFactor != 1) { + if (!parallelTerms && parallelPenaltyFactor != 1) { cli::cli_abort( "{.arg parallelPenaltyFactor} cannot be used without parallel terms." ) } # restructure based on weights (requires `y` to be a factor) - if (! is.null(weights)) { + if (!is.null(weights)) { y_levs <- levels(y) y <- lapply(y_levs, function(u) (y == u) * weights) y <- do.call(cbind, y) @@ -110,9 +114,12 @@ ordinalNet_wrapper <- function( # execute call on modified inputs cl <- rlang::call2( - .fn = "ordinalNet", .ns = "ordinalNet", - x = rlang::expr(x), y = rlang::expr(y), - family = rlang::expr(family), link = rlang::expr(link), + .fn = "ordinalNet", + .ns = "ordinalNet", + x = rlang::expr(x), + y = rlang::expr(y), + family = rlang::expr(family), + link = rlang::expr(link), parallelTerms = parallelTerms, nonparallelTerms = nonparallelTerms, parallelPenaltyFactor = parallelPenaltyFactor, @@ -122,7 +129,7 @@ ordinalNet_wrapper <- function( } match_ordinal_link_ordinalNet <- function(link, call = rlang::caller_env()) { - if (! is.character(link)) { + if (!is.character(link)) { return(link) } check_string(link, arg = "ordinal_link", call = call) @@ -154,7 +161,11 @@ match_ordinal_link_ordinalNet <- function(link, call = rlang::caller_env()) { #' @rdname ordinalNet_wrapper #' @export predict_ordinalNet_wrapper <- function( - object, newx, type, lambda, criteria = c("aic", "bic") + object, + newx, + type, + lambda, + criteria = c("aic", "bic") ) { # REVIEW: This is necessary in order to prevent requiring the user to pass # a `penalty` value and nevertheless ignoring it. @@ -210,7 +221,6 @@ predict_ordinalNet_wrapper <- function( # return 1 or 2 (adjacent) penalty path indices: # if 1, it is used; if 2, predictions are interpolated adjacent_penalties <- function(object, penalty) { - # NB: `$lambdaVals` must be unique and decreasing. len <- length(object$lambdaVals) if (penalty < object$lambdaVals[len]) { @@ -271,15 +281,15 @@ approx_prediction_row <- function(values, adjacent, penalty) { #' @rdname ordinalNet_wrapper #' @export predict._ordinalNet <- function( - object, - new_data, - type = NULL, - opts = list(), - penalty = NULL, - multi = FALSE, - ... + object, + new_data, + type = NULL, + opts = list(), + penalty = NULL, + multi = FALSE, + ... ) { - if (is.null(penalty) && ! is.null(object$spec$args$penalty)) { + if (is.null(penalty) && !is.null(object$spec$args$penalty)) { penalty <- object$spec$args$penalty } @@ -294,12 +304,12 @@ predict._ordinalNet <- function( #' @rdname ordinalNet_wrapper #' @export multi_predict._ordinalNet <- function( - object, - new_data, - type = NULL, - opts = list(), - penalty = NULL, - ... + object, + new_data, + type = NULL, + opts = list(), + penalty = NULL, + ... ) { type <- check_pred_type(object, type) check_spec_pred_type(object, type) @@ -311,7 +321,7 @@ multi_predict._ordinalNet <- function( if (is.null(penalty)) { # See discussion in https://github.com/tidymodels/parsnip/issues/195 - if (! is.null(object$spec$args$penalty)) { + if (!is.null(object$spec$args$penalty)) { penalty <- object$spec$args$penalty } else { penalty <- object$fit$lambdaVals @@ -332,14 +342,22 @@ multi_predict._ordinalNet <- function( pred <- switch( type, "prob" = multi_predict_classprob_ordinal_net( - object, new_data = new_data, penalty = penalty + object, + new_data = new_data, + penalty = penalty ), "class" = multi_predict_class_ordinal_net( - object, new_data = new_data, penalty = penalty + object, + new_data = new_data, + penalty = penalty ), "raw" = predict( - object, new_data = new_data, type = "raw", - opts = opts, penalty = penalty, multi = TRUE + object, + new_data = new_data, + type = "raw", + opts = opts, + penalty = penalty, + multi = TRUE ) ) @@ -348,7 +366,7 @@ multi_predict._ordinalNet <- function( #' @rdname ordinalNet_wrapper #' @export -predict_raw._ordinalNet <- function(object, new_data, opts = list(), ...) { +predict_raw._ordinalNet <- function(object, new_data, opts = list(), ...) { object$spec <- eval_args(object$spec) opts$whichLambda <- object$spec$args$penalty @@ -372,10 +390,10 @@ predict_class._ordinalNet <- function(object, new_data, ...) { # adapted from `parsnip:::.check_glmnet_penalty_predict()` check_penalty_predict <- function( - penalty = NULL, - object, - multi = FALSE, - call = rlang::caller_env() + penalty = NULL, + object, + multi = FALSE, + call = rlang::caller_env() ) { engine <- object$spec$engine penalty_path_arg <- switch( @@ -389,7 +407,7 @@ check_penalty_predict <- function( } # when using `predict()`, allow for a single lambda - if (! multi) { + if (!multi) { if (length(penalty) != 1) { cli::cli_abort( c( @@ -402,8 +420,10 @@ check_penalty_predict <- function( } } - if (length(object$fit[[penalty_path_arg]]) == 1L && - penalty != object$fit[[penalty_path_arg]]) { + if ( + length(object$fit[[penalty_path_arg]]) == 1L && + penalty != object$fit[[penalty_path_arg]] + ) { cli::cli_abort( c( "The {.val {engine}} model was fit with a single penalty value of diff --git a/R/param-engine-ordinalNet.R b/R/param-engine-ordinalNet.R index 9006714..6356965 100644 --- a/R/param-engine-ordinalNet.R +++ b/R/param-engine-ordinalNet.R @@ -32,8 +32,8 @@ #' @export #' @rdname ordinalNet_parameters parallel_penalty_factor <- function( - range = c(-Inf, Inf), - trans = scales::transform_log10() + range = c(-Inf, Inf), + trans = scales::transform_log10() ) { dials::new_quant_param( type = "double", diff --git a/R/parsnip-utils.R b/R/parsnip-utils.R index f166d78..3b6f256 100644 --- a/R/parsnip-utils.R +++ b/R/parsnip-utils.R @@ -1,11 +1,20 @@ # mimicking file of same name in {censored} pred_types <- c( - "raw", "numeric", "class", "prob", "conf_int", "pred_int", - "quantile", "time", "survival", "linear_pred", "hazard" + "raw", + "numeric", + "class", + "prob", + "conf_int", + "pred_int", + "quantile", + "time", + "survival", + "linear_pred", + "hazard" ) -check_pred_type <- function (object, type, ..., call = rlang::caller_env()) { +check_pred_type <- function(object, type, ..., call = rlang::caller_env()) { if (is.null(type)) { type <- switch( object$spec$mode, @@ -13,55 +22,77 @@ check_pred_type <- function (object, type, ..., call = rlang::caller_env()) { classification = "class", `censored regression` = "time", `quantile regression` = "quantile", - cli::cli_abort("{.arg type} should be one of {.or {.val {all_modes}}}.", - call = call) + cli::cli_abort( + "{.arg type} should be one of {.or {.val {all_modes}}}.", + call = call + ) + ) + } + if (!(type %in% pred_types)) { + cli::cli_abort( + "{.arg type} should be one of {.or {.arg {pred_types}}}.", + call = call ) } - if (!(type %in% pred_types)) - cli::cli_abort("{.arg type} should be one of {.or {.arg {pred_types}}}.", - call = call) switch( type, numeric = if (object$spec$mode != "regression") { - cli::cli_abort("For numeric predictions, + cli::cli_abort( + "For numeric predictions, the object should be a regression model.", - call = call) + call = call + ) }, class = if (object$spec$mode != "classification") { - cli::cli_abort("For class predictions, + cli::cli_abort( + "For class predictions, the object should be a classification model.", - call = call) + call = call + ) }, prob = if (object$spec$mode != "classification") { - cli::cli_abort("For probability predictions, + cli::cli_abort( + "For probability predictions, the object should be a classification model.", - call = call) + call = call + ) }, time = if (object$spec$mode != "censored regression") { - cli::cli_abort("For event time predictions, + cli::cli_abort( + "For event time predictions, the object should be a censored regression.", - call = call) + call = call + ) }, survival = if (object$spec$mode != "censored regression") { - cli::cli_abort("For survival probability predictions, + cli::cli_abort( + "For survival probability predictions, the object should be a censored regression.", - call = call) + call = call + ) }, hazard = if (object$spec$mode != "censored regression") { - cli::cli_abort("For hazard predictions, + cli::cli_abort( + "For hazard predictions, the object should be a censored regression.", - call = call) + call = call + ) }, - linear_pred = if (object$spec$mode != "censored regression" && - ! inherits(object$spec, "ordinal_reg")) { - cli::cli_abort("For the linear predictor, + linear_pred = if ( + object$spec$mode != "censored regression" && + !inherits(object$spec, "ordinal_reg") + ) { + cli::cli_abort( + "For the linear predictor, the object should be a censored or ordinal regression.", - call = call) - }) + call = call + ) + } + ) type } -check_spec_levels <- function (spec) { +check_spec_levels <- function(spec) { if ("class" %in% spec$lvl) { cli::cli_abort( c( @@ -75,17 +106,19 @@ check_spec_levels <- function (spec) { } } -check_spec_pred_type <- function (object, type, call = rlang::caller_env()) { +check_spec_pred_type <- function(object, type, call = rlang::caller_env()) { if (!spec_has_pred_type(object, type)) { possible_preds <- names(object$spec$method$pred) - cli::cli_abort("No {.val {type}} prediction method available for this model. + cli::cli_abort( + "No {.val {type}} prediction method available for this model. {.arg type} should be one of: {.val {possible_preds}}.", - call = call) + call = call + ) } invisible(NULL) } -spec_has_pred_type <- function (object, type) { +spec_has_pred_type <- function(object, type) { possible_preds <- names(object$spec$method$pred) any(possible_preds == type) } diff --git a/R/rand_forest-data.R b/R/rand_forest-data.R index c932513..7ec4d17 100644 --- a/R/rand_forest-data.R +++ b/R/rand_forest-data.R @@ -12,7 +12,6 @@ # `ordinalForest::ordfor` components make_rand_forest_ordinalForest <- function() { - parsnip::set_model_engine("rand_forest", "classification", "ordinalForest") parsnip::set_dependency( "rand_forest", @@ -113,11 +112,10 @@ make_rand_forest_ordinalForest <- function() { data = c(x = "x", y = "y"), protect = c("x", "y"), func = c(pkg = "ordered", fun = "ordinalForest_wrapper"), - defaults = - list( - num.threads = 1, - perffunction = "probability" - ) + defaults = list( + num.threads = 1, + perffunction = "probability" + ) ) ) @@ -142,11 +140,10 @@ make_rand_forest_ordinalForest <- function() { pre = NULL, post = function(x, object) x$ypred, func = c(fun = "predict"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data) - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data) + ) ) ) @@ -175,11 +172,10 @@ make_rand_forest_ordinalForest <- function() { tibble::as_tibble(x) }, func = c(fun = "predict"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data) - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data) + ) ) ) } @@ -188,7 +184,6 @@ make_rand_forest_ordinalForest <- function() { # `orf::orf` components make_rand_forest_orf <- function() { - parsnip::set_model_engine("rand_forest", "classification", "orf") parsnip::set_dependency( "rand_forest", @@ -290,12 +285,11 @@ make_rand_forest_orf <- function() { pre = NULL, post = orf_class_post, func = c(fun = "predict"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "class" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "class" + ) ) ) @@ -308,12 +302,11 @@ make_rand_forest_orf <- function() { pre = NULL, post = orf_prob_post, func = c(fun = "predict"), - args = - list( - object = quote(object$fit), - newdata = quote(new_data), - type = "probs" - ) + args = list( + object = quote(object$fit), + newdata = quote(new_data), + type = "probs" + ) ) ) } diff --git a/R/rand_forest-ordinalForest.R b/R/rand_forest-ordinalForest.R index bd4487f..0c923cc 100644 --- a/R/rand_forest-ordinalForest.R +++ b/R/rand_forest-ordinalForest.R @@ -33,8 +33,11 @@ ordinalForest_wrapper <- function(x, y, ...) { x$.outcome <- y # execute call on modified inputs cl <- rlang::call2( - .fn = "ordfor", .ns = "ordinalForest", - depvar = ".outcome", data = rlang::expr(x), ... + .fn = "ordfor", + .ns = "ordinalForest", + depvar = ".outcome", + data = rlang::expr(x), + ... ) rlang::eval_tidy(cl) } diff --git a/R/rand_forest-orf.R b/R/rand_forest-orf.R index 3fc71a1..8ba4384 100644 --- a/R/rand_forest-orf.R +++ b/R/rand_forest-orf.R @@ -29,8 +29,11 @@ orf_wrapper <- function(X, Y, ...) { # execute call on modified inputs cl <- rlang::call2( - .fn = "orf", .ns = "orf", - X = X, Y = Y, ... + .fn = "orf", + .ns = "orf", + X = X, + Y = Y, + ... ) rlang::eval_tidy(cl) } diff --git a/README.md b/README.md index 2717426..f5aef49 100644 --- a/README.md +++ b/README.md @@ -86,19 +86,19 @@ remotes::install_github("corybrunson/ordered") ordered provides new engines for several models, including all engines for `ordinal_reg()`: -| model | engine | class | prob | linear_pred | -|:-------------------|:----------------|:------|:-----|:------------| -| `decision_tree` | `rpartScore` | ✔ | ✖ | ✖ | -| `gen_additive_mod` | `vgam` | ✔ | ✔ | ✔ | -| `ordinal_reg` | `polr` | ✔ | ✔ | ✖ | -| `ordinal_reg` | `clm` | ✔ | ✔ | ✔ | -| `ordinal_reg` | `lrm` | ✔ | ✔ | ✔ | -| `ordinal_reg` | `orm` | ✔ | ✔ | ✔ | -| `ordinal_reg` | `vglm` | ✔ | ✔ | ✔ | -| `ordinal_reg` | `ordinalNet` | ✔ | ✔ | ✔ | -| `ordinal_reg` | `glmnetcr` | ✔ | ✔ | ✖ | -| `rand_forest` | `ordinalForest` | ✔ | ✔ | ✖ | -| `rand_forest` | `orf` | ✔ | ✔ | ✖ | +| model | engine | class | prob | linear_pred | conf_int | +|:-------------------|:----------------|:------|:-----|:------------|:---------| +| `decision_tree` | `rpartScore` | ✔ | ✖ | ✖ | ✖ | +| `gen_additive_mod` | `vgam` | ✔ | ✔ | ✔ | ✖ | +| `ordinal_reg` | `polr` | ✔ | ✔ | ✖ | ✖ | +| `ordinal_reg` | `clm` | ✔ | ✔ | ✔ | ✔ | +| `ordinal_reg` | `lrm` | ✔ | ✔ | ✔ | ✖ | +| `ordinal_reg` | `orm` | ✔ | ✔ | ✔ | ✖ | +| `ordinal_reg` | `vglm` | ✔ | ✔ | ✔ | ✖ | +| `ordinal_reg` | `ordinalNet` | ✔ | ✔ | ✔ | ✖ | +| `ordinal_reg` | `glmnetcr` | ✔ | ✔ | ✖ | ✖ | +| `rand_forest` | `ordinalForest` | ✔ | ✔ | ✖ | ✖ | +| `rand_forest` | `orf` | ✔ | ✔ | ✖ | ✖ | ## Tunable model arguments diff --git a/tests/spelling.R b/tests/spelling.R index 6713838..c597a12 100644 --- a/tests/spelling.R +++ b/tests/spelling.R @@ -1,3 +1,7 @@ -if(requireNamespace('spelling', quietly = TRUE)) - spelling::spell_check_test(vignettes = TRUE, error = FALSE, - skip_on_cran = TRUE) +if (requireNamespace('spelling', quietly = TRUE)) { + spelling::spell_check_test( + vignettes = TRUE, + error = FALSE, + skip_on_cran = TRUE + ) +} diff --git a/tests/testthat/helper-data.R b/tests/testthat/helper-data.R index 385278d..abda8dd 100644 --- a/tests/testthat/helper-data.R +++ b/tests/testthat/helper-data.R @@ -18,7 +18,7 @@ if (rlang::is_installed("QSARdata")) { ) |> dplyr::slice_sample(n = 20, by = class) caco_train <- caco_dat[-c(1:2, 21:22, 41:42), ] - caco_test <- caco_dat[ c(1:2, 21:22, 41:42), ] + caco_test <- caco_dat[c(1:2, 21:22, 41:42), ] } get_house <- function() { diff --git a/tests/testthat/test-decision_tree-rpartScore.R b/tests/testthat/test-decision_tree-rpartScore.R index 1abc4ee..ef963bf 100644 --- a/tests/testthat/test-decision_tree-rpartScore.R +++ b/tests/testthat/test-decision_tree-rpartScore.R @@ -150,14 +150,29 @@ test_that("arguments agree", { # `split` and `prune` specify `split` and `eval` functions. odt_def_funs <- odt_def_fit$fit$functions odt_arg_funs <- odt_arg_fit$fit$functions - expect_true(identical(odt_def_funs$summary, odt_arg_funs$summary, - ignore.environment = TRUE)) - expect_true(identical(odt_def_funs$text, odt_arg_funs$text, - ignore.environment = TRUE)) - expect_false(identical(odt_def_funs$eval, odt_arg_funs$eval, - ignore.environment = TRUE)) - expect_false(identical(odt_def_funs$split, odt_arg_funs$split, - ignore.environment = TRUE)) - expect_true(identical(odt_def_funs$init, odt_arg_funs$init, - ignore.environment = TRUE)) + expect_true(identical( + odt_def_funs$summary, + odt_arg_funs$summary, + ignore.environment = TRUE + )) + expect_true(identical( + odt_def_funs$text, + odt_arg_funs$text, + ignore.environment = TRUE + )) + expect_false(identical( + odt_def_funs$eval, + odt_arg_funs$eval, + ignore.environment = TRUE + )) + expect_false(identical( + odt_def_funs$split, + odt_arg_funs$split, + ignore.environment = TRUE + )) + expect_true(identical( + odt_def_funs$init, + odt_arg_funs$init, + ignore.environment = TRUE + )) }) diff --git a/tests/testthat/test-dials.R b/tests/testthat/test-dials.R index 6cd5fdb..cd8091e 100644 --- a/tests/testthat/test-dials.R +++ b/tests/testthat/test-dials.R @@ -8,13 +8,17 @@ test_that("`threshold_structure` dial values and customization", { test_that("VGAM `ordinal_link` values extend the bundled dial", { expect_true(all(dials::values_ordinal_link %in% values_ordinal_link_VGAM)) - expect_true(all(c("foldsqrt", "logc", "gord", "pord", "nbord") %in% - values_ordinal_link_VGAM)) + expect_true(all( + c("foldsqrt", "logc", "gord", "pord", "nbord") %in% + values_ordinal_link_VGAM + )) }) test_that("VGAM `threshold_structure` values extend the bundled dial", { - expect_true(all(dials::values_threshold_structure %in% - values_threshold_structure_VGAM)) + expect_true(all( + dials::values_threshold_structure %in% + values_threshold_structure_VGAM + )) expect_true("qnorm" %in% values_threshold_structure_VGAM) }) @@ -27,8 +31,10 @@ test_that("`parallel_reg` dial values and customization", { }) test_that("`ordinalNet::ordinalNet` dial customization", { - expect_equal(parallel_penalty_factor(range = c(-2, 2))$range, - list(lower = -2, upper = 2)) + expect_equal( + parallel_penalty_factor(range = c(-2, 2))$range, + list(lower = -2, upper = 2) + ) }) test_that("`rpartScore::rpartScore` dial customization", { diff --git a/tests/testthat/test-gen_additive_mod-VGAM.R b/tests/testthat/test-gen_additive_mod-VGAM.R index ecd8dcd..a79f3e3 100644 --- a/tests/testthat/test-gen_additive_mod-VGAM.R +++ b/tests/testthat/test-gen_additive_mod-VGAM.R @@ -133,7 +133,9 @@ test_that("class prediction", { fit(class ~ s(mol_weight) + volume + s(ClogP), data = caco_train) orig_pred <- VGAM::predict( - tidy_fit$fit, newdata = caco_train, type = "response" + tidy_fit$fit, + newdata = caco_train, + type = "response" ) orig_pred <- apply(orig_pred, 1L, which.max) orig_pred <- ordered(tidy_fit$lvl[orig_pred], tidy_fit$lvl) @@ -160,7 +162,9 @@ test_that("probability prediction", { fit(class ~ mol_weight + s(volume) + s(ClogP), data = caco_train) orig_pred <- VGAM::predict( - tidy_fit$fit, newdata = caco_train, type = "response" + tidy_fit$fit, + newdata = caco_train, + type = "response" ) orig_pred <- tibble::as_tibble(orig_pred) names(orig_pred) <- paste0(".pred_", names(orig_pred)) @@ -222,7 +226,8 @@ test_that("interfaces agree", { expect_equal( slot(onet_f_fit$fit, s), slot(onet_xy_fit$fit, s), - ignore_attr = TRUE, ignore_formula_env = TRUE + ignore_attr = TRUE, + ignore_formula_env = TRUE ) } }) diff --git a/tests/testthat/test-ordinal_reg-VGAM.R b/tests/testthat/test-ordinal_reg-VGAM.R index 7c3a2e4..0bc090c 100644 --- a/tests/testthat/test-ordinal_reg-VGAM.R +++ b/tests/testthat/test-ordinal_reg-VGAM.R @@ -26,7 +26,8 @@ test_that("model object", { expect_equal( slot(orig_fit, s), slot(tidy_fit$fit, s), - ignore_attr = TRUE, ignore_formula_env = TRUE + ignore_attr = TRUE, + ignore_formula_env = TRUE ) } @@ -37,11 +38,15 @@ test_that("model object", { Sat ~ Type + Infl + Cont, family = if (utils::packageVersion("VGAM") == package_version("1.1.9")) { VGAM::cratio( - link = "probitlink", parallel = TRUE, threshold = "symmetric1" + link = "probitlink", + parallel = TRUE, + threshold = "symmetric1" ) } else { VGAM::cratio( - link = "probitlink", parallel = TRUE, Thresh = "symm1" + link = "probitlink", + parallel = TRUE, + Thresh = "symm1" ) }, data = house_sub @@ -51,7 +56,8 @@ test_that("model object", { set_engine("vglm") |> set_mode("classification") |> set_args( - ordinal_link = "probit", odds_link = "continuation_ratio", + ordinal_link = "probit", + odds_link = "continuation_ratio", threshold_structure = "symmetric_median" ) set.seed(seed) @@ -62,7 +68,8 @@ test_that("model object", { expect_equal( slot(orig_fit, s), slot(tidy_fit$fit, s), - ignore_attr = TRUE, ignore_formula_env = TRUE + ignore_attr = TRUE, + ignore_formula_env = TRUE ) } @@ -106,7 +113,8 @@ test_that("case weights", { expect_equal( slot(orig_fit, s), slot(tidy_fit$fit, s), - ignore_attr = TRUE, ignore_formula_env = TRUE + ignore_attr = TRUE, + ignore_formula_env = TRUE ) } }) @@ -216,7 +224,8 @@ test_that("interfaces agree", { expect_equal( slot(onet_f_fit$fit, s), slot(onet_xy_fit$fit, s), - ignore_attr = TRUE, ignore_formula_env = TRUE + ignore_attr = TRUE, + ignore_formula_env = TRUE ) } }) @@ -228,7 +237,8 @@ test_that("arguments agree", { onet_arg_spec <- ordinal_reg( parallel_reg = TRUE, - ordinal_link = "cloglog", odds_link = "stopping_ratio" + ordinal_link = "cloglog", + odds_link = "stopping_ratio" ) |> set_mode("classification") |> set_engine("vglm") @@ -269,7 +279,8 @@ test_that("parallel regression argument handles logicals", { expect_equal( slot(tidy_fit$fit, s), slot(orig_fit, s), - ignore_attr = TRUE, ignore_formula_env = TRUE + ignore_attr = TRUE, + ignore_formula_env = TRUE ) } @@ -291,7 +302,8 @@ test_that("parallel regression argument handles logicals", { expect_equal( slot(tidy_fit$fit, s), slot(orig_fit, s), - ignore_attr = TRUE, ignore_formula_env = TRUE + ignore_attr = TRUE, + ignore_formula_env = TRUE ) } }) @@ -351,15 +363,21 @@ test_that("VGAM wrappers translate standardized argument values", { # native values pass through unchanged native <- if (utils::packageVersion("VGAM") == package_version("1.1.9")) { VGAM_vglm_wrapper( - Sat ~ Infl + Type, data = house_data, - family = "sratio", link = "probitlink", - threshold = "symmetric1", parallel = TRUE + Sat ~ Infl + Type, + data = house_data, + family = "sratio", + link = "probitlink", + threshold = "symmetric1", + parallel = TRUE ) } else { VGAM_vglm_wrapper( - Sat ~ Infl + Type, data = house_data, - family = "sratio", link = "probitlink", - Thresh = "symm1", parallel = TRUE + Sat ~ Infl + Type, + data = house_data, + family = "sratio", + link = "probitlink", + Thresh = "symm1", + parallel = TRUE ) } expect_equal(native@family@infos()$link, "probitlink") @@ -367,8 +385,11 @@ test_that("VGAM wrappers translate standardized argument values", { # standardized values are converted standardized <- VGAM_vglm_wrapper( - Sat ~ Infl + Type, data = house_data, - family = "stopping_ratio", link = "probit", parallel = TRUE, + Sat ~ Infl + Type, + data = house_data, + family = "stopping_ratio", + link = "probit", + parallel = TRUE, Thresh = "symmetric_median" ) expect_equal(standardized@family@infos()$link, "probitlink") diff --git a/tests/testthat/test-ordinal_reg-clm-wrapper.R b/tests/testthat/test-ordinal_reg-clm-wrapper.R index 351c984..b576e58 100644 --- a/tests/testthat/test-ordinal_reg-clm-wrapper.R +++ b/tests/testthat/test-ordinal_reg-clm-wrapper.R @@ -119,7 +119,8 @@ test_that("clm_wrapper() records relays revalued arguments in the call", { res <- clm_wrapper( SURENESS ~ PROD, data = soup, - link = "logistic", threshold = "symmetric_zero" + link = "logistic", + threshold = "symmetric_zero" ) # unmodified call expect_equal(res$call$link, "logistic") diff --git a/tests/testthat/test-ordinal_reg-clm.R b/tests/testthat/test-ordinal_reg-clm.R index 12b1e9d..801ae77 100644 --- a/tests/testthat/test-ordinal_reg-clm.R +++ b/tests/testthat/test-ordinal_reg-clm.R @@ -1,5 +1,3 @@ -seed <- 144688L - # specification: arguments ----------------------------------------------------- test_that("specification handles model parameters", { @@ -53,7 +51,8 @@ test_that("model object", { ) tidy_spec <- ordinal_reg( - ordinal_link = "probit", threshold_structure = "symmetric_median" + ordinal_link = "probit", + threshold_structure = "symmetric_median" ) |> set_engine("clm") |> set_mode("classification") @@ -154,6 +153,94 @@ test_that("linear_pred prediction", { expect_equal(orig_pred, tidy_pred) }) +# prediction: conf_int --------------------------------------------------------- + +conf_int_bounds <- function(x) { + x <- as.matrix(x) + list( + lo = x[, grepl("^\\.pred_lower_", colnames(x)), drop = FALSE], + hi = x[, grepl("^\\.pred_upper_", colnames(x)), drop = FALSE] + ) +} + +test_that("conf_int prediction", { + skip_if_not_installed("MASS") + skip_if_not_installed("ordinal") + house_sub <- get_house()$sub + + tidy_spec <- ordinal_reg() |> + set_engine("clm") |> + set_mode("classification") + set.seed(seed) + tidy_fit <- fit(tidy_spec, Sat ~ Infl + Type + Cont, data = house_sub) + + tidy_ci <- predict(tidy_fit, house_sub, type = "conf_int") + expect_identical(tidy_ci, predict_confint(tidy_fit, house_sub)) + expect_identical( + names(tidy_ci), + c( + ".pred_lower_Low", + ".pred_upper_Low", + ".pred_lower_Medium", + ".pred_upper_Medium", + ".pred_lower_High", + ".pred_upper_High" + ) + ) + expect_identical(nrow(tidy_ci), nrow(house_sub)) + expect_identical(attr(tidy_ci, "level"), 0.95) + + bounds <- conf_int_bounds(tidy_ci) + expect_true(all(bounds$lo >= 0)) + expect_true(all(bounds$hi <= 1)) + expect_true(all(bounds$lo <= bounds$hi)) + + tidy_prob <- as.matrix(predict(tidy_fit, house_sub, type = "prob")) + expect_true(all(tidy_prob >= bounds$lo)) + expect_true(all(tidy_prob <= bounds$hi)) + + ci_80 <- predict(tidy_fit, house_sub, type = "conf_int", level = 0.8) + expect_identical(attr(ci_80, "level"), 0.8) + bounds_80 <- conf_int_bounds(ci_80) + expect_true(all(bounds_80$lo >= bounds$lo)) + expect_true(all(bounds_80$hi <= bounds$hi)) + expect_lt(mean(bounds_80$hi - bounds_80$lo), mean(bounds$hi - bounds$lo)) + + # with standard errors + + tidy_ci <- predict(tidy_fit, house_sub, type = "conf_int", std_error = TRUE) + expect_identical( + names(tidy_ci), + c( + ".pred_lower_Low", + ".pred_upper_Low", + ".pred_lower_Medium", + ".pred_upper_Medium", + ".pred_lower_High", + ".pred_upper_High", + ".std_error_Low", + ".std_error_Medium", + ".std_error_High" + ) + ) + tidy_se <- + tidy_ci[, c(".std_error_Low", ".std_error_Medium", ".std_error_High")] + expect_true(all(as.matrix(tidy_se) > 0)) + + bounds <- conf_int_bounds(tidy_ci) + orig_pred <- predict( + tidy_fit$fit, + newdata = house_sub |> subset(select = -Sat), + type = "prob", + se.fit = TRUE, + interval = TRUE, + level = 0.95 + ) + expect_equal(unname(as.matrix(tidy_se)), unname(orig_pred$se.fit)) + expect_equal(unname(bounds$lo), unname(orig_pred$lwr)) + expect_equal(unname(bounds$hi), unname(orig_pred$upr)) +}) + # translation & interfaces ----------------------------------------------------- test_that("interfaces agree", { @@ -224,7 +311,8 @@ test_that("parallel regression argument handles logicals", { set.seed(seed) orig_fit <- ordinal::clm( - Sat ~ Infl + Cont, data = house_sub + Sat ~ Infl + Cont, + data = house_sub ) tidy_fit$fit$call <- orig_fit$call <- NULL @@ -239,7 +327,8 @@ test_that("parallel regression argument handles logicals", { set.seed(seed) orig_fit <- ordinal::clm( - Sat ~ 1, data = house_sub, + Sat ~ 1, + data = house_sub, nominal = ~ Infl + Cont ) @@ -258,7 +347,8 @@ test_that("parallel regression argument handles logicals", { set.seed(seed) suppressWarnings( orig_fit <- ordinal::clm( - Sat ~ 1, data = house_sub, + Sat ~ 1, + data = house_sub, # wrapper expands full interaction expressions into summands nominal = ~ Infl + Cont + Infl:Cont ) diff --git a/tests/testthat/test-ordinal_reg-glmnetcr.R b/tests/testthat/test-ordinal_reg-glmnetcr.R index e8fde27..14e52e6 100644 --- a/tests/testthat/test-ordinal_reg-glmnetcr.R +++ b/tests/testthat/test-ordinal_reg-glmnetcr.R @@ -1,5 +1,3 @@ -seed <- 144688L - # model: basic ----------------------------------------------------------------- test_that("model object (penalty path from original to tidy)", { @@ -9,7 +7,8 @@ test_that("model object (penalty path from original to tidy)", { # https://stackoverflow.com/a/4569239 house_vars <- model.matrix( - Sat ~ Type + Infl + Cont + 0, data = house_sub, + Sat ~ Type + Infl + Cont + 0, + data = house_sub, contrasts.arg = lapply(house_sub[, 2:4], contrasts, contrasts = FALSE) ) @@ -25,7 +24,7 @@ test_that("model object (penalty path from original to tidy)", { ) tidy_spec <- ordinal_reg(penalty = 0.01) |> - set_engine("glmnetcr", path_values = !! orig_fit$lambda) + set_engine("glmnetcr", path_values = !!orig_fit$lambda) set.seed(seed) tidy_fit <- fit(tidy_spec, Sat ~ Type + Infl + Cont, data = house_sub) @@ -42,12 +41,13 @@ test_that("model object (penalty path from original to tidy)", { house_vars, y = house_sub$Sat, nlambda = 120, - alpha = .5, method = "forward" + alpha = .5, + method = "forward" ) ) tidy_spec <- ordinal_reg(penalty = 0.01, mixture = .5) |> - set_engine("glmnetcr", method = "forward", path_values = !! orig_fit$lambda) + set_engine("glmnetcr", method = "forward", path_values = !!orig_fit$lambda) set.seed(seed) tidy_fit <- fit(tidy_spec, Sat ~ Type + Infl + Cont, data = house_sub) @@ -63,7 +63,8 @@ test_that("model object (penalty path from tidy to original)", { house_sub <- get_house()$sub house_vars <- model.matrix( - Sat ~ Type + Infl + Cont + 0, data = house_sub, + Sat ~ Type + Infl + Cont + 0, + data = house_sub, contrasts.arg = lapply(house_sub[, 2:4], contrasts, contrasts = FALSE) ) @@ -103,7 +104,8 @@ test_that("model object (penalty path from tidy to original)", { house_vars, y = house_sub$Sat, lambda = tidy_fit$fit$lambda, - alpha = .5, method = "forward" + alpha = .5, + method = "forward" ) ) @@ -125,24 +127,27 @@ test_that("case weights", { wt_data <- transform(house_data, Freq = frequency_weights(Freq)) wt_vars <- model.matrix( - Sat ~ Type + Infl + Cont + 0, data = wt_data, + Sat ~ Type + Infl + Cont + 0, + data = wt_data, contrasts.arg = lapply(wt_data[, 2:4], contrasts, contrasts = FALSE) ) set.seed(seed) orig_fit <- suppressWarnings( glmnetcr::glmnetcr( - wt_vars, y = wt_data$Sat, + wt_vars, + y = wt_data$Sat, weights = wt_data$Freq ) ) tidy_spec <- ordinal_reg(penalty = 1) |> - set_engine("glmnetcr", path_values = !! orig_fit$lambda) + set_engine("glmnetcr", path_values = !!orig_fit$lambda) set.seed(seed) tidy_fit <- fit( tidy_spec, - Sat ~ Type + Infl + Cont, data = wt_data, + Sat ~ Type + Infl + Cont, + data = wt_data, case_weights = wt_data$Freq ) @@ -164,7 +169,8 @@ test_that("class prediction", { ) house_vars <- model.matrix( - Sat ~ Type + Cont + 0, data = house_sub, + Sat ~ Type + Cont + 0, + data = house_sub, contrasts.arg = lapply(house_sub[, 3:4], contrasts, contrasts = FALSE) ) @@ -176,7 +182,8 @@ test_that("class prediction", { orig_pred <- tibble::tibble(.pred_class = orig_pred) tidy_pred <- predict( - tidy_fit, house_sub, + tidy_fit, + house_sub, penalty = tidy_fit$fit$lambda[s_bic] ) @@ -196,19 +203,21 @@ test_that("prob prediction", { ) house_vars <- model.matrix( - Sat ~ Type + Cont + 0, data = house_sub, + Sat ~ Type + Cont + 0, + data = house_sub, contrasts.arg = lapply(house_sub[, 3:4], contrasts, contrasts = FALSE) ) # use BIC-selected step (default) pred_all <- predict(tidy_fit$fit, newx = house_vars) s_bic <- which.min(pred_all$BIC) - orig_pred <- pred_all$probs[, , s_bic] + orig_pred <- pred_all$probs[,, s_bic] orig_pred <- tibble::as_tibble(orig_pred) orig_pred <- set_names(orig_pred, paste0(".pred_", colnames(orig_pred))) tidy_pred <- predict( - tidy_fit, house_sub, + tidy_fit, + house_sub, type = "prob", penalty = tidy_fit$fit$lambda[s_bic] ) @@ -232,19 +241,26 @@ test_that("multi-penalty wrapper output structure", { pen_vals <- fit$lambda[length(fit$lambda) * seq(4) / 5] house_vars <- model.matrix( - Sat ~ Type + Cont + 0, data = house_sub, + Sat ~ Type + Cont + 0, + data = house_sub, contrasts.arg = lapply(house_sub[, 3:4], contrasts, contrasts = FALSE) ) # probability: array [nrow, nclass, npenalty] prob_res <- multi_predict_glmnetcr_wrapper( - fit, house_vars, type = "prob", lambda = pen_vals + fit, + house_vars, + type = "prob", + lambda = pen_vals ) expect_equal(dim(prob_res), c(n, length(tidy_fit$lvl), length(pen_vals))) # class: matrix [nrow, npenalty] class_res <- multi_predict_glmnetcr_wrapper( - fit, house_vars, type = "class", lambda = pen_vals + fit, + house_vars, + type = "class", + lambda = pen_vals ) expect_equal(dim(class_res), c(n, length(pen_vals))) }) @@ -260,23 +276,36 @@ test_that("multi-penalty wrapper endpoint use", { ) fit <- tidy_fit$fit house_vars <- model.matrix( - Sat ~ Type + Cont + 0, data = house_sub, + Sat ~ Type + Cont + 0, + data = house_sub, contrasts.arg = lapply(house_sub[, 3:4], contrasts, contrasts = FALSE) ) prob_lo <- multi_predict_glmnetcr_wrapper( - fit, house_vars, "prob", lambda = min(fit$lambda) / 10 + fit, + house_vars, + "prob", + lambda = min(fit$lambda) / 10 ) prob_at <- multi_predict_glmnetcr_wrapper( - fit, house_vars, "prob", lambda = min(fit$lambda) + fit, + house_vars, + "prob", + lambda = min(fit$lambda) ) expect_equal(prob_lo, prob_at) prob_hi <- multi_predict_glmnetcr_wrapper( - fit, house_vars, "prob", lambda = max(fit$lambda) * 10 + fit, + house_vars, + "prob", + lambda = max(fit$lambda) * 10 ) prob_at <- multi_predict_glmnetcr_wrapper( - fit, house_vars, "prob", lambda = max(fit$lambda) + fit, + house_vars, + "prob", + lambda = max(fit$lambda) ) expect_equal(prob_hi, prob_at) }) @@ -292,21 +321,28 @@ test_that("single- and multi-penalty wrappers agree", { ) fit <- tidy_fit$fit house_vars <- model.matrix( - Sat ~ Type + Cont + 0, data = house_sub, + Sat ~ Type + Cont + 0, + data = house_sub, contrasts.arg = lapply(house_sub[, 3:4], contrasts, contrasts = FALSE) ) pen_vals <- fit$lambda[length(fit$lambda) * seq(4) / 5] for (type in c("class", "prob")) { multi_res <- multi_predict_glmnetcr_wrapper( - fit, house_vars, type = type, lambda = pen_vals + fit, + house_vars, + type = type, + lambda = pen_vals ) for (i in seq_along(pen_vals)) { single_res <- predict_glmnetcr_wrapper( - fit, house_vars, type = type, lambda = pen_vals[i] + fit, + house_vars, + type = type, + lambda = pen_vals[i] ) if (type == "prob") { - expect_equal(multi_res[, , i], single_res) + expect_equal(multi_res[,, i], single_res) } else { expect_equal(multi_res[, i], single_res) } @@ -325,7 +361,8 @@ test_that("multiple prediction structure", { ) house_vars <- model.matrix( - Sat ~ Type + Cont + 0, data = house_sub, + Sat ~ Type + Cont + 0, + data = house_sub, contrasts.arg = lapply(house_sub[, 3:4], contrasts, contrasts = FALSE) ) @@ -392,7 +429,10 @@ test_that("multiple prediction values match sequential prediction values", { tidyr::unnest(cols = c(.pred)) for (i in seq_along(pen_vals)) { single_pred <- predict( - tidy_fit, house_sub, type = "class", penalty = pen_vals[i] + tidy_fit, + house_sub, + type = "class", + penalty = pen_vals[i] ) expect_equal( multi_pred |> @@ -409,7 +449,10 @@ test_that("multiple prediction values match sequential prediction values", { tidyr::unnest(cols = c(.pred)) for (i in seq_along(pen_vals)) { single_pred <- predict( - tidy_fit, house_sub, type = "prob", penalty = pen_vals[i] + tidy_fit, + house_sub, + type = "prob", + penalty = pen_vals[i] ) expect_equal( multi_pred |> @@ -460,7 +503,7 @@ test_that("arguments agree", { skip_if_not_installed("QSARdata") # using mixture as model-level parameter - pen_vec <- 10 ^ seq(0, -2, -.5) + pen_vec <- 10^seq(0, -2, -.5) gcr_arg_spec <- ordinal_reg(penalty = 1, mixture = .25) |> set_mode("classification") |> diff --git a/tests/testthat/test-ordinal_reg-ordinalNet.R b/tests/testthat/test-ordinal_reg-ordinalNet.R index b19c899..5744091 100644 --- a/tests/testthat/test-ordinal_reg-ordinalNet.R +++ b/tests/testthat/test-ordinal_reg-ordinalNet.R @@ -7,7 +7,8 @@ test_that("model object (penalty path from original to tidy)", { # https://stackoverflow.com/a/4569239 house_vars <- model.matrix( - Sat ~ Type + Infl + Cont + 0, data = house_sub, + Sat ~ Type + Infl + Cont + 0, + data = house_sub, contrasts.arg = lapply(house_sub[, 2:4], contrasts, contrasts = FALSE) ) @@ -17,7 +18,9 @@ test_that("model object (penalty path from original to tidy)", { orig_fit <- ordinalNet::ordinalNet( house_vars, y = house_sub$Sat, - nLambda = 120, lambdaMinRatio = .001, includeLambda0 = TRUE + nLambda = 120, + lambdaMinRatio = .001, + includeLambda0 = TRUE ) tidy_spec <- ordinal_reg(penalty = 0.01) |> @@ -37,8 +40,11 @@ test_that("model object (penalty path from original to tidy)", { orig_fit <- ordinalNet::ordinalNet( house_vars, y = house_sub$Sat, - nLambda = 120, lambdaMinRatio = .001, includeLambda0 = TRUE, - alpha = .5, family = "sratio" + nLambda = 120, + lambdaMinRatio = .001, + includeLambda0 = TRUE, + alpha = .5, + family = "sratio" ) tidy_spec <- @@ -62,7 +68,8 @@ test_that("model object (penalty path from tidy to original)", { # https://stackoverflow.com/a/4569239 house_vars <- model.matrix( - Sat ~ Type + Infl + Cont + 0, data = house_sub, + Sat ~ Type + Infl + Cont + 0, + data = house_sub, contrasts.arg = lapply(house_sub[, 2:4], contrasts, contrasts = FALSE) ) @@ -100,7 +107,8 @@ test_that("model object (penalty path from tidy to original)", { house_vars, y = house_sub$Sat, lambdaVals = tidy_fit$fit$lambdaVals, - alpha = .5, family = "sratio" + alpha = .5, + family = "sratio" ) # FIXME: Why are these not equal? (The penalty paths are equal.) @@ -126,12 +134,12 @@ test_that("case weights", { Medium = ifelse(Sat == "Medium", Freq, 0L), High = ifelse(Sat == "High", Freq, 0L) ) |> - subset(select = -c(Sat, Freq)) -> - house_nums + subset(select = -c(Sat, Freq)) -> house_nums house_vars <- house_nums[, 1:3] house_vars <- model.matrix( - ~ Type + Infl + Cont + 0, data = house_vars, + ~ Type + Infl + Cont + 0, + data = house_vars, contrasts.arg = lapply(house_vars, contrasts, contrasts = FALSE) ) @@ -147,7 +155,9 @@ test_that("case weights", { house_vars, y = house_resp, # lambdaVals = .001 - nLambda = 120, lambdaMinRatio = .001, includeLambda0 = TRUE + nLambda = 120, + lambdaMinRatio = .001, + includeLambda0 = TRUE ) tidy_spec <- ordinal_reg(penalty = 0.01) |> @@ -174,8 +184,11 @@ test_that("case weights", { house_vars, y = house_resp, # lambdaVals = .001 - nLambda = 120, lambdaMinRatio = .001, includeLambda0 = TRUE, - alpha = .5, family = "sratio" + nLambda = 120, + lambdaMinRatio = .001, + includeLambda0 = TRUE, + alpha = .5, + family = "sratio" ) tidy_spec <- ordinal_reg(penalty = 0.01) |> @@ -214,12 +227,12 @@ test_that("multinomial formulation", { direction = "wide", idvar = c("Infl", "Type", "Cont"), timevar = "Sat" - ) -> - house_agg + ) -> house_agg house_vars <- house_agg[, 1:3] house_vars <- model.matrix( - ~ Type + Infl + Cont + 0, data = house_vars, + ~ Type + Infl + Cont + 0, + data = house_vars, contrasts.arg = lapply(house_vars, contrasts, contrasts = FALSE) ) @@ -232,7 +245,9 @@ test_that("multinomial formulation", { orig_fit <- ordinalNet::ordinalNet( house_vars, y = house_resp, - nLambda = 120, lambdaMinRatio = .001, includeLambda0 = TRUE + nLambda = 120, + lambdaMinRatio = .001, + includeLambda0 = TRUE ) tidy_spec <- ordinal_reg(penalty = 0.01) |> @@ -261,7 +276,8 @@ test_that("class prediction", { # NB: `newx` must contain exactly those predictors used in the fit. house_vars <- model.matrix( - Sat ~ Type + Cont + 0, data = house_sub, + Sat ~ Type + Cont + 0, + data = house_sub, contrasts.arg = lapply(house_sub[, 3:4], contrasts, contrasts = FALSE) ) @@ -286,7 +302,8 @@ test_that("multiple prediction structure", { fit(Sat ~ Type + Cont, data = house_sub) house_vars <- model.matrix( - Sat ~ Type + Cont + 0, data = house_sub, + Sat ~ Type + Cont + 0, + data = house_sub, contrasts.arg = lapply(house_sub[, 3:4], contrasts, contrasts = FALSE) ) @@ -350,7 +367,10 @@ test_that("multiple prediction values match sequential prediction values", { tidyr::unnest(cols = c(.pred)) for (i in seq_along(pen_vals)) { single_pred <- predict( - tidy_fit, house_sub, type = "class", penalty = pen_vals[i] + tidy_fit, + house_sub, + type = "class", + penalty = pen_vals[i] ) expect_equal( multi_pred |> @@ -367,7 +387,10 @@ test_that("multiple prediction values match sequential prediction values", { tidyr::unnest(cols = c(.pred)) for (i in seq_along(pen_vals)) { single_pred <- predict( - tidy_fit, house_sub, type = "prob", penalty = pen_vals[i] + tidy_fit, + house_sub, + type = "prob", + penalty = pen_vals[i] ) expect_equal( multi_pred |> @@ -386,10 +409,11 @@ test_that("linear_pred prediction", { house_sub <- get_house()$sub tidy_fit <- ordinal_reg(penalty = 0.01, engine = "ordinalNet") |> - fit(Sat ~ Type + Infl + Cont, data = house_sub) + fit(Sat ~ Type + Infl + Cont, data = house_sub) house_vars <- model.matrix( - Sat ~ Type + Infl + Cont + 0, data = house_sub, + Sat ~ Type + Infl + Cont + 0, + data = house_sub, contrasts.arg = lapply(house_sub[, 2:4], contrasts, contrasts = FALSE) ) orig_link <- predict(tidy_fit$fit, newx = house_vars, type = "link") @@ -437,17 +461,18 @@ test_that("arguments agree", { ordinal_reg( penalty = 0.1, mixture = .25, - ordinal_link = "cloglog", odds_link = "stopping_ratio" + ordinal_link = "cloglog", + odds_link = "stopping_ratio" ) |> set_mode("classification") |> - set_engine("ordinalNet", path_values = 10 ^ seq(-6, -1)) + set_engine("ordinalNet", path_values = 10^seq(-6, -1)) expect_snapshot(onet_arg_spec |> translate()) expect_snapshot({ set.seed(13) onet_arg_fit <- fit(onet_arg_spec, class ~ ., data = caco_train) }) - expect_equal(onet_arg_fit$fit$args$lambdaVals, 10 ^ seq(-6, -1)) + expect_equal(onet_arg_fit$fit$args$lambdaVals, 10^seq(-6, -1)) expect_equal(onet_arg_fit$fit$args$alpha, .25) expect_equal(onet_arg_fit$fit$args$link, "cloglog") expect_equal(onet_arg_fit$fit$args$family, "sratio") @@ -459,7 +484,8 @@ house_sub <- get_house()$sub |> dplyr::filter(Type == "Apartment") |> dplyr::select(-Type) house_vars <- model.matrix( - Sat ~ Infl + Cont + 0, data = house_sub, + Sat ~ Infl + Cont + 0, + data = house_sub, contrasts.arg = lapply(house_sub[, 2:3], contrasts, contrasts = FALSE) ) @@ -473,7 +499,9 @@ test_that("parallel regression argument handles logicals", { orig_fit <- ordinalNet::ordinalNet( house_vars, y = house_sub$Sat, - nLambda = 120, lambdaMinRatio = .001, includeLambda0 = TRUE + nLambda = 120, + lambdaMinRatio = .001, + includeLambda0 = TRUE ) set.seed(seed) @@ -496,8 +524,11 @@ test_that("parallel regression argument handles logicals", { orig_fit <- ordinalNet::ordinalNet( house_vars, y = house_sub$Sat, - nLambda = 120, lambdaMinRatio = .001, includeLambda0 = TRUE, - nonparallelTerms = TRUE, parallelTerms = FALSE + nLambda = 120, + lambdaMinRatio = .001, + includeLambda0 = TRUE, + nonparallelTerms = TRUE, + parallelTerms = FALSE ), "nonparallelTerms" ) @@ -542,8 +573,10 @@ test_that("ordinalNet wrapper translates standardized argument values", { x <- matrix(rnorm(100), ncol = 2) y <- factor(rep(1:3, length.out = 50), ordered = TRUE) fits <- suppressWarnings(ordinalNet_wrapper( - x, y, - family = "cumulative_link", link = "cauchit", + x, + y, + family = "cumulative_link", + link = "cauchit", parallel_reg = FALSE )) expect_equal(fits$args$family, "cumulative") @@ -553,8 +586,10 @@ test_that("ordinalNet wrapper translates standardized argument values", { # native values and `parallel_reg = NULL` leave the defaults unchanged native <- ordinalNet_wrapper( - x, y, - family = "sratio", link = "logit", + x, + y, + family = "sratio", + link = "logit", parallel_reg = NULL ) expect_equal(native$args$family, "sratio") diff --git a/tests/testthat/test-ordinal_reg-polr.R b/tests/testthat/test-ordinal_reg-polr.R index 8568312..f044923 100644 --- a/tests/testthat/test-ordinal_reg-polr.R +++ b/tests/testthat/test-ordinal_reg-polr.R @@ -1,4 +1,3 @@ - # specification: arguments ----------------------------------------------------- test_that("specification handles model parameters", { diff --git a/tests/testthat/test-ordinal_reg-rms.R b/tests/testthat/test-ordinal_reg-rms.R index 32b2b0b..a9c7c1b 100644 --- a/tests/testthat/test-ordinal_reg-rms.R +++ b/tests/testthat/test-ordinal_reg-rms.R @@ -1,5 +1,3 @@ -seed <- 144688L - # model: basic ----------------------------------------------------------------- test_that("model object (lrm)", { @@ -371,8 +369,12 @@ test_that("arguments agree (lrm)", { # The penalty is passed to lrm(); compare to a direct call expect_equal( lrm_arg_fit$fit$penalty, - list(simple = 0.1, nonlinear = 0.1, - interaction = 0.1, nonlinear.interaction = 0.1) + list( + simple = 0.1, + nonlinear = 0.1, + interaction = 0.1, + nonlinear.interaction = 0.1 + ) ) }) @@ -394,7 +396,11 @@ test_that("arguments agree (orm)", { expect_equal(orm_arg_fit$fit$family, "cauchit") expect_equal( orm_arg_fit$fit$penalty, - list(simple = 0.1, nonlinear = 0.1, - interaction = 0.1, nonlinear.interaction = 0.1) + list( + simple = 0.1, + nonlinear = 0.1, + interaction = 0.1, + nonlinear.interaction = 0.1 + ) ) }) diff --git a/tests/testthat/test-rand_forest-ordinalForest.R b/tests/testthat/test-rand_forest-ordinalForest.R index 3761a45..a2e31bb 100644 --- a/tests/testthat/test-rand_forest-ordinalForest.R +++ b/tests/testthat/test-rand_forest-ordinalForest.R @@ -20,7 +20,10 @@ test_that("model object", { set_engine("ordinalForest") |> set_mode("classification") |> set_args( - nsets = 10, min_n = 20, ntreeperdiv = 100, trees = 10, + nsets = 10, + min_n = 20, + ntreeperdiv = 100, + trees = 10, # prevent 'min.node.size' warning perffunction = "equal" ) @@ -55,7 +58,10 @@ test_that("model object w/ probability metric", { set_engine("ordinalForest") |> set_mode("classification") |> set_args( - nsets = 10, min_n = 20, ntreeperdiv = 100, trees = 10, + nsets = 10, + min_n = 20, + ntreeperdiv = 100, + trees = 10, perffunction = "probability" ) set.seed(seed) @@ -139,8 +145,12 @@ test_that("class prediction", { tidy_fit <- rand_forest(trees = 10) |> set_engine("ordinalForest") |> set_mode("classification") |> - set_args(nsets = 10, ntreeperdiv = 100, - perffunction = "oneclass", classimp = "Medium") |> + set_args( + nsets = 10, + ntreeperdiv = 100, + perffunction = "oneclass", + classimp = "Medium" + ) |> fit(Sat ~ Type + Cont, data = house_sub) set.seed(seed) @@ -219,7 +229,10 @@ test_that("arguments agree", { set_mode("classification") |> set_engine( "ordinalForest", - nsets = 50, ntreeperdiv = 80, npermtrial = 70, nbest = 10 + nsets = 50, + ntreeperdiv = 80, + npermtrial = 70, + nbest = 10 ) |> set_args(perffunction = "probability") expect_snapshot(orf_arg_spec |> translate()) diff --git a/tests/testthat/test-rand_forest-orf.R b/tests/testthat/test-rand_forest-orf.R index cff757f..e2df426 100644 --- a/tests/testthat/test-rand_forest-orf.R +++ b/tests/testthat/test-rand_forest-orf.R @@ -1,5 +1,3 @@ -seed <- 144688L - # model: basic ----------------------------------------------------------------- test_that("model object", { @@ -16,18 +14,27 @@ test_that("model object", { set.seed(seed) orig_fit <- orf::orf( - house_mat, house_vec, - num.trees = 10, mtry = 4, min.node.size = 5, - replace = FALSE, sample.fraction = 0.5, - honesty = FALSE, inference = FALSE, importance = FALSE + house_mat, + house_vec, + num.trees = 10, + mtry = 4, + min.node.size = 5, + replace = FALSE, + sample.fraction = 0.5, + honesty = FALSE, + inference = FALSE, + importance = FALSE ) tidy_spec <- rand_forest() |> set_engine("orf") |> set_mode("classification") |> set_args( - trees = 10, mtry = 4, min_n = 5, - sample.fraction = 0.5, honesty = FALSE + trees = 10, + mtry = 4, + min_n = 5, + sample.fraction = 0.5, + honesty = FALSE ) set.seed(seed) tidy_fit <- fit(tidy_spec, Sat ~ Infl + Type + Cont, data = house_sub) @@ -48,7 +55,8 @@ test_that("class prediction", { set_engine("orf") |> set_mode("classification") |> set_args( - sample.fraction = 0.5, honesty = FALSE + sample.fraction = 0.5, + honesty = FALSE ) set.seed(seed) tidy_fit <- fit(tidy_spec, Sat ~ Type + Cont, data = house_train) @@ -79,7 +87,8 @@ test_that("probability prediction", { set_engine("orf") |> set_mode("classification") |> set_args( - sample.fraction = 0.5, honesty = FALSE + sample.fraction = 0.5, + honesty = FALSE ) set.seed(seed) tidy_fit <- fit(tidy_spec, Sat ~ Type + Cont, data = house_train) @@ -150,7 +159,9 @@ test_that("arguments agree", { set_mode("classification") |> set_engine( "orf", - sample.fraction = 0.7, honesty = TRUE, honesty.fraction = 0.4 + sample.fraction = 0.7, + honesty = TRUE, + honesty.fraction = 0.4 ) expect_snapshot(orf_arg_spec |> translate()) diff --git a/vignettes/template/build-examples.R b/vignettes/template/build-examples.R index 8ebba6d..141988d 100644 --- a/vignettes/template/build-examples.R +++ b/vignettes/template/build-examples.R @@ -62,7 +62,8 @@ oxford_c <- function(x) { } else { paste0( paste(x[seq(length(x) - 1L)], collapse = ", "), - ", or ", x[[length(x)]] + ", or ", + x[[length(x)]] ) } res @@ -74,7 +75,6 @@ readLines("vignettes/template/_template-examples-front.Rmd") |> write(file = vignette_rmd, append = FALSE) for (model_name in ordered_mods$model) { - model_hyphen <- str_replace_all(model_name, "\\_", "-") model_abbr <- str_replace_all(model_name, "(^|\\_)([a-z])[a-z]+", "\\2") @@ -84,19 +84,21 @@ for (model_name in ordered_mods$model) { engine_names <- ordered_specs |> filter(model == model_name) |> pull(engine) for (engine_name in engine_names) { - engine_hyphen <- str_replace_all(engine_name, "(\\_|\\.)", "-") model_args <- spec_args |> filter(engine == engine_name) |> - pull(args) |> unlist() |> + pull(args) |> + unlist() |> enframe(name = "arg", value = "value") |> unite(pass, arg, value, sep = " = ") |> - pull(pass) |> str_c(collapse = ", ") + pull(pass) |> + str_c(collapse = ", ") engine_args <- eng_args |> filter(engine == engine_name) |> - pull(args) |> unlist() |> + pull(args) |> + unlist() |> enframe(name = "arg", value = "value") |> unite(pass, arg, value, sep = " = ") |> pull(pass) @@ -110,10 +112,13 @@ for (model_name in ordered_mods$model) { types_clause <- oxford_c(type_phrases[engine_types]) type_lines <- map_chr( engine_types, - function(s) glue::glue( - "predict({abbr}_fit, house_test, type = \"{type}\")", - abbr = model_abbr, type = s - ) + function(s) { + glue::glue( + "predict({abbr}_fit, house_test, type = \"{type}\")", + abbr = model_abbr, + type = s + ) + } ) types_chunk <- str_c(type_lines, collapse = "\n") @@ -128,7 +133,5 @@ for (model_name in ordered_mods$model) { gsub(pattern = "\\{types_clause\\}", replacement = types_clause) |> gsub(pattern = "\\{types_chunk\\}", replacement = types_chunk) |> write(file = vignette_rmd, append = TRUE) - } - } From ee9daec28b5d2072338ef8ad645f94ee156e5646 Mon Sep 17 00:00:00 2001 From: Jason Cory Brunson Date: Fri, 2 Oct 2026 11:51:38 -0400 Subject: [PATCH 2/2] rename helper for consistency --- R/ordinal_reg-clm.R | 2 +- R/ordinal_reg-data.R | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/ordinal_reg-clm.R b/R/ordinal_reg-clm.R index 2e7844f..47f23a8 100644 --- a/R/ordinal_reg-clm.R +++ b/R/ordinal_reg-clm.R @@ -151,7 +151,7 @@ predict_clm_pre <- function(new_data, object) { new_data } -clm_conf_int_post <- function(x, object) { +predict_clm_confint_post <- function(x, object) { lvl <- object$lvl bounds <- vector("list", 2L * length(lvl)) for (i in seq_along(lvl)) { diff --git a/R/ordinal_reg-data.R b/R/ordinal_reg-data.R index c8df8b5..3239242 100644 --- a/R/ordinal_reg-data.R +++ b/R/ordinal_reg-data.R @@ -873,7 +873,7 @@ make_ordinal_reg_clm <- function() { type = "conf_int", value = list( pre = predict_clm_pre, - post = clm_conf_int_post, + post = predict_clm_confint_post, func = c(fun = "predict"), args = list( object = quote(object$fit),