Nothing
#' Predict from an orthoMTL Model
#'
#' Generate predictions from a fitted \code{orthoMTL} model for new
#' observations. In survival mode, predictions are projected onto the
#' non-negative non-increasing space to ensure monotonicity across tasks.
#'
#' @param object A fitted model object of class \code{"orthoMTL"}.
#' @param newdata A numeric matrix of new observations with dimensions
#' \code{n_new x p}. Column names are used for feature alignment if
#' available in the fitted object.
#' @param type Character; scale of the returned predictions. One of:
#' \describe{
#' \item{\code{"link"}}{(default) the raw linear predictor
#' \eqn{X B} (after the survival monotonicity projection, if
#' applicable). Preserves the historical behaviour.}
#' \item{\code{"response"}}{for \code{logistic} fits, the sigmoid
#' \eqn{1 / (1 + e^{-XB})} giving \eqn{P(Y = +1)}; for non-logistic
#' fits identical to \code{"link"}.}
#' \item{\code{"class"}}{for \code{logistic} fits, the predicted
#' class label in \eqn{\{-1, +1\}} (\code{sign(XB)}, with 0 mapped
#' to \code{+1}). Errors for non-logistic fits.}
#' }
#' @param ... Additional arguments (currently ignored).
#'
#' @return A numeric matrix of predictions with dimensions
#' \code{n_new x n_tasks}. Column names correspond to task names
#' if available.
#'
#' @details
#' If the fitted object contains \code{feature_names} (i.e., the training
#' matrix \code{X} had column names), \code{newdata} columns are aligned
#' to match. Missing features cause an error. Extra features trigger a
#' warning and are dropped.
#'
#' In survival mode (\code{object$hyperparameters$survival == TRUE}),
#' each row of the raw prediction matrix is projected via
#' \code{nnmaxheap_C()} to enforce non-negative, non-increasing values
#' across tasks (time thresholds).
#'
#' @references
#' Vervier, K., Mahe, P., d'Aspremont, A., Veyrieras, J.-B., and
#' Vert, J.-P. (2014). On Learning Matrices with Orthogonal Columns
#' or Disjoint Supports. \emph{ECML-PKDD 2014}.
#' \url{https://hal.science/hal-00985654}
#'
#' @method predict orthoMTL
#' @export
#'
#' @examples
#' set.seed(42)
#' n <- 100; p <- 10; n_tasks <- 3
#' X <- matrix(rnorm(n * p), n, p)
#' colnames(X) <- paste0("V", seq_len(p))
#' W_true <- qr.Q(qr(matrix(rnorm(p * n_tasks), p, n_tasks)))
#' Y <- X %*% W_true + matrix(rnorm(n * n_tasks), n) * 0.1
#' K <- matrix(1, n_tasks, n_tasks); diag(K) <- 0.5
#' fit <- orthoMTL(X, Y, lambda = 1e-3, K = K, disjoint = FALSE)
#'
#' X_new <- matrix(rnorm(20 * p), 20, p)
#' colnames(X_new) <- paste0("V", seq_len(p))
#' preds <- predict(fit, newdata = X_new)
#' dim(preds)
predict.orthoMTL <- function(object, newdata,
type = c("link", "response", "class"), ...) {
type <- match.arg(type)
is_logistic <- isTRUE(object$hyperparameters$logistic)
if (type == "class" && !is_logistic) {
stop("type = \"class\" is only available for logistic ",
"(classification) fits.", call. = FALSE)
}
# --- Column alignment ---
if (!is.null(object$feature_names)) {
if (is.null(colnames(newdata))) {
stop("'newdata' must have column names when the fitted model ",
"contains feature_names.", call. = FALSE)
}
expected <- object$feature_names
provided <- colnames(newdata)
# Error on missing features
missing_feats <- setdiff(expected, provided)
if (length(missing_feats) > 0) {
stop("The following features required by the model are missing ",
"from 'newdata': ",
paste(missing_feats, collapse = ", "), call. = FALSE)
}
# Warn on extra features
extra_feats <- setdiff(provided, expected)
if (length(extra_feats) > 0) {
warning("The following features in 'newdata' are not used by the ",
"model and will be ignored: ",
paste(extra_feats, collapse = ", "), call. = FALSE)
}
# Align and subset
newdata <- newdata[, expected, drop = FALSE]
} else {
# No feature names — fall back to dimension check
if (ncol(newdata) != object$n_features) {
stop("'newdata' has ", ncol(newdata), " columns but the model ",
"expects ", object$n_features, ".", call. = FALSE)
}
}
# --- Prediction ---
B <- object$B
Mt <- newdata %*% B
# --- Survival monotonicity projection ---
# TODO(v1.1): Monotonicity projection is applied here in predict() but
# not during training loss computation. This asymmetry means the model
# is trained on raw scores but evaluated on projected scores.
# Investigate impact on SOLAR-1 results and simulated vignette.
if (isTRUE(object$hyperparameters$survival)) {
Mt <- t(apply(Mt, 1, nnmaxheap_C))
}
# --- Response-scale / class transforms (logistic fits only) ---
if (is_logistic && type == "response") {
Mt <- 1 / (1 + exp(-Mt))
} else if (type == "class") {
# sign(0) is 0; map the boundary to the positive class for a clean
# {-1, +1} output.
Mt <- ifelse(Mt >= 0, 1, -1)
}
# --- Label output ---
if (!is.null(object$task_names)) {
colnames(Mt) <- object$task_names
}
if (!is.null(rownames(newdata))) {
rownames(Mt) <- rownames(newdata)
}
return(Mt)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.