diff --git a/DESCRIPTION b/DESCRIPTION index 85dc9bd59..288964041 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -101,7 +101,7 @@ Suggests: sm, stats, survival, - survivalmodels, + survivalmodels (>= 0.1.19), survivalsvm, tensorflow (>= 2.0.0), testthat, @@ -122,5 +122,5 @@ Config/testthat/edition: 3 Encoding: UTF-8 NeedsCompilation: no Roxygen: list(markdown = TRUE, r6 = TRUE) -RoxygenNote: 7.2.3.9000 +RoxygenNote: 7.3.1.9000 Config/Needs/website: rmarkdown diff --git a/NEWS.md b/NEWS.md index 871ee2255..1799b8e71 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # mlr3extralearners 0.7.1-9000 +* Ported `surv.parametric` code to `survivalmodels`, changed `type` parameter to `form` to avoid conflict with survivalmodels's default parameter list * Fix: Replace hardcoded `VectorDistribution`s from partykit and flexsurv survival learners with survival matrices (`Matdist`) (thanks to @bblodfon) * Feat: Add `discrete` parameter in `surv.parametric` learner to return `Matdist` survival predictions * Added method `selected_features()` to CoxBoost survival learners (thanks to @bblodfon) diff --git a/R/learner_survival_surv_parametric.R b/R/learner_survival_surv_parametric.R index c4db4f486..72b9d8ee7 100644 --- a/R/learner_survival_surv_parametric.R +++ b/R/learner_survival_surv_parametric.R @@ -1,54 +1,66 @@ #' @title Survival Fully Parametric Learner #' @name mlr_learners_surv.parametric -#' @author RaphaelS1 +#' @author bblodfon #' #' @description #' Parametric survival model. -#' Calls [survival::survreg()] from \CRANpkg{survival}. +#' Calls [survivalmodels::parametric()] from 'survivalmodels'. #' #' @section Custom mlr3 parameters: #' - `discrete` determines the class of the returned survival probability #' distribution. If `FALSE` (default) continuous probability #' distributions are returned using [distr6::VectorDistribution], otherwise -#' [distr6::Matdist]. +#' [distr6::Matdist] (faster to calculate survival measures that require a +#' `distr` prediction type). #' #' @template learner #' @templateVar id surv.parametric +#' @template install_survivalmodels #' #' @details -#' This learner allows you to choose a distribution and a model form to compose a predicted -#' survival probability distribution. +#' This learner allows you to choose a distribution and a model form to compose +#' a predicted survival probability distribution. #' -#' The internal predict method is implemented in this package as our implementation is more -#' efficient for composition to distributions than [survival::predict.survreg()]. +#' The predict method is implemented in [survivalmodels::predict.parametric()]. +#' Our implementation is more efficient for composition to distributions than +#' [survival::predict.survreg()]. #' -#' `lp` is predicted using the formula \eqn{lp = X\beta} where \eqn{X} are the variables in the test -#' data set and \eqn{\beta} are the fitted coefficients. +#' Three types of prediction are returned for this learner: +#' 1. `lp`: a vector of linear predictors (relative risk scores), one per test +#' observation. +#' `lp` is predicted using the formula \eqn{lp = X\beta} where \eqn{X} are the +#' variables in the test data set and \eqn{\beta} are the fitted coefficients. +#' 2. `crank`: same as `lp`. +#' 3. `distr`: a survival matrix in two dimensions, where observations are +#' represented in rows and time points in columns. +#' The distribution `distr` is composed using the `lp` predictions and specifying +#' a model form in the `form` hyper-parameter. These are as follows, with respective +#' survival functions: #' -#' The distribution `distr` is composed using the `lp` and specifying a model form in the -#' `type` hyper-parameter. These are as follows, with respective survival functions, -#' * Accelerated Failure Time (`aft`) \deqn{S(t) = S_0(\frac{t}{exp(lp)})}{S(t) = S0(t/exp(lp))} -#' * Proportional Hazards (`ph`) \deqn{S(t) = S_0(t)^{exp(lp)}}{S(t) = S0(t)^exp(lp)} -#' * Proportional Odds (`po`) \deqn{S(t) = +#' - Accelerated Failure Time (`aft`) \deqn{S(t) = S_0(\frac{t}{exp(lp)})}{S(t) = S0(t/exp(lp))} +#' - Proportional Hazards (`ph`) \deqn{S(t) = S_0(t)^{exp(lp)}}{S(t) = S0(t)^exp(lp)} +#' - Proportional Odds (`po`) \deqn{S(t) = #' \frac{S_0(t)}{exp(-lp) + (1-exp(-lp)) S_0(t)}}{S(t) = S0(t) / [exp(-lp) + S0(t) (1-exp(-lp))]} -#' * Tobit (`tobit`) \deqn{S(t) = 1 - F((t - lp)/s)} +#' - Tobit (`tobit`) \deqn{S(t) = 1 - \Phi((t - lp)/s)} #' -#' where \eqn{S_0}{S0} is the estimated baseline survival distribution (in this case -#' with a given parametric form), \eqn{lp} is the predicted linear predictor, \eqn{F} is the cdf -#' of a N(0, 1) distribution, and \eqn{s} is the fitted scale parameter. +#' where \eqn{S_0}{S0} is the estimated baseline survival distribution (in +#' this case with a given parametric form), \eqn{lp} is the predicted linear +#' predictor, \eqn{\Phi} is the cdf of a N(0, 1) distribution, and \eqn{s} is +#' the fitted scale parameter. #' -#' Whilst any combination of distribution and model form is possible, this does not mean it will -#' necessarily create a sensible or interpretable prediction. The following combinations are -#' 'sensible': +#' Whilst any combination of distribution and model form is possible, this does +#' not mean it will necessarily create a sensible or interpretable prediction. +#' The following combinations are 'sensible' (we note that ones mostly used in +#' the literature): #' -#' * dist = "gaussian"; type = "tobit"; -#' * dist = "weibull"; type = "ph"; -#' * dist = "exponential"; type = "ph"; -#' * dist = "weibull"; type = "aft"; -#' * dist = "exponential"; type = "aft"; -#' * dist = "loglogistic"; type = "aft"; -#' * dist = "lognormal"; type = "aft"; -#' * dist = "loglogistic"; type = "po"; +#' - dist = "gaussian"; form = "tobit"; +#' - dist = "weibull"; form = "ph"; (fairly used) +#' - dist = "exponential"; form = "ph"; +#' - dist = "weibull"; form = "aft"; (fairly used, **default option**) +#' - dist = "exponential"; form = "aft"; +#' - dist = "loglogistic"; form = "aft"; (fairly used) +#' - dist = "lognormal"; form = "aft"; +#' - dist = "loglogistic"; form = "po"; #' #' @references #' `r format_bib("kalbfleisch2011statistical")` @@ -63,12 +75,11 @@ LearnerSurvParametric = R6Class("LearnerSurvParametric", #' Creates a new instance of this [R6][R6::R6Class] class. initialize = function() { ps = ps( - type = p_fct(default = "aft", levels = c("aft", "ph", "po", "tobit"), - tags = "predict"), + form = p_fct(default = "aft", levels = c("aft", "ph", "po", "tobit"), + tags = "predict"), na.action = p_uty(tags = "train"), - dist = p_fct(default = "weibull", - levels = c("weibull", "exponential", "gaussian", - "lognormal", "loglogistic"), tags = "train"), + dist = p_fct(default = "weibull", levels = c("weibull", "exponential", # nolint + "gaussian", "lognormal", "loglogistic"), tags = "train"),# nolint parms = p_uty(tags = "train"), init = p_uty(tags = "train"), scale = p_dbl(default = 0, lower = 0, tags = "train"), @@ -83,7 +94,7 @@ LearnerSurvParametric = R6Class("LearnerSurvParametric", discrete = p_lgl(tags = c("required", "predict")) ) - ps$values = list(discrete = FALSE, dist = "weibull", type = "aft") + ps$values = list(discrete = FALSE, dist = "weibull", form = "aft") super$initialize( id = "surv.parametric", @@ -100,256 +111,40 @@ LearnerSurvParametric = R6Class("LearnerSurvParametric", private = list( .train = function(task) { - pv = self$param_set$get_values(tags = "train") if ("weights" %in% task$properties) { pv$weights = task$weights$weight } - fit = invoke(survival::survreg, formula = task$formula(), data = task$data(), - .args = pv) - - # Fits the baseline distribution by reparameterising the fitted coefficients. - # These were mostly derived numerically as precise documentation on the parameterisations is - # hard to find. - location = as.numeric(fit$coefficients[1]) - scale = fit$scale - eps = 1e-15 - - if (scale < eps) { - scale = eps - } else if (scale > .Machine$double.xmax) { - scale = .Machine$double.xmax - } - - if (location < -709 & fit$dist %in% c("weibull", "exponential", "loglogistic")) { - location = -709 - } - - basedist = switch(fit$dist, - "weibull" = distr6::Weibull$new(shape = 1 / scale, scale = exp(location), - decorators = "ExoticStatistics"), - "exponential" = distr6::Exponential$new(scale = exp(location), - decorators = "ExoticStatistics"), - "gaussian" = distr6::Normal$new(mean = location, sd = scale, - decorators = "ExoticStatistics"), - "lognormal" = distr6::Lognormal$new(meanlog = location, sdlog = scale, - decorators = "ExoticStatistics"), - "loglogistic" = distr6::Loglogistic$new(scale = exp(location), - shape = 1 / scale, - decorators = "ExoticStatistics") + invoke( + survivalmodels::parametric, + data = data.table::setDF(task$data()), + time_variable = task$target_names[1L], + status_variable = task$target_names[2L], + .args = pv ) - - set_class(list(fit = fit, basedist = basedist), "surv.parametric") }, .predict = function(task) { pv = self$param_set$get_values(tags = "predict") - if (pv$discrete) { - pred = invoke(.predict_survreg_discrete, object = self$model, task = task, - learner = self, type = pv$type) - } else { - pred = invoke(.predict_survreg_continuous, object = self$model, task = task, - learner = self, type = pv$type) - } - # lp is aft-style, where higher value = lower risk, opposite needed for crank - list(distr = pred$distr, crank = -pred$lp, lp = -pred$lp) - } - ) -) - - -.predict_survreg_continuous = function(object, task, learner, type = "aft") { - feature_names = intersect(names(learner$state$data_prototype) %??% learner$state$feature_names, task$feature_names) - # Extracts baseline distribution and the model fit, performs assertions - basedist = object$basedist - fit = object$fit - distr6::assertDistribution(basedist) - assertClass(fit, "survreg") - - # define newdata from the supplied task and convert to model matrix - newdata = ordered_features(task, learner) - if (any(is.na(newdata))) { - stopf("Learner %s on task %s failed to predict: Missing values in new data (line(s) %s)\n", learner$id, task$id) - } - x = stats::model.matrix(formulate(rhs = feature_names), data = newdata, - xlev = task$levels())[, -1] - - # linear predictor defined by the fitted cofficients multiplied by the model matrix - # (i.e. covariates) - lp = matrix(fit$coefficients[-1], nrow = 1) %*% t(x) - - # checks and parameterises the chosen model type: proportional hazard (ph), accelerated failure - # time (aft), odds. - # PH: h(t) = h0(t)exp(lp) - # AFT: h(t) = exp(-lp)h0(t/exp(lp)) - # PO: h(t)/h0(t) = {1 + (exp(lp)-1)S0(t)}^-1 - - dist = toproper(fit$dist) - - if (type == "tobit") { - name = paste(dist, "Tobit Model") - short_name = paste0(dist, "Tobit") - description = paste(dist, "Tobit Model with negative log-likelihood", - -fit$loglik[2]) - } else if (type == "ph") { - name = paste(dist, "Proportional Hazards Model") - short_name = paste0(dist, "PH") - description = paste(dist, "Proportional Hazards Model with negative log-likelihood", - -fit$loglik[2]) - } else if (type == "aft") { - name = paste(dist, "Accelerated Failure Time Model") - short_name = paste0(dist, "AFT") - description = paste(dist, "Accelerated Failure Time Model with negative log-likelihood", - -fit$loglik[2]) - } else if (type == "po") { - name = paste(dist, "Proportional Odds Model") - short_name = paste0(dist, "PO") - description = paste(dist, "Proportional Odds Model with negative log-likelihood", - -fit$loglik[2]) - } - - params = list(list(name = name, - short_name = short_name, - type = set6::PosReals$new(), - support = set6::PosReals$new(), - valueSupport = "continuous", - variateForm = "univariate", - description = description, - .suppressChecks = TRUE, - pdf = function() { - }, - cdf = function() { - }, - parameters = param6::pset() - )) - - params = rep(params, length(lp)) - pdf = function(x) {} # nolint - cdf = function(x) {} # nolint - quantile = function(p) {} # nolint + newdata = as.data.frame(ordered_features(task, self)) - if (type == "tobit") { - for (i in seq_along(lp)) { - body(pdf) = substitute(pnorm((x - y) / scale), list( - y = lp[i] + fit$coefficients[1], - scale = basedist$stdev() - )) - body(cdf) = substitute(pnorm((x - y) / scale), list( - y = lp[i] + fit$coefficients[1], - scale = basedist$stdev() - )) - body(quantile) = substitute(qnorm(p) * scale + y, list( - y = lp[i] + fit$coefficients[1], - scale = basedist$stdev() - )) - params[[i]]$pdf = pdf - params[[i]]$cdf = cdf - params[[i]]$quantile = quantile - } - } else if (type == "ph") { - for (i in seq_along(lp)) { - body(pdf) = substitute((exp(y) * basedist$hazard(x)) * (1 - self$cdf(x)), list(y = -lp[i])) - body(cdf) = substitute(1 - (basedist$survival(x)^exp(y)), list(y = -lp[i])) - body(quantile) = substitute( - basedist$quantile(1 - exp(exp(-y) * log(1 - p))), # nolint - list(y = -lp[i]) + pred = invoke( + predict, + self$model, + newdata = newdata, + distr6 = !pv$discrete, + type = "all", + .args = pv ) - params[[i]]$pdf = pdf - params[[i]]$cdf = cdf - params[[i]]$quantile = quantile - } - } else if (type == "aft") { - for (i in seq_along(lp)) { - body(pdf) = substitute((exp(-y) * basedist$hazard(x / exp(y))) * (1 - self$cdf(x)), - list(y = lp[i])) - body(cdf) = substitute(1 - (basedist$survival(x / exp(y))), list(y = lp[i])) - body(quantile) = substitute(exp(y) * basedist$quantile(p), list(y = lp[i])) - params[[i]]$pdf = pdf - params[[i]]$cdf = cdf - params[[i]]$quantile = quantile - } - } else if (type == "po") { - for (i in seq_along(lp)) { - body(pdf) = substitute((basedist$hazard(x) * - (1 - (basedist$survival(x) / - (((exp(y) - 1)^-1) + basedist$survival(x))))) * - (1 - self$cdf(x)), list(y = lp[i])) - body(cdf) = substitute(1 - (basedist$survival(x) * - (exp(-y) + (1 - exp(-y)) * basedist$survival(x))^-1), # nolint - list(y = lp[i])) - body(quantile) = substitute(basedist$quantile(-p / ((exp(-y) * (p - 1)) - p)), # nolint - list(y = lp[i])) - params[[i]]$pdf = pdf - params[[i]]$cdf = cdf - params[[i]]$quantile = quantile - } - } - - distlist = lapply(params, function(.x) do.call(distr6::Distribution$new, .x)) - names(distlist) = paste0(short_name, seq_along(distlist)) - - distr = distr6::VectorDistribution$new(distlist, - decorators = c("CoreStatistics", "ExoticStatistics")) - - lp = lp + fit$coefficients[1] - list(lp = as.numeric(lp), distr = distr) -} - - -.predict_survreg_discrete = function(object, task, learner, type = "aft") { - feature_names = intersect(names(learner$state$data_prototype), task$feature_names) - - # Extracts baseline distribution and the model fit, performs assertions - basedist = object$basedist - fit = object$fit - distr6::assertDistribution(basedist) - assertClass(fit, "survreg") - - # define newdata from the supplied task and convert to model matrix - newdata = ordered_features(task, learner) - if (any(is.na(newdata))) { - stopf("Learner %s on task %s failed to predict: Missing values in new data (line(s) %s)\n", learner$id, task$id) - } - - times = task$unique_times() - - # PH: h(t) = h0(t)exp(lp) - # AFT: h(t) = exp(-lp)h0(t/exp(lp)) - # PO: h(t)/h0(t) = {1 + (exp(lp)-1)S0(t)}^-1 - if (type == "tobit") { - fun = function(y) stats::pnorm((times - y - fit$coefficients[1]) / basedist$stdev()) - } else if (type == "ph") { - fun = function(y) 1 - (basedist$survival(times)^exp(-y)) - } else if (type == "aft") { - fun = function(y) 1 - (basedist$survival(times / exp(y))) - } else if (type == "po") { - fun = function(y) { - surv = basedist$survival(times) - 1 - (surv * (exp(-y) + (1 - exp(-y)) * surv)^-1) + #' returned `risk` from survivalmodels is hp-style + #' ie higher value => higher risk + list(crank = pred$risk, lp = pred$risk, distr = pred$surv) } - } - - # linear predictor defined by the fitted cofficients multiplied by the model matrix - # (i.e. covariates) - x = stats::model.matrix(mlr3misc::formulate(rhs = feature_names), data = newdata, - xlev = task$levels())[, -1] - lp = matrix(fit$coefficients[-1], nrow = 1) %*% t(x) - - if (length(times) == 1) { # edge case - mat = as.matrix(vapply(lp, fun, numeric(1)), ncol = 1) - } else { - mat = t(vapply(lp, fun, numeric(length(times)))) - } - colnames(mat) = times - - list( - lp = as.numeric(lp + fit$coefficients[1]), - distr = distr6::as.Distribution(mat, fun = "cdf") ) -} +) .extralrns_dict$add("surv.parametric", LearnerSurvParametric) diff --git a/man/mlr_learners_surv.parametric.Rd b/man/mlr_learners_surv.parametric.Rd index b5b422a5d..3c8bde511 100644 --- a/man/mlr_learners_surv.parametric.Rd +++ b/man/mlr_learners_surv.parametric.Rd @@ -6,44 +6,55 @@ \title{Survival Fully Parametric Learner} \description{ Parametric survival model. -Calls \code{\link[survival:survreg]{survival::survreg()}} from \CRANpkg{survival}. +Calls \code{\link[survivalmodels:parametric]{survivalmodels::parametric()}} from 'survivalmodels'. } \details{ -This learner allows you to choose a distribution and a model form to compose a predicted -survival probability distribution. - -The internal predict method is implemented in this package as our implementation is more -efficient for composition to distributions than \code{\link[survival:predict.survreg]{survival::predict.survreg()}}. - -\code{lp} is predicted using the formula \eqn{lp = X\beta} where \eqn{X} are the variables in the test -data set and \eqn{\beta} are the fitted coefficients. - -The distribution \code{distr} is composed using the \code{lp} and specifying a model form in the -\code{type} hyper-parameter. These are as follows, with respective survival functions, +This learner allows you to choose a distribution and a model form to compose +a predicted survival probability distribution. + +The predict method is implemented in \code{\link[survivalmodels:predict.parametric]{survivalmodels::predict.parametric()}}. +Our implementation is more efficient for composition to distributions than +\code{\link[survival:predict.survreg]{survival::predict.survreg()}}. + +Three types of prediction are returned for this learner: +\enumerate{ +\item \code{lp}: a vector of linear predictors (relative risk scores), one per test +observation. +\code{lp} is predicted using the formula \eqn{lp = X\beta} where \eqn{X} are the +variables in the test data set and \eqn{\beta} are the fitted coefficients. +\item \code{crank}: same as \code{lp}. +\item \code{distr}: a survival matrix in two dimensions, where observations are +represented in rows and time points in columns. +The distribution \code{distr} is composed using the \code{lp} predictions and specifying +a model form in the \code{form} hyper-parameter. These are as follows, with respective +survival functions: +} \itemize{ \item Accelerated Failure Time (\code{aft}) \deqn{S(t) = S_0(\frac{t}{exp(lp)})}{S(t) = S0(t/exp(lp))} \item Proportional Hazards (\code{ph}) \deqn{S(t) = S_0(t)^{exp(lp)}}{S(t) = S0(t)^exp(lp)} \item Proportional Odds (\code{po}) \deqn{S(t) = \frac{S_0(t)}{exp(-lp) + (1-exp(-lp)) S_0(t)}}{S(t) = S0(t) / [exp(-lp) + S0(t) (1-exp(-lp))]} -\item Tobit (\code{tobit}) \deqn{S(t) = 1 - F((t - lp)/s)} +\item Tobit (\code{tobit}) \deqn{S(t) = 1 - \Phi((t - lp)/s)} } -where \eqn{S_0}{S0} is the estimated baseline survival distribution (in this case -with a given parametric form), \eqn{lp} is the predicted linear predictor, \eqn{F} is the cdf -of a N(0, 1) distribution, and \eqn{s} is the fitted scale parameter. +where \eqn{S_0}{S0} is the estimated baseline survival distribution (in +this case with a given parametric form), \eqn{lp} is the predicted linear +predictor, \eqn{\Phi} is the cdf of a N(0, 1) distribution, and \eqn{s} is +the fitted scale parameter. -Whilst any combination of distribution and model form is possible, this does not mean it will -necessarily create a sensible or interpretable prediction. The following combinations are -'sensible': +Whilst any combination of distribution and model form is possible, this does +not mean it will necessarily create a sensible or interpretable prediction. +The following combinations are 'sensible' (we note that ones mostly used in +the literature): \itemize{ -\item dist = "gaussian"; type = "tobit"; -\item dist = "weibull"; type = "ph"; -\item dist = "exponential"; type = "ph"; -\item dist = "weibull"; type = "aft"; -\item dist = "exponential"; type = "aft"; -\item dist = "loglogistic"; type = "aft"; -\item dist = "lognormal"; type = "aft"; -\item dist = "loglogistic"; type = "po"; +\item dist = "gaussian"; form = "tobit"; +\item dist = "weibull"; form = "ph"; (fairly used) +\item dist = "exponential"; form = "ph"; +\item dist = "weibull"; form = "aft"; (fairly used, \strong{default option}) +\item dist = "exponential"; form = "aft"; +\item dist = "loglogistic"; form = "aft"; (fairly used) +\item dist = "lognormal"; form = "aft"; +\item dist = "loglogistic"; form = "po"; } } \section{Custom mlr3 parameters}{ @@ -52,7 +63,8 @@ necessarily create a sensible or interpretable prediction. The following combina \item \code{discrete} determines the class of the returned survival probability distribution. If \code{FALSE} (default) continuous probability distributions are returned using \link[distr6:VectorDistribution]{distr6::VectorDistribution}, otherwise -\link[distr6:Matdist]{distr6::Matdist}. +\link[distr6:Matdist]{distr6::Matdist} (faster to calculate survival measures that require a +\code{distr} prediction type). } } @@ -78,7 +90,7 @@ lrn("surv.parametric") \section{Parameters}{ \tabular{lllll}{ Id \tab Type \tab Default \tab Levels \tab Range \cr - type \tab character \tab aft \tab aft, ph, po, tobit \tab - \cr + form \tab character \tab aft \tab aft, ph, po, tobit \tab - \cr na.action \tab untyped \tab - \tab \tab - \cr dist \tab character \tab weibull \tab weibull, exponential, gaussian, lognormal, loglogistic \tab - \cr parms \tab untyped \tab - \tab \tab - \cr @@ -92,10 +104,16 @@ lrn("surv.parametric") robust \tab logical \tab FALSE \tab TRUE, FALSE \tab - \cr score \tab logical \tab FALSE \tab TRUE, FALSE \tab - \cr cluster \tab untyped \tab - \tab \tab - \cr - discrete \tab logical \tab FALSE \tab TRUE, FALSE \tab - \cr + discrete \tab logical \tab - \tab TRUE, FALSE \tab - \cr } } +\section{Installation}{ + +Package 'survivalmodels' is not on CRAN and has to be install from GitHub via +\code{remotes::install_github("RaphaelS1/survivalmodels")}. +} + \examples{ learner = mlr3::lrn("surv.parametric") print(learner) @@ -120,7 +138,7 @@ John Wiley & Sons. } } \author{ -RaphaelS1 +bblodfon } \section{Super classes}{ \code{\link[mlr3:Learner]{mlr3::Learner}} -> \code{\link[mlr3proba:LearnerSurv]{mlr3proba::LearnerSurv}} -> \code{LearnerSurvParametric} @@ -151,6 +169,8 @@ RaphaelS1 \if{latex}{\out{\hypertarget{method-LearnerSurvParametric-new}{}}} \subsection{Method \code{new()}}{ Creates a new instance of this \link[R6:R6Class]{R6} class. +returned \code{risk} from survivalmodels is hp-style +i.e. higher value => higher risk \subsection{Usage}{ \if{html}{\out{
}}\preformatted{LearnerSurvParametric$new()}\if{html}{\out{
}} } diff --git a/tests/testthat/test_paramtest_survival_surv_parametric.R b/tests/testthat/test_paramtest_survival_surv_parametric.R index 3cce938f6..b430d12f7 100644 --- a/tests/testthat/test_paramtest_survival_surv_parametric.R +++ b/tests/testthat/test_paramtest_survival_surv_parametric.R @@ -1,32 +1,24 @@ test_that("paramtest surv.parametric train", { learner = lrn("surv.parametric") - fun = survival::survreg + fun = list(survivalmodels::parametric, survival::survreg) exclude = c( - "maxiter", - # control parameter - "rel.tolerance", - # control parameter - "toler.chol", - # control parameter - "debug", - # control parameter - "outer.max", - # control parameter - "control", - # control parameter - "weights", - # handled in mlr3 - "subset", - # handled in mlr3 - "x", - # x,y,data are all coerced to formula internally - "y", - # x,y,data are all coerced to formula internally - "model", - # model always returned - "data", - # x,y,data are all coerced to formula internally - "formula" # x,y,data are all coerced to formula internally + "maxiter", # control parameter + "rel.tolerance", # control parameter + "toler.chol", # control parameter + "debug", # control parameter + "outer.max", # control parameter + "control", # control parameter + "weights", # handled in mlr3 + "subset", # handled in mlr3 + "x", # x,y,data are all coerced to formula internally + "y", # x,y,data are all coerced to formula internally + "model", # model always returned + "data", # x,y,data are all coerced to formula internally + "formula", # x,y,data are all coerced to formula internally + "reverse", # handled in mlr3 + "time_variable", # handled in mlr3 + "status_variable", # handled in mlr3 + "eps" # handled in mlr3 ) paramtest = run_paramtest(learner, fun, exclude, tag = "train") @@ -35,15 +27,16 @@ test_that("paramtest surv.parametric train", { test_that("paramtest surv.parametric predict", { learner = lrn("surv.parametric") - fun = list(.predict_survreg_continuous, .predict_survreg_discrete) + fun = survivalmodels:::predict.parametric exclude = c( - # handled via mlr3 - "type", - "object", - "task", - "feature_names", - "tobit", - "learner", + "object", # handled internally + "newdata", # handled internally + "type", # handled internally + "distr6", # handled internally + "form", # handled internally + "times", # handled internally + "ntime", # handled internally + "round_time", # handled internally "discrete" ) diff --git a/tests/testthat/test_survival_surv_parametric.R b/tests/testthat/test_survival_surv_parametric.R index c45fc928b..570e8d38f 100644 --- a/tests/testthat/test_survival_surv_parametric.R +++ b/tests/testthat/test_survival_surv_parametric.R @@ -2,23 +2,23 @@ test_that("autotest aft", { set.seed(1) learner = lrn("surv.parametric") expect_learner(learner) - result = run_autotest(learner, check_replicable = FALSE) + result = run_autotest(learner, check_replicable = FALSE, exclude = "utf8_feature_names") expect_true(result, info = result$error) }) test_that("autotest ph", { set.seed(1) - learner = lrn("surv.parametric", type = "ph") + learner = lrn("surv.parametric", form = "ph", discrete = TRUE) expect_learner(learner) - result = run_autotest(learner, check_replicable = FALSE) + result = run_autotest(learner, check_replicable = FALSE, exclude = "utf8_feature_names") expect_true(result, info = result$error) }) test_that("autotest po", { set.seed(1) - learner = lrn("surv.parametric", type = "po") + learner = lrn("surv.parametric", form = "po") expect_learner(learner) - result = run_autotest(learner, check_replicable = FALSE) + result = run_autotest(learner, check_replicable = FALSE, exclude = "utf8_feature_names") expect_true(result, info = result$error) }) @@ -31,27 +31,26 @@ task = TaskSurv$new("param", event = "status") test_that("manualtest - aft", { - learner = lrn("surv.parametric", dist = "weibull", type = "aft") + learner = lrn("surv.parametric", dist = "weibull", form = "aft") expect_silent(learner$train(task)) p = learner$predict(task) expect_prediction_surv(p) - expect_equal(-p$lp, predict(learner$model$fit, type = "lp")) + expect_equal(-p$lp, predict(learner$model$model, type = "lp")) expect_equal(p$distr[1]$survival(predict( - learner$model$fit, type = "quantile", p = c(0.2, 0.8) + learner$model$model, type = "quantile", p = c(0.2, 0.8) )[1, ]), c(0.8, 0.2)) expect_equal(p$distr[10]$cdf(predict( - learner$model$fit, type = "quantile", p = seq.int(0, 1, 0.1) + learner$model$model, type = "quantile", p = seq.int(0, 1, 0.1) )[10, ]), seq.int(0, 1, 0.1)) - learner = lrn("surv.parametric", dist = "lognormal", type = "aft")$train(task) + learner = lrn("surv.parametric", dist = "lognormal", form = "aft")$train(task) p = learner$predict(task) expect_equal(p$distr[15]$cdf(predict( - learner$model$fit, type = "quantile", p = seq.int(0, 1, 0.1) + learner$model$model, type = "quantile", p = seq.int(0, 1, 0.1) )[15, ]), seq.int(0, 1, 0.1)) }) - test_that("missing", { learner = lrn("surv.parametric") learner$train(task) @@ -59,73 +58,73 @@ test_that("missing", { }) test_that("quantile type", { - learner = lrn("surv.parametric", dist = "weibull", type = "aft")$train(task) - p = lrn("surv.parametric", dist = "weibull", type = "aft")$train(task)$predict(task) + learner = lrn("surv.parametric", dist = "weibull", form = "aft")$train(task) + p = lrn("surv.parametric", dist = "weibull", form = "aft")$train(task)$predict(task) quantile = p$distr$quantile(c(0.2, 0.8)) expect_equal(matrix(t(quantile), ncol = 2), - predict(learner$model$fit, type = "quantile", p = c(0.2, 0.8))) + predict(learner$model$model, type = "quantile", p = c(0.2, 0.8))) quantile = p$distr$quantile(0.5) expect_equal(unlist(p$distr$cdf(quantile), use.names = FALSE), rep(0.5, 227)) - p = lrn("surv.parametric", dist = "weibull", type = "ph")$train(task)$predict(task) + p = lrn("surv.parametric", dist = "weibull", form = "ph")$train(task)$predict(task) quantile = p$distr$quantile(0.5) expect_equal(unlist(p$distr$cdf(quantile), use.names = FALSE), rep(0.5, 227)) - p = lrn("surv.parametric", dist = "weibull", type = "po")$train(task)$predict(task) + p = lrn("surv.parametric", dist = "weibull", form = "po")$train(task)$predict(task) quantile = p$distr$quantile(0.5) expect_equal(unlist(p$distr$cdf(quantile), use.names = FALSE), rep(0.5, 227)) }) test_that("quantile dist", { - learner = lrn("surv.parametric", dist = "weibull", type = "aft")$train(task) + learner = lrn("surv.parametric", dist = "weibull", form = "aft")$train(task) p = learner$predict(task) quantile = p$distr$quantile(c(0.2, 0.8)) expect_equal(matrix(t(quantile), ncol = 2), - predict(learner$model$fit, type = "quantile", p = c(0.2, 0.8))) + predict(learner$model$model, type = "quantile", p = c(0.2, 0.8))) - learner = lrn("surv.parametric", dist = "exponential", type = "aft")$train(task) + learner = lrn("surv.parametric", dist = "exponential", form = "aft")$train(task) p = learner$predict(task) quantile = p$distr$quantile(c(0.2, 0.8)) expect_equal(matrix(t(quantile), ncol = 2), - predict(learner$model$fit, type = "quantile", p = c(0.2, 0.8))) + predict(learner$model$model, type = "quantile", p = c(0.2, 0.8))) - learner = lrn("surv.parametric", dist = "gaussian", type = "tobit")$train(task) + learner = lrn("surv.parametric", dist = "gaussian", form = "tobit")$train(task) p = learner$predict(task) quantile = p$distr$quantile(c(0.2, 0.8)) expect_equal(matrix(t(quantile), ncol = 2), - predict(learner$model$fit, type = "quantile", p = c(0.2, 0.8))) + predict(learner$model$model, type = "quantile", p = c(0.2, 0.8))) learner = lrn("surv.parametric", dist = "lognormal")$train(task) p = learner$predict(task) quantile = p$distr$quantile(c(0.2, 0.8)) expect_equal(matrix(t(quantile), ncol = 2), - predict(learner$model$fit, type = "quantile", p = c(0.2, 0.8))) + predict(learner$model$model, type = "quantile", p = c(0.2, 0.8))) }) test_that("cdf dist", { - learner = lrn("surv.parametric", dist = "weibull", type = "aft")$train(task) + learner = lrn("surv.parametric", dist = "weibull", form = "aft")$train(task) p = learner$predict(task, row_ids = 151:200) - cdf = predict(learner$model$fit, type = "quantile", p = c(0.2, 0.8))[151:200, ] + cdf = predict(learner$model$model, type = "quantile", p = c(0.2, 0.8))[151:200, ] expect_equal(unname(as.matrix(p$distr$cdf(data = t(cdf)))), - matrix(c(rep(0.2, 50), rep(0.8, 50)), byrow = TRUE, nrow = 2)) + matrix(c(rep(0.2, 50), rep(0.8, 50)), byrow = TRUE, nrow = 2)) - learner = lrn("surv.parametric", dist = "exponential", type = "aft")$train(task) + learner = lrn("surv.parametric", dist = "exponential", form = "aft")$train(task) p = learner$predict(task, row_ids = 151:200) - cdf = predict(learner$model$fit, type = "quantile", p = c(0.2, 0.8))[151:200, ] + cdf = predict(learner$model$model, type = "quantile", p = c(0.2, 0.8))[151:200, ] expect_equal(unname(as.matrix(p$distr$cdf(data = t(cdf)))), - matrix(c(rep(0.2, 50), rep(0.8, 50)), byrow = TRUE, nrow = 2)) + matrix(c(rep(0.2, 50), rep(0.8, 50)), byrow = TRUE, nrow = 2)) - learner = lrn("surv.parametric", dist = "gaussian", type = "tobit")$train(task) + learner = lrn("surv.parametric", dist = "gaussian", form = "tobit")$train(task) p = learner$predict(task, row_ids = 151:200) - cdf = predict(learner$model$fit, type = "quantile", p = c(0.2, 0.8))[151:200, ] + cdf = predict(learner$model$model, type = "quantile", p = c(0.2, 0.8))[151:200, ] expect_equal(unname(as.matrix(p$distr$cdf(data = t(cdf)))), - matrix(c(rep(0.2, 50), rep(0.8, 50)), byrow = TRUE, nrow = 2)) + matrix(c(rep(0.2, 50), rep(0.8, 50)), byrow = TRUE, nrow = 2)) learner = lrn("surv.parametric", dist = "lognormal")$train(task) p = learner$predict(task, row_ids = 151:200) - cdf = predict(learner$model$fit, type = "quantile", p = c(0.2, 0.8))[151:200, ] + cdf = predict(learner$model$model, type = "quantile", p = c(0.2, 0.8))[151:200, ] expect_equal(unname(as.matrix(p$distr$cdf(data = t(cdf)))), - matrix(c(rep(0.2, 50), rep(0.8, 50)), byrow = TRUE, nrow = 2)) + matrix(c(rep(0.2, 50), rep(0.8, 50)), byrow = TRUE, nrow = 2)) }) test_that("loglogistic", { @@ -135,14 +134,14 @@ test_that("loglogistic", { p = learner$predict(task) quantile = p$distr$quantile(c(0.2, 0.8)) expect_equal(matrix(t(quantile), ncol = 2), - predict(learner$model$fit, type = "quantile", p = c(0.2, 0.8))) + predict(learner$model$model, type = "quantile", p = c(0.2, 0.8))) learner = lrn("surv.parametric", dist = "loglogistic")$train(task) p = learner$predict(task, row_ids = 151:200) - cdf = predict(learner$model$fit, type = "quantile", p = c(0.2, 0.8))[151:200, ] + cdf = predict(learner$model$model, type = "quantile", p = c(0.2, 0.8))[151:200, ] expect_equal(unname(as.matrix(p$distr$cdf(data = t(cdf)))), - matrix(c(rep(0.2, 50), rep(0.8, 50)), byrow = TRUE, nrow = 2)) + matrix(c(rep(0.2, 50), rep(0.8, 50)), byrow = TRUE, nrow = 2)) }) task = tsk("rats") @@ -160,8 +159,8 @@ test_that("discrete - aft", { }) test_that("discrete - tobit", { - p_cont = lrn("surv.parametric", discrete = FALSE, type = "tobit")$train(task)$predict(task) - p_disc = lrn("surv.parametric", discrete = TRUE, type = "tobit")$train(task)$predict(task) + p_cont = lrn("surv.parametric", discrete = FALSE, form = "tobit")$train(task)$predict(task) + p_disc = lrn("surv.parametric", discrete = TRUE, form = "tobit")$train(task)$predict(task) expect_equal(p_cont$lp, p_disc$lp) expect_true(inherits(p_disc$distr, "Matdist")) s_cont = as.matrix(p_cont$distr$survival(task$unique_times())) @@ -171,8 +170,8 @@ test_that("discrete - tobit", { }) test_that("discrete - ph", { - p_cont = lrn("surv.parametric", discrete = FALSE, type = "ph")$train(task)$predict(task) - p_disc = lrn("surv.parametric", discrete = TRUE, type = "ph")$train(task)$predict(task) + p_cont = lrn("surv.parametric", discrete = FALSE, form = "ph")$train(task)$predict(task) + p_disc = lrn("surv.parametric", discrete = TRUE, form = "ph")$train(task)$predict(task) expect_equal(p_cont$lp, p_disc$lp) expect_true(inherits(p_disc$distr, "Matdist")) s_cont = as.matrix(p_cont$distr$survival(task$unique_times())) @@ -182,8 +181,8 @@ test_that("discrete - ph", { }) test_that("discrete - po", { - p_cont = lrn("surv.parametric", discrete = FALSE, type = "po")$train(task)$predict(task) - p_disc = lrn("surv.parametric", discrete = TRUE, type = "po")$train(task)$predict(task) + p_cont = lrn("surv.parametric", discrete = FALSE, form = "po")$train(task)$predict(task) + p_disc = lrn("surv.parametric", discrete = TRUE, form = "po")$train(task)$predict(task) expect_equal(p_cont$lp, p_disc$lp) expect_true(inherits(p_disc$distr, "Matdist")) s_cont = as.matrix(p_cont$distr$survival(task$unique_times()))