Title: | Create 'shiny' Applications for Cox Proportional Hazards Models |
---|---|
Description: | Takes one or more fitted Cox proportional hazards models and writes a 'shiny' application to a directory specified by the user. The 'shiny' application displays predicted survival curves based on user input, and contains none of the original data used to create the Cox model or models. The goal is towards visualization and presentation of predicted survival curves. |
Authors: | Harrison Clement [aut, cre], Subodh Selukar [aut], Stanley Pounds [aut], St Jude Children's Research Hospital [fnd] |
Maintainer: | Harrison Clement <[email protected]> |
License: | LGPL (>= 3) |
Version: | 1.1.3 |
Built: | 2025-03-03 04:33:47 UTC |
Source: | https://github.com/harryc598/shinycox |
The main purpose of this function is to be used to create plots within the
shiny app created by shine_coxph()
. For this reason the argument it takes,
KM.hat
, is created through a process delineated in the example. This can
make the function more complicated if you want to use it outside of the shiny
app, although it is fully possible to do so.
cox_KM_plots(KM.hat, clrs = NULL, confint, ylab = "Prob")
cox_KM_plots(KM.hat, clrs = NULL, confint, ylab = "Prob")
KM.hat |
Time and survival probability created by |
clrs |
color of lines |
confint |
logical value to determine if confidence intervals should be plotted |
ylab |
text label for y-axis |
Plot of predicted survival curve(s)
library(survival) # First colon is split into three treatment arms to compare predicted # survival across arms split_colon <- split(colon, colon$rx) colon_arm1 <- split_colon$Obs colon_arm2 <- split_colon$Lev colon_arm3 <- split_colon$`Lev+5FU` # One coxph model is fit for each treatment colon1ph <- coxph(Surv(time, status) ~sex + age + obstruct + nodes, colon_arm1, x = TRUE, model = TRUE) colon2ph <- coxph(Surv(time, status) ~ sex + age + obstruct + nodes, colon_arm2, x = TRUE, model = TRUE) colon3ph <- coxph(Surv(time, status) ~ sex + age + obstruct + nodes, colon_arm3, x = TRUE, model = TRUE) # Creating list of models cox.fit.list <- vector("list", 3) cox.fit.list[[1]] <- prep_coxfit(colon1ph) cox.fit.list[[2]] <- prep_coxfit(colon2ph) cox.fit.list[[3]] <- prep_coxfit(colon3ph) # Creating new data row for predictions new.data <- colon[1, ] # Creating KM.hat object n.models=length(cox.fit.list) KM.hat=vector('list',n.models) lp=rep(NA,n.models) names(KM.hat)=names(cox.fit.list) for (i in 1:n.models) { km.hat=predict_one_coxfit(cox.fit.list[[i]],new.data) lp[i]=attr(km.hat,'lp') sfit=list(time=km.hat$time,surv=km.hat$surv) class(sfit)='survfit' KM.hat[[i]]=sfit } # Plot cox_KM_plots(KM.hat)
library(survival) # First colon is split into three treatment arms to compare predicted # survival across arms split_colon <- split(colon, colon$rx) colon_arm1 <- split_colon$Obs colon_arm2 <- split_colon$Lev colon_arm3 <- split_colon$`Lev+5FU` # One coxph model is fit for each treatment colon1ph <- coxph(Surv(time, status) ~sex + age + obstruct + nodes, colon_arm1, x = TRUE, model = TRUE) colon2ph <- coxph(Surv(time, status) ~ sex + age + obstruct + nodes, colon_arm2, x = TRUE, model = TRUE) colon3ph <- coxph(Surv(time, status) ~ sex + age + obstruct + nodes, colon_arm3, x = TRUE, model = TRUE) # Creating list of models cox.fit.list <- vector("list", 3) cox.fit.list[[1]] <- prep_coxfit(colon1ph) cox.fit.list[[2]] <- prep_coxfit(colon2ph) cox.fit.list[[3]] <- prep_coxfit(colon3ph) # Creating new data row for predictions new.data <- colon[1, ] # Creating KM.hat object n.models=length(cox.fit.list) KM.hat=vector('list',n.models) lp=rep(NA,n.models) names(KM.hat)=names(cox.fit.list) for (i in 1:n.models) { km.hat=predict_one_coxfit(cox.fit.list[[i]],new.data) lp[i]=attr(km.hat,'lp') sfit=list(time=km.hat$time,surv=km.hat$surv) class(sfit)='survfit' KM.hat[[i]]=sfit } # Plot cox_KM_plots(KM.hat)
Generates tables of predicted probabilities at specified time or vector of
times. The KM.hat
object contains time and predicted survival
probability information as a list of survfit
objects.
cox_times_table(KM.hat, fixTimes = NULL)
cox_times_table(KM.hat, fixTimes = NULL)
KM.hat |
List of |
fixTimes |
character or vector of characters representing times for which predicted survival probability is given |
The main purpose of this function is to be used within the shiny app for the purpose of creating predicted probability tables for user-inputted times. For this reason it is not expressly recommended to use this function outside the context of the shiny app, but it is still possible to do so if desired. The time or vector of times are inputted as characters due to the use of this function in the shiny app, where times are inputted as numbers separated by a comma
Table of predicted probabilities, one column for each time, and one row for each curve
library(survival) library(shinyCox) # First colon is split into three treatment arms to compare predicted # survival across arms split_colon <- split(colon, colon$rx) colon_arm1 <- split_colon$Obs colon_arm2 <- split_colon$Lev colon_arm3 <- split_colon$`Lev+5FU` # One coxph model is fit for each treatment colon1ph <- coxph(Surv(time, status) ~sex + age + obstruct + nodes, colon_arm1, x = TRUE, model = TRUE) colon2ph <- coxph(Surv(time, status) ~ sex + age + obstruct + nodes, colon_arm2, x = TRUE, model = TRUE) colon3ph <- coxph(Surv(time, status) ~ sex + age + obstruct + nodes, colon_arm3, x = TRUE, model = TRUE) # Creating list of models cox.fit.list <- vector("list", 3) cox.fit.list[[1]] <- prep_coxfit(colon1ph) cox.fit.list[[2]] <- prep_coxfit(colon2ph) cox.fit.list[[3]] <- prep_coxfit(colon3ph) # Creating new data row for predictions new.data <- colon[1, ] # Creating KM.hat object n.models=length(cox.fit.list) KM.hat=vector('list',n.models) lp=rep(NA,n.models) names(KM.hat)=names(cox.fit.list) for (i in 1:n.models) { km.hat=predict_one_coxfit(cox.fit.list[[i]],new.data) lp[i]=attr(km.hat,'lp') sfit=list(time=km.hat$time,surv=km.hat$surv) class(sfit)='survfit' KM.hat[[i]]=sfit } # Function takes KM.hat object and a time or vector of times cox_times_table(KM.hat, fixTimes = "100")
library(survival) library(shinyCox) # First colon is split into three treatment arms to compare predicted # survival across arms split_colon <- split(colon, colon$rx) colon_arm1 <- split_colon$Obs colon_arm2 <- split_colon$Lev colon_arm3 <- split_colon$`Lev+5FU` # One coxph model is fit for each treatment colon1ph <- coxph(Surv(time, status) ~sex + age + obstruct + nodes, colon_arm1, x = TRUE, model = TRUE) colon2ph <- coxph(Surv(time, status) ~ sex + age + obstruct + nodes, colon_arm2, x = TRUE, model = TRUE) colon3ph <- coxph(Surv(time, status) ~ sex + age + obstruct + nodes, colon_arm3, x = TRUE, model = TRUE) # Creating list of models cox.fit.list <- vector("list", 3) cox.fit.list[[1]] <- prep_coxfit(colon1ph) cox.fit.list[[2]] <- prep_coxfit(colon2ph) cox.fit.list[[3]] <- prep_coxfit(colon3ph) # Creating new data row for predictions new.data <- colon[1, ] # Creating KM.hat object n.models=length(cox.fit.list) KM.hat=vector('list',n.models) lp=rep(NA,n.models) names(KM.hat)=names(cox.fit.list) for (i in 1:n.models) { km.hat=predict_one_coxfit(cox.fit.list[[i]],new.data) lp[i]=attr(km.hat,'lp') sfit=list(time=km.hat$time,surv=km.hat$surv) class(sfit)='survfit' KM.hat[[i]]=sfit } # Function takes KM.hat object and a time or vector of times cox_times_table(KM.hat, fixTimes = "100")
Creates confidence levels for plotting predicted survival curves.
get_confint(p, se, conf.type, conf.int, ulimit = TRUE)
get_confint(p, se, conf.type, conf.int, ulimit = TRUE)
p |
Vector of survival probabilities |
se |
Vector of standard errors |
conf.type |
Type of confidence interval, includes 'plain', 'log', 'log-log', 'logit', and 'arcsin'. |
conf.int |
The level for two-sided confidence interval on the predicted survival curve, default is 0.95. |
ulimit |
Should upper bound be limited to 1, default is 'TRUE' |
list of length two, containing the lower and upper confidence levels
library(survival) library(shinyCox) colondeaths <- colon[colon$etype == 2, ] split_colon <- split(colondeaths, colondeaths$rx) colon_arm1 <- split_colon$Obs colon1ph <- coxph(Surv(time, status) ~ factor(extent) + nodes + strata(surg) + factor(differ), colon_arm1, x = TRUE, model = TRUE) new.data = cbind.data.frame(`factor(extent)` = 3, `surg` = "surg=0",`factor(differ)` = 2,`nodes` = 5) coxfit = prep_coxfit(colon1ph) coxlist = surv_pred_info(colon1ph) for_ci = predict_se(coxlist, coxfit, new.data) get_confint(for_ci$surv, for_ci$std.err, conf.int = 0.95, conf.type = "log-log")
library(survival) library(shinyCox) colondeaths <- colon[colon$etype == 2, ] split_colon <- split(colondeaths, colondeaths$rx) colon_arm1 <- split_colon$Obs colon1ph <- coxph(Surv(time, status) ~ factor(extent) + nodes + strata(surg) + factor(differ), colon_arm1, x = TRUE, model = TRUE) new.data = cbind.data.frame(`factor(extent)` = 3, `surg` = "surg=0",`factor(differ)` = 2,`nodes` = 5) coxfit = prep_coxfit(colon1ph) coxlist = surv_pred_info(colon1ph) for_ci = predict_se(coxlist, coxfit, new.data) get_confint(for_ci$surv, for_ci$std.err, conf.int = 0.95, conf.type = "log-log")
survival::coxph()
object suitable for shine_coxph()
Performs survival::coxph()
with model = TRUE
and x = TRUE
as defaults.
Checks that Cox model is appropriate for use with shine_coxph()
.
make_coxph(formula, data, ...)
make_coxph(formula, data, ...)
formula |
a formula object, with the response on the left of a |
data |
a data.frame in which to interpret the variables named in
the |
... |
other arguments which will be passed to |
Object of class "coxph"
representing the fit
library(survival) ovarianph <- make_coxph(Surv(futime, fustat) ~ age + strata(rx), data = ovarian)
library(survival) ovarianph <- make_coxph(Surv(futime, fustat) ~ age + strata(rx), data = ovarian)
Computes Cox-model predicted survival function for one new data row using
coxfit
list object created by prep_coxfit()
.
predict_one_coxfit(coxfit, newdata)
predict_one_coxfit(coxfit, newdata)
coxfit |
This is an object returned by |
newdata |
vector of new data |
data.frame of predicted survival probabilities over time, one column is time, one is probability
This function's primary use is within the shiny app, where a coxph
object
is not available. It can be used outside of that context but that is the
main purpose of this function, and why it only accepts the return object
of prep_coxfit()
. In the context of the shiny app, the new data is taken
from user inputs.
# First, fit model using coxph library(survival) bladderph <- coxph(Surv(stop, event) ~ rx + number + size, bladder, model = TRUE, x = TRUE) # Use coxph object with function bladderfit <- prep_coxfit(bladderph) # Take first row of bladder as 'new data' newdata <- bladder[1, ] predictions <- predict_one_coxfit(bladderfit, newdata)
# First, fit model using coxph library(survival) bladderph <- coxph(Surv(stop, event) ~ rx + number + size, bladder, model = TRUE, x = TRUE) # Use coxph object with function bladderfit <- prep_coxfit(bladderph) # Take first row of bladder as 'new data' newdata <- bladder[1, ] predictions <- predict_one_coxfit(bladderfit, newdata)
Adapted from parts of survival::survfit.coxph()
, computes predictions for
standard errors based on surv_pred_info()
output and newdata
from the
shiny app.
predict_se(listsurv, coxfit, newdata)
predict_se(listsurv, coxfit, newdata)
listsurv |
Output from |
coxfit |
|
newdata |
Data used to make predicted standard errors |
a list of number of subjects for each curve, times at which the curve has a step, number at risk for each time, number of events at each time, number censored at each time (no event but exit risk set), estimated survival, cumulative hazard at each transition, and standard error of the cumulative hazard.
library(survival) library(shinyCox) colondeaths <- colon[colon$etype == 2, ] split_colon <- split(colondeaths, colondeaths$rx) colon_arm1 <- split_colon$Obs colon1ph <- coxph(Surv(time, status) ~ factor(extent) + nodes + strata(surg) + factor(differ), colon_arm1, x = TRUE, model = TRUE) new.data = cbind.data.frame(`factor(extent)` = 3, `surg` = "surg=0",`factor(differ)` = 2,`nodes` = 5) coxfit = prep_coxfit(colon1ph) coxlist = surv_pred_info(colon1ph) predict_se(coxlist, coxfit, new.data)
library(survival) library(shinyCox) colondeaths <- colon[colon$etype == 2, ] split_colon <- split(colondeaths, colondeaths$rx) colon_arm1 <- split_colon$Obs colon1ph <- coxph(Surv(time, status) ~ factor(extent) + nodes + strata(surg) + factor(differ), colon_arm1, x = TRUE, model = TRUE) new.data = cbind.data.frame(`factor(extent)` = 3, `surg` = "surg=0",`factor(differ)` = 2,`nodes` = 5) coxfit = prep_coxfit(colon1ph) coxlist = surv_pred_info(colon1ph) predict_se(coxlist, coxfit, new.data)
coxph()
object for shiny appSimplifies coxph()
output and checks that predictions
match those of the original object
prep_coxfit(coxph.result, tol = 1e-07)
prep_coxfit(coxph.result, tol = 1e-07)
coxph.result |
Result returned by |
tol |
numerical tolerance for prediction differences, default is |
list containing baseline survival estimates, linear predictor
estimates, predictor types, coefficient estimates, mean and range of numeric
predictors, levels of categorical predictors, strata if any, coxph()
formula, table of hazard ratios, table with proportional hazard assumption
results, number of subjects, and number of events
# First, fit model using coxph library(survival) bladderph <- coxph(Surv(stop, event) ~ rx + number + size, bladder, model = TRUE, x = TRUE) # Use coxph object with function bladderfit <- prep_coxfit(bladderph)
# First, fit model using coxph library(survival) bladderph <- coxph(Surv(stop, event) ~ rx + number + size, bladder, model = TRUE, x = TRUE) # Use coxph object with function bladderfit <- prep_coxfit(bladderph)
Writes a shiny app to visualize predicted survival curves from one or multiple Cox models. One feature of this function is that the shiny app, once created, will not contain any identifiable data, containing only information necessary for predictions.
shine_coxph(..., app.dir = NULL, theme = c("default", "dashboard"))
shine_coxph(..., app.dir = NULL, theme = c("default", "dashboard"))
... |
Arbitrary number of Cox proportional hazard models, created by
|
app.dir |
Directory where shiny app is created. Specifically, a
sub-folder will be made containing the |
theme |
Theme of shiny app.
|
A list containing Cox model information along with the shiny app code. The app is written to the directory while the function is operating.
There are some requirements in order for this function to run without
error: in your original survival::coxph()
function or functions,
model = TRUE
and x = TRUE
are required arguments (used to create the
simplified "coxph"
object). Currently, this function does not support
penalized models (e.g., as created by ridge()
and pspline()
). Multiple
strata terms and strata by covariate interaction terms in the formula are
also not currently supported, but workarounds are available by respectively
using a new strata factor variable encompassing all combinations of desired
stratum variable levels. Use of time-varying covariates (e.g. with tt()
)
and multi-state models is not supported in our function. The package is not
intended to support Fine-gray models by survival::finegray()
creating Cox
models, but doing so will not result in an error.
This package is intended to visualize and present predicted survival
functions for fitted Cox models. In regards to formula notation, the
variable names used are ultimately what will be displayed in the
application. Using functions in the formula will work, but with multiple
nested functions it will fail. Using "." notation is not currently
supported. The na.action
is inherited from the Cox models, with omit
being the only option with support at this time. For these reasons, we
recommend creating all final variables (including suitable transformations)
with meaningful names prior to using survival::coxph()
.
library(survival) # Data used is from survival package, renamed for legibility names(leukemia)[names(leukemia) == "x"] <- "treatment" # Make Cox model, with x = TRUE and model = TRUE model1 <- coxph(Surv(time, status) ~ treatment, leukemia, x = TRUE, model = TRUE) # Use shine_coxph() to create shiny app in temporary directory shine_coxph("Model 1" = model1) # Get directory for shiny app (should be first, check file list if not) filedir <- list.files(tempdir())[1] # Run shiny app from temporary directory shiny::runApp(paste0(tempdir(), "/", filedir)) # Remove app from directory once finished unlink(paste0(tempdir(),"/",filedir), recursive = TRUE)
library(survival) # Data used is from survival package, renamed for legibility names(leukemia)[names(leukemia) == "x"] <- "treatment" # Make Cox model, with x = TRUE and model = TRUE model1 <- coxph(Surv(time, status) ~ treatment, leukemia, x = TRUE, model = TRUE) # Use shine_coxph() to create shiny app in temporary directory shine_coxph("Model 1" = model1) # Get directory for shiny app (should be first, check file list if not) filedir <- list.files(tempdir())[1] # Run shiny app from temporary directory shiny::runApp(paste0(tempdir(), "/", filedir)) # Remove app from directory once finished unlink(paste0(tempdir(),"/",filedir), recursive = TRUE)
Computes necessary information to calculate standard errors and
confidence intervals in shiny app. This is adapted from parts of
survival::survfit.coxph()
. This function is meant to be used in conjunction with
predict_se()
.
surv_pred_info(model, ctype, individual = FALSE, id, se.fit = TRUE, stype = 2)
surv_pred_info(model, ctype, individual = FALSE, id, se.fit = TRUE, stype = 2)
model |
a |
ctype |
whether the cumulative hazard computation should have a correction for ties, 1=no, 2=yes. |
individual |
deprecated argument, replaced by |
id |
optional variable name of subject identifiers. Not supported in app |
se.fit |
a logical value indicating whether standard errors should be computed. Default is TRUE for standard models, FALSE for multi-state (code not yet present for that case.) |
stype |
computation of the survival curve, 1=direct, 2=exponential of the cumulative hazard. Default is 2. |
A list of information needed for computing predicted standard errors.
library(survival) colondeaths <- colon[colon$etype == 2, ] split_colon <- split(colondeaths, colondeaths$rx) colon_arm1 <- split_colon$Obs colon1ph <- coxph(Surv(time, status) ~ factor(extent) + nodes + strata(surg) + factor(differ), colon_arm1, x = TRUE, model = TRUE) surv_pred_info(colon1ph)
library(survival) colondeaths <- colon[colon$etype == 2, ] split_colon <- split(colondeaths, colondeaths$rx) colon_arm1 <- split_colon$Obs colon1ph <- coxph(Surv(time, status) ~ factor(extent) + nodes + strata(surg) + factor(differ), colon_arm1, x = TRUE, model = TRUE) surv_pred_info(colon1ph)