Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modifications in Cox model visualization #410

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
27 changes: 20 additions & 7 deletions R/ggcoxfunctional.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@
#' @importFrom stats approx
#' @importFrom stats resid
#' @importFrom survival coxph
#' @importFrom magrittr %>%
NULL
#' Functional Form of Continuous Variable in Cox Proportional Hazards Model
#'@description Displays graphs of continuous explanatory variable against martingale residuals of null
#'cox proportional hazards model, for each term in of the right side of \code{formula}. This might help to properly
#'choose the functional form of continuous variable in cox model (\link{coxph}). Fitted lines with \link{lowess} function
#'should be linear to satisfy cox proportional hazards model assumptions.
#'
#'@param fit an object of class \link{coxph.object} - created with \link{coxph} function.
#'@param formula a formula object, with the response on the left of a ~ operator, and the terms on the right. The response must be a survival object as returned by the \link{Surv} function.
#'@param formula (deprecated) a formula object, with the response on the left of a ~ operator, and the terms on the right. The response must be a survival object as returned by the \link{Surv} function.
#'@param data a \code{data.frame} in which to interpret the variables named in the formula,
#'@param iter parameter of \link{lowess}.
#'@param f parameter of \link{lowess}.
#'@param vars.keep character vector of variables for which we want to draw the plot.
#'@param xlim,ylim x and y axis limits e.g. xlim = c(0, 1000), ylim = c(0, 1).
#'@param ylab y axis label.
#'@param title the title of the final \link{grob} (\code{top} in \link{arrangeGrob})
Expand All @@ -31,16 +32,17 @@ NULL
#'
#' library(survival)
#' data(mgus)
#' res.cox <- coxph(Surv(futime, death) ~ mspike + log(mspike) + I(mspike^2) +
#' res.cox <- coxph(Surv(futime, death) ~ mspike + sex + log(mspike) + I(mspike^2) +
#' age + I(log(age)^2) + I(sqrt(age)), data = mgus)
#' ggcoxfunctional(res.cox, data = mgus, point.col = "blue", point.alpha = 0.5)
#' ggcoxfunctional(res.cox, data = mgus, point.col = "blue", point.alpha = 0.5)
#' ggcoxfunctional(res.cox, data = mgus, vars.keep=c("mspike", "log(mspike)"))
#' ggcoxfunctional(res.cox, data = mgus, point.col = "blue", point.alpha = 0.5,
#' title = "Pass the title", caption = "Pass the caption")
#'
#'
#'@describeIn ggcoxfunctional Functional Form of Continuous Variable in Cox Proportional Hazards Model.
#'@export
ggcoxfunctional <- function (formula, data = NULL, fit, iter = 0, f = 0.6,
ggcoxfunctional <- function (formula, data = NULL, fit, iter = 0, f = 0.6, vars.keep,
point.col = "red", point.size = 1, point.shape = 19, point.alpha = 1,
xlim = NULL, ylim = NULL,
ylab = "Martingale Residuals \nof Null Cox Model",
Expand All @@ -58,9 +60,20 @@ ggcoxfunctional <- function (formula, data = NULL, fit, iter = 0, f = 0.6,
}
formula <- fit$formula
data <- .get_data(fit, data)
remov = sapply(attr(stats::terms(formula), "term.labels"),
function(x) {!is.numeric(with(data, eval(parse(text=x))))})
if(any(remov))
formula <- stats::drop.terms(stats::terms(formula), which(remov), keep.response=TRUE)

explanatory.variables.names <- attr(stats::terms(formula), "term.labels")

if(!missing(vars.keep)){
if(!all(vars.keep %in% explanatory.variables.names)) stop("Unknown or non-numeric `vars.keep`")
explanatory.variables.names <- vars.keep
}

attr(stats::terms(formula), "term.labels") -> explanatory.variables.names
stats::model.matrix(formula, data = data) -> explanatory.variables.values
explanatory.variables.values <- stats::model.matrix(formula, data = data)
data = data[rownames(explanatory.variables.values), ]
SurvFormula <- deparse(formula[[2]])
martingale_resid <- lowess_x <- lowess_y <- NULL
lapply(explanatory.variables.names, function(i){
Expand Down
64 changes: 53 additions & 11 deletions R/ggcoxzph.R
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#'Graphical Test of Proportional Hazards with ggplot2
#'@description Displays a graph of the scaled Schoenfeld residuals, along with a
#' smooth curve using \pkg{ggplot2}. Wrapper around \link{plot.cox.zph}.
#'@param fit an object of class \link{cox.zph}.
#' smooth curve using \pkg{ggplot2}. Wrapper around \link{plot.cox.zph}. If fit is a \code{coxph}, the function will also plot the coefficient estimated by the model as a horizontal line.
#'@param fit an object of class \link{coxph} or \link{cox.zph}.
#'@param resid a logical value, if TRUE the residuals are included on the plot,
#' as well as the smooth fit.
#'@param se a logical value, if TRUE, confidence bands at two standard errors
Expand All @@ -11,8 +11,11 @@
#'@param nsmo number of points used to plot the fitted spline.
#'@param var the set of variables for which plots are desired. By default, plots
#' are produced in turn for each variable of a model.
#'@param var_pval the max Grambsch-Therneau test pvalue for which plots are desired. Use only one of \code{var} or \code{var_pval}.
#'@param point.col,point.size,point.shape,point.alpha color, size, shape and visibility to be used for points.
#'@param caption the caption of the final \link{grob} (\code{bottom} in \link{arrangeGrob})
#'@param zph.transform the argument to pass to \code{survival::cox.zph} if \code{fit} is a \code{coxph} object
#'@param hline_size the argument to pass to \code{survival::cox.zph} if \code{fit} is a \code{coxph} object
#'@param ggtheme function, ggplot2 theme name.
#' Allowed values include ggplot2 official themes: see \code{\link[ggplot2]{theme}}.
#'@param ... further arguments passed to either the print() function or to the \code{\link[ggpubr]{ggpar}} function for customizing the plot (see Details section).
Expand All @@ -37,22 +40,43 @@
#' cox.zph.fit <- cox.zph(fit)
#' # plot all variables
#' ggcoxzph(cox.zph.fit)
#' ggcoxzph(fit)
#' # plot all variables for which the Grambsch-Therneau test pvalue is less than 0.55
#' ggcoxzph(fit, var_pval=0.55)
#' # plot all variables in specified order
#' ggcoxzph(cox.zph.fit, var = c("ecog.ps", "rx", "age"), font.main = 12)
#' # plot specified variables in specified order
#' ggcoxzph(cox.zph.fit, var = c("ecog.ps", "rx"), font.main = 12, caption = "Caption goes here")
#'
#'@describeIn ggcoxzph Graphical Test of Proportional Hazards using ggplot2.
#'@export
ggcoxzph <- function (fit, resid = TRUE, se = TRUE, df = 4, nsmo = 40, var,
ggcoxzph <- function (fit, resid = TRUE, se = TRUE, df = 4, nsmo = 40, var, var_pval,
point.col = "red", point.size = 1, point.shape = 19, point.alpha = 1,
caption = NULL,
ggtheme = theme_survminer(), ...){

x <- fit
if(!methods::is(x, "cox.zph"))
stop("Can't handle an object of class ", class(x))

caption = NULL, zph.transform="km", hline_size = 1.25,
ggtheme = theme_survminer(), ...){


if(!methods::is(fit, "cox.zph") && !methods::is(fit, "coxph"))
stop("Can't handle an object of class ", class(fit))

if(methods::is(fit, "coxph")){
COX_FIT <- TRUE
x <- survival::cox.zph(fit, transform = zph.transform)
} else{
COX_FIT <- FALSE
x <- fit
}

if(!missing(var_pval)){
if(!is.numeric(var_pval))
stop("var_pval should be a numeric vector")
if(!missing(var))
stop("Can't handle both var and var_pval")
tmp <- x$table[,"p"]
var <- names(tmp[tmp<var_pval])
var <- var[var!="GLOBAL"]
}

xx <- x$x
yy <- x$y
d <- nrow(yy)
Expand Down Expand Up @@ -146,15 +170,33 @@ ggcoxzph <- function (fit, resid = TRUE, se = TRUE, df = 4, nsmo = 40, var,
names(plots) <- var
class(plots) <- c("ggcoxzph", "ggsurv", "list")

if(COX_FIT){
if(missing(var)||is.null(var)||is.na(var)){
betas <- fit$coefficients
} else {
betas <- fit$coefficients[var]
}

if (length(betas) != length(plots))
stop("Error: length of plot list (", length(plots),
") is different from length of coefficients (", length(betas), ")")
for (i in 1:length(betas)) {
p <- plots[[i]]
p <- p + geom_hline(yintercept = betas[i], color = "steel blue",
size = hline_size)
plots[[i]] <- p
}
}

if("GLOBAL" %in% rownames(x$table)) # case of multivariate Cox
global_p <- x$table["GLOBAL", 3]
else global_p <- NULL # Univariate Cox
attr(plots, "global_pval") <- global_p
attr(plots, "caption") <- caption
plots

}


#' @param x an object of class ggcoxzph
#' @param newpage open a new page. See \code{\link{grid.arrange}}.
#' @method print ggcoxzph
Expand Down
18 changes: 18 additions & 0 deletions fork_survminer.Rproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Version: 1.0

RestoreWorkspace: Default
SaveWorkspace: Default
AlwaysSaveHistory: Default

EnableCodeIndexing: Yes
UseSpacesForTab: Yes
NumSpacesForTab: 2
Encoding: UTF-8

RnwWeave: Sweave
LaTeX: pdfLaTeX

BuildType: Package
PackageUseDevtools: Yes
PackageInstallArgs: --no-multiarch --with-keep.source
PackageRoxygenize: rd,collate,namespace
2 changes: 1 addition & 1 deletion man/ggcoxfunctional.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 14 additions & 4 deletions man/ggcoxzph.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.