├── inst
└── WORDLIST
├── vignettes
├── .gitignore
├── plot_cs.Rmd
└── estimate_cs.Rmd
├── .gitignore
├── .Rbuildignore
├── docs
├── articles
│ ├── plot_cs_files
│ │ └── figure-html
│ │ │ └── unnamed-chunk-3-1.png
│ ├── index.html
│ ├── plot_cs.html
│ └── estimate_cs.html
├── pkgdown.yml
├── link.svg
├── sitemap.xml
├── bootstrap-toc.css
├── docsearch.js
├── pkgdown.js
├── bootstrap-toc.js
├── 404.html
├── reference
│ ├── index.html
│ ├── conditional_surv_est.html
│ └── gg_conditional_surv.html
├── authors.html
├── index.html
├── pkgdown.css
└── docsearch.css
├── NAMESPACE
├── condsurv.Rproj
├── _pkgdown.yml
├── DESCRIPTION
├── README.md
├── README.Rmd
├── man
├── gg_conditional_surv.Rd
└── conditional_surv_est.Rd
├── archive
├── condKMplot.R
└── condKMapp.R
└── R
├── conditional_surv_est.R
└── gg_conditional_surv.R
/inst/WORDLIST:
--------------------------------------------------------------------------------
1 | ggplot
2 | Kaplan
3 |
--------------------------------------------------------------------------------
/vignettes/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 | *.R
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .Rproj.user
2 | .Rhistory
3 | .RData
4 | .Ruserdata
5 | inst/doc
6 |
--------------------------------------------------------------------------------
/.Rbuildignore:
--------------------------------------------------------------------------------
1 | ^.*\.Rproj$
2 | ^\.Rproj\.user$
3 | ^archive$
4 | ^README\.Rmd$
5 | ^data-raw$
6 | ^_pkgdown\.yml$
7 | ^docs$
8 | ^pkgdown$
9 |
--------------------------------------------------------------------------------
/docs/articles/plot_cs_files/figure-html/unnamed-chunk-3-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zabore/condsurv/HEAD/docs/articles/plot_cs_files/figure-html/unnamed-chunk-3-1.png
--------------------------------------------------------------------------------
/NAMESPACE:
--------------------------------------------------------------------------------
1 | # Generated by roxygen2: do not edit by hand
2 |
3 | export(conditional_surv_est)
4 | export(gg_conditional_surv)
5 | importFrom(magrittr,"%>%")
6 | importFrom(rlang,.data)
7 |
--------------------------------------------------------------------------------
/docs/pkgdown.yml:
--------------------------------------------------------------------------------
1 | pandoc: '2.18'
2 | pkgdown: 2.0.6
3 | pkgdown_sha: ~
4 | articles:
5 | estimate_cs: estimate_cs.html
6 | plot_cs: plot_cs.html
7 | last_built: 2022-10-20T15:11Z
8 | urls:
9 | reference: http://www.emilyzabor.com/condsurv/reference
10 | article: http://www.emilyzabor.com/condsurv/articles
11 |
12 |
--------------------------------------------------------------------------------
/condsurv.Rproj:
--------------------------------------------------------------------------------
1 | Version: 1.0
2 |
3 | RestoreWorkspace: Default
4 | SaveWorkspace: Default
5 | AlwaysSaveHistory: Default
6 |
7 | EnableCodeIndexing: Yes
8 | UseSpacesForTab: Yes
9 | NumSpacesForTab: 2
10 | Encoding: UTF-8
11 |
12 | RnwWeave: Sweave
13 | LaTeX: pdfLaTeX
14 |
15 | AutoAppendNewline: Yes
16 | StripTrailingWhitespace: Yes
17 |
18 | BuildType: Package
19 | PackageUseDevtools: Yes
20 | PackageInstallArgs: --no-multiarch --with-keep.source
21 | PackageRoxygenize: rd,collate,namespace
22 |
--------------------------------------------------------------------------------
/_pkgdown.yml:
--------------------------------------------------------------------------------
1 | url: http://www.emilyzabor.com/condsurv/
2 |
3 | template:
4 | params:
5 | bootswatch: lumen
6 |
7 | navbar:
8 | type: default
9 | left:
10 | - icon: fa-home
11 | href: index.html
12 | - text: Function Documentation
13 | href: reference/index.html
14 | - text: Tutorials
15 | menu:
16 | - text: "Estimate conditional survival"
17 | href: articles/estimate_cs.html
18 | - text: "Plot conditional survival curves"
19 | href: articles/plot_cs.html
20 | right:
21 | - icon: fa-github fa-lg
22 | href: https://github.com/zabore/condsurv
23 |
24 |
--------------------------------------------------------------------------------
/DESCRIPTION:
--------------------------------------------------------------------------------
1 | Package: condsurv
2 | Title: Conditional survival estimates and plots
3 | Version: 1.0.0
4 | Date: 2018-04-07
5 | Authors@R: c(person("Emily C.", "Zabor", email = "zabore2@ccf.org", role = c("aut", "cre")), person("Mithat", "Gonen", role = "aut"))
6 | Description: Functions to produce conditional survival estimates with 95% confidence intervals, and to plot Kaplan-Meier conditional survival estimates over a range of conditioned times.
7 | Depends:
8 | R (>= 3.1.0)
9 | License: GPL-2
10 | LazyData: TRUE
11 | Imports:
12 | survival,
13 | dplyr,
14 | ggplot2,
15 | purrr,
16 | tibble,
17 | magrittr,
18 | rlang
19 | RoxygenNote: 7.2.1
20 | Encoding: UTF-8
21 | Suggests:
22 | knitr,
23 | rmarkdown
24 | VignetteBuilder: knitr
25 | URL: http://www.emilyzabor.com/condsurv/
26 | Roxygen: list(markdown = TRUE)
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # condsurv
5 |
6 | The `condsurv` package contains a function for generating conditional
7 | survival estimates with associated confidence intervals, and a function
8 | for plotting conditional survival curves.
9 |
10 | ## Installation
11 |
12 | Install the package using
13 |
14 | ``` r
15 | remotes::install_github("zabore/condsurv")
16 | ```
17 |
18 | ## Documentation
19 |
20 | This package is documented using
21 | [pkgdown](https://pkgdown.r-lib.org/articles/pkgdown.html), and the
22 | resulting website is available at ,
23 | where detailed Tutorials can be found covering all of the package
24 | functionality.
25 |
26 | See for detailed
27 | function documentation.
28 |
--------------------------------------------------------------------------------
/docs/link.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/README.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | output: github_document
3 | ---
4 |
5 |
6 |
7 | # condsurv
8 |
9 | The `condsurv` package contains a function for generating conditional survival estimates with associated confidence intervals, and a function for plotting conditional survival curves.
10 |
11 | ## Installation
12 |
13 | Install the package using
14 |
15 | ```{r eval = FALSE}
16 | remotes::install_github("zabore/condsurv")
17 | ```
18 |
19 | ## Documentation
20 |
21 | This package is documented using [pkgdown](https://pkgdown.r-lib.org/articles/pkgdown.html), and the resulting website is available at [http://www.emilyzabor.com/condsurv/](http://www.emilyzabor.com/condsurv/), where detailed Tutorials can be found covering all of the package functionality.
22 |
23 | See [http://www.emilyzabor.com/condsurv/reference/](http://www.emilyzabor.com/condsurv/reference/) for detailed function documentation.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/docs/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://www.emilyzabor.com/condsurv/404.html
5 |
6 |
7 | http://www.emilyzabor.com/condsurv/articles/estimate_cs.html
8 |
9 |
10 | http://www.emilyzabor.com/condsurv/articles/index.html
11 |
12 |
13 | http://www.emilyzabor.com/condsurv/articles/plot_cs.html
14 |
15 |
16 | http://www.emilyzabor.com/condsurv/authors.html
17 |
18 |
19 | http://www.emilyzabor.com/condsurv/index.html
20 |
21 |
22 | http://www.emilyzabor.com/condsurv/reference/conditional_surv_est.html
23 |
24 |
25 | http://www.emilyzabor.com/condsurv/reference/gg_conditional_surv.html
26 |
27 |
28 | http://www.emilyzabor.com/condsurv/reference/index.html
29 |
30 |
31 |
--------------------------------------------------------------------------------
/man/gg_conditional_surv.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/gg_conditional_surv.R
3 | \name{gg_conditional_surv}
4 | \alias{gg_conditional_surv}
5 | \title{Generate conditional survival plots using ggplot2}
6 | \usage{
7 | gg_conditional_surv(
8 | basekm,
9 | at,
10 | main = NULL,
11 | xlab = "Years",
12 | ylab = "Survival probability",
13 | lwd = 1
14 | )
15 | }
16 | \arguments{
17 | \item{basekm}{\code{survfit} object}
18 |
19 | \item{at}{vector of times on which to condition}
20 |
21 | \item{main}{plot title}
22 |
23 | \item{xlab}{x-axis label}
24 |
25 | \item{ylab}{y-axis label, defaults to "Survival probability"}
26 |
27 | \item{lwd}{plot line width, defaults to 1}
28 | }
29 | \value{
30 | A ggplot with a line for the overall Kaplan-Meier plot and one
31 | additional line for each value in \code{at}
32 |
33 | #' @details See the vignette
34 | at \url{http://www.emilyzabor.com/condsurv/articles/plot_cs.html} for
35 | details and examples.
36 | }
37 | \description{
38 | \code{gg_conditional_surv} produces a Kaplan-Meier plot for a variety of times on
39 | which to condition using ggplot2
40 | }
41 |
--------------------------------------------------------------------------------
/man/conditional_surv_est.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/conditional_surv_est.R
3 | \name{conditional_surv_est}
4 | \alias{conditional_surv_est}
5 | \title{Estimate conditional survival with a 95\% confidence interval}
6 | \usage{
7 | conditional_surv_est(basekm, t1, t2)
8 | }
9 | \arguments{
10 | \item{basekm}{\code{survfit} object}
11 |
12 | \item{t1}{the time on which to condition}
13 |
14 | \item{t2}{the survival time to estimate}
15 | }
16 | \value{
17 | A list where \code{cs_est} is the conditional survival estimate,
18 | \code{cs_lci} is the lower bound of the 95\% confidence interval and
19 | \code{cs_uci} is the upper bound of the 95\% confidence interval
20 | }
21 | \description{
22 | \code{conditional_surv_est} estimates the Kaplan-Meier conditional survival at
23 | fixed time points and produces a 95\% confidence interval
24 | }
25 | \details{
26 | For example, if \code{t1} = 2 and \code{t2} = 5, the function
27 | will return the probability of surviving to year 5 conditioned on having
28 | already survived to year 2. See the vignette
29 | at \url{http://www.emilyzabor.com/condsurv/articles/estimate_cs.html} for
30 | details on calculations of conditional survival estimates and confidence
31 | intervals, and examples.
32 | }
33 |
--------------------------------------------------------------------------------
/vignettes/plot_cs.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Plot conditional survival curves"
3 | author: "Emily C. Zabor"
4 | date: "Last updated: `r Sys.Date()`"
5 | output: rmarkdown::html_vignette
6 | vignette: >
7 | %\VignetteIndexEntry{Plot conditional survival curves}
8 | %\VignetteEngine{knitr::rmarkdown}
9 | %\VignetteEncoding{UTF-8}
10 | ---
11 |
12 | ```{r, include = FALSE}
13 | knitr::opts_chunk$set(
14 | collapse = TRUE,
15 | comment = "#>"
16 | )
17 | ```
18 |
19 | ```{r setup, message = FALSE, warning = FALSE}
20 | library(condsurv)
21 | library(survival)
22 | library(ggplot2)
23 | library(dplyr)
24 | ```
25 |
26 | To plot the conditional survival curves at baseline, and for those who have survived 6 months, 1 year, 1.5 years, and 2 years, we use the `gg_conditional_surv` function.
27 |
28 | The `lung` dataset from the `survival` package will be used to illustrate.
29 |
30 | ```{r message = FALSE}
31 | # Scale the time variable to be in years rather than days
32 | lung2 <-
33 | mutate(
34 | lung,
35 | os_yrs = time / 365.25
36 | )
37 | ```
38 |
39 | ```{r fig.width = 6, fig.height = 4}
40 | myfit <- survfit(Surv(os_yrs, status) ~ 1, data = lung2)
41 |
42 | cond_times <- seq(0, 2, 0.5)
43 |
44 | gg_conditional_surv(
45 | basekm = myfit,
46 | at = cond_times,
47 | main = "Conditional survival in lung data"
48 | )
49 | ```
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/archive/condKMplot.R:
--------------------------------------------------------------------------------
1 | #' Generate conditional survival plots
2 | #'
3 | #' \code{condKMplot} produces a Kaplan-Meier plot for a variety of times on
4 | #' which to condition
5 | #'
6 | #' @param .basekm \code{survfit} object
7 | #' @param .at vector of times on which to condition
8 | #' @param .main plot title
9 | #' @param .xlab x-axis label
10 | #' @param .ylab y-axis label, defaults to "Survival probability"
11 | #' @param .lwd plot line width, defaults to 1
12 | #' @param .mark controls whether censoring times are marked on curves,
13 | #' defaults to F
14 | #'
15 | #' @return A plot with a line for the overall Kaplan-Meier plot and one
16 | #' additional line for each value in \code{.at}
17 | #'
18 | #' @export
19 | #'
20 |
21 | condKMplot <- function(.basekm, .at, .main = NULL, .xlab = NULL,
22 | .ylab = "Survival probability", .lwd = 1, .mark = F) {
23 | library(survival)
24 |
25 | if (class(.basekm) != "survfit") {
26 | stop("Argument to .basekm must be of class survfit")
27 | }
28 | if (max(.at) > max(.basekm$time)) {
29 | stop(paste(
30 | "Argument to .at specifies value(s) outside the range of observed times;",
31 | "the maximum observed time is", round(max(.basekm$time), 2)
32 | ))
33 | }
34 | plot(.basekm,
35 | conf.int = F, xlab = .xlab, ylab = .ylab, main = .main,
36 | lwd = .lwd, mark.time = .mark
37 | )
38 | nt <- length(.at)
39 | fitkm <- list()
40 | for (i in 1:nt) {
41 | fitkm[[i]] <- survfit(
42 | formula = as.formula(.basekm$call$formula),
43 | data = eval(.basekm$call$data),
44 | start.time = .at[i]
45 | )
46 | lines(fitkm[[i]], conf.int = F, col = i + 1, lwd = .lwd, mark.time = .mark)
47 | abline(v = i, lty = 3)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/docs/bootstrap-toc.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/)
3 | * Copyright 2015 Aidan Feldman
4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */
5 |
6 | /* modified from https://github.com/twbs/bootstrap/blob/94b4076dd2efba9af71f0b18d4ee4b163aa9e0dd/docs/assets/css/src/docs.css#L548-L601 */
7 |
8 | /* All levels of nav */
9 | nav[data-toggle='toc'] .nav > li > a {
10 | display: block;
11 | padding: 4px 20px;
12 | font-size: 13px;
13 | font-weight: 500;
14 | color: #767676;
15 | }
16 | nav[data-toggle='toc'] .nav > li > a:hover,
17 | nav[data-toggle='toc'] .nav > li > a:focus {
18 | padding-left: 19px;
19 | color: #563d7c;
20 | text-decoration: none;
21 | background-color: transparent;
22 | border-left: 1px solid #563d7c;
23 | }
24 | nav[data-toggle='toc'] .nav > .active > a,
25 | nav[data-toggle='toc'] .nav > .active:hover > a,
26 | nav[data-toggle='toc'] .nav > .active:focus > a {
27 | padding-left: 18px;
28 | font-weight: bold;
29 | color: #563d7c;
30 | background-color: transparent;
31 | border-left: 2px solid #563d7c;
32 | }
33 |
34 | /* Nav: second level (shown on .active) */
35 | nav[data-toggle='toc'] .nav .nav {
36 | display: none; /* Hide by default, but at >768px, show it */
37 | padding-bottom: 10px;
38 | }
39 | nav[data-toggle='toc'] .nav .nav > li > a {
40 | padding-top: 1px;
41 | padding-bottom: 1px;
42 | padding-left: 30px;
43 | font-size: 12px;
44 | font-weight: normal;
45 | }
46 | nav[data-toggle='toc'] .nav .nav > li > a:hover,
47 | nav[data-toggle='toc'] .nav .nav > li > a:focus {
48 | padding-left: 29px;
49 | }
50 | nav[data-toggle='toc'] .nav .nav > .active > a,
51 | nav[data-toggle='toc'] .nav .nav > .active:hover > a,
52 | nav[data-toggle='toc'] .nav .nav > .active:focus > a {
53 | padding-left: 28px;
54 | font-weight: 500;
55 | }
56 |
57 | /* from https://github.com/twbs/bootstrap/blob/e38f066d8c203c3e032da0ff23cd2d6098ee2dd6/docs/assets/css/src/docs.css#L631-L634 */
58 | nav[data-toggle='toc'] .nav > .active > ul {
59 | display: block;
60 | }
61 |
--------------------------------------------------------------------------------
/docs/docsearch.js:
--------------------------------------------------------------------------------
1 | $(function() {
2 |
3 | // register a handler to move the focus to the search bar
4 | // upon pressing shift + "/" (i.e. "?")
5 | $(document).on('keydown', function(e) {
6 | if (e.shiftKey && e.keyCode == 191) {
7 | e.preventDefault();
8 | $("#search-input").focus();
9 | }
10 | });
11 |
12 | $(document).ready(function() {
13 | // do keyword highlighting
14 | /* modified from https://jsfiddle.net/julmot/bL6bb5oo/ */
15 | var mark = function() {
16 |
17 | var referrer = document.URL ;
18 | var paramKey = "q" ;
19 |
20 | if (referrer.indexOf("?") !== -1) {
21 | var qs = referrer.substr(referrer.indexOf('?') + 1);
22 | var qs_noanchor = qs.split('#')[0];
23 | var qsa = qs_noanchor.split('&');
24 | var keyword = "";
25 |
26 | for (var i = 0; i < qsa.length; i++) {
27 | var currentParam = qsa[i].split('=');
28 |
29 | if (currentParam.length !== 2) {
30 | continue;
31 | }
32 |
33 | if (currentParam[0] == paramKey) {
34 | keyword = decodeURIComponent(currentParam[1].replace(/\+/g, "%20"));
35 | }
36 | }
37 |
38 | if (keyword !== "") {
39 | $(".contents").unmark({
40 | done: function() {
41 | $(".contents").mark(keyword);
42 | }
43 | });
44 | }
45 | }
46 | };
47 |
48 | mark();
49 | });
50 | });
51 |
52 | /* Search term highlighting ------------------------------*/
53 |
54 | function matchedWords(hit) {
55 | var words = [];
56 |
57 | var hierarchy = hit._highlightResult.hierarchy;
58 | // loop to fetch from lvl0, lvl1, etc.
59 | for (var idx in hierarchy) {
60 | words = words.concat(hierarchy[idx].matchedWords);
61 | }
62 |
63 | var content = hit._highlightResult.content;
64 | if (content) {
65 | words = words.concat(content.matchedWords);
66 | }
67 |
68 | // return unique words
69 | var words_uniq = [...new Set(words)];
70 | return words_uniq;
71 | }
72 |
73 | function updateHitURL(hit) {
74 |
75 | var words = matchedWords(hit);
76 | var url = "";
77 |
78 | if (hit.anchor) {
79 | url = hit.url_without_anchor + '?q=' + escape(words.join(" ")) + '#' + hit.anchor;
80 | } else {
81 | url = hit.url + '?q=' + escape(words.join(" "));
82 | }
83 |
84 | return url;
85 | }
86 |
--------------------------------------------------------------------------------
/R/conditional_surv_est.R:
--------------------------------------------------------------------------------
1 | #' Estimate conditional survival with a 95% confidence interval
2 | #'
3 | #' `conditional_surv_est` estimates the Kaplan-Meier conditional survival at
4 | #' fixed time points and produces a 95% confidence interval
5 | #'
6 | #' @param basekm `survfit` object
7 | #' @param t1 the time on which to condition
8 | #' @param t2 the survival time to estimate
9 | #'
10 | #' @details For example, if `t1` = 2 and `t2` = 5, the function
11 | #' will return the probability of surviving to year 5 conditioned on having
12 | #' already survived to year 2. See the vignette
13 | #' at [http://www.emilyzabor.com/condsurv/articles/estimate_cs.html](http://www.emilyzabor.com/condsurv/articles/estimate_cs.html) for
14 | #' details on calculations of conditional survival estimates and confidence
15 | #' intervals, and examples.
16 | #'
17 | #' @return A list where `cs_est` is the conditional survival estimate,
18 | #' `cs_lci` is the lower bound of the 95% confidence interval and
19 | #' `cs_uci` is the upper bound of the 95% confidence interval
20 | #'
21 | #' @export
22 | #'
23 |
24 | conditional_surv_est <- function(basekm, t1, t2) {
25 | if (class(basekm) != "survfit") {
26 | stop(
27 | "Argument to basekm must be of class survfit"
28 | )
29 | }
30 |
31 | if (max(t1) > max(basekm$time)) {
32 | stop(
33 | paste(
34 | "Argument to t1 specifies a value outside the range of observed times;", "the maximum observed time is", round(max(basekm$time), 2)
35 | )
36 | )
37 | }
38 |
39 | if (max(t2) > max(basekm$time)) {
40 | stop(paste(
41 | "Argument to t2 specifies a value outside the range of observed times;",
42 | "the maximum observed time is", round(max(basekm$time), 2)
43 | ))
44 | }
45 |
46 | cs <- summary(basekm, times = c(t1, t2))$surv[2] /
47 | summary(basekm, times = c(t1, t2))$surv[1]
48 |
49 | cs.sq <- cs^2
50 |
51 | d <- basekm$n.event[basekm$time >= t1 &
52 | basekm$time <= t2 &
53 | basekm$n.event > 0]
54 |
55 | r <- basekm$n.risk[basekm$time >= t1 &
56 | basekm$time <= t2 &
57 | basekm$n.event > 0]
58 |
59 | dr <- d / (r * (r - d))
60 |
61 | var.cs <- 1 / (log(cs)^2) * sum(dr)
62 |
63 | ci <- cs^(exp(c(1, -1) * stats::qnorm(0.975) * sqrt(var.cs)))
64 |
65 | ci.cs <- round(ci, 2)
66 |
67 | return(
68 | list(
69 | cs_est = round(cs, 2),
70 | cs_lci = ci.cs[1],
71 | cs_uci = ci.cs[2]
72 | )
73 | )
74 | }
75 |
--------------------------------------------------------------------------------
/R/gg_conditional_surv.R:
--------------------------------------------------------------------------------
1 | #' Generate conditional survival plots using ggplot2
2 | #'
3 | #' `gg_conditional_surv` produces a Kaplan-Meier plot for a variety of times on
4 | #' which to condition using ggplot2
5 | #'
6 | #' @param basekm `survfit` object
7 | #' @param at vector of times on which to condition
8 | #' @param main plot title
9 | #' @param xlab x-axis label
10 | #' @param ylab y-axis label, defaults to "Survival probability"
11 | #' @param lwd plot line width, defaults to 1
12 | #'
13 | #' @return A ggplot with a line for the overall Kaplan-Meier plot and one
14 | #' additional line for each value in `at`
15 | #'
16 | #' #' @details See the vignette
17 | #' at [http://www.emilyzabor.com/condsurv/articles/plot_cs.html](http://www.emilyzabor.com/condsurv/articles/plot_cs.html) for
18 | #' details and examples.
19 | #'
20 | #' @importFrom magrittr "%>%"
21 | #' @importFrom rlang .data
22 | #'
23 | #' @export
24 | #'
25 |
26 | gg_conditional_surv <- function(basekm,
27 | at,
28 | main = NULL,
29 | xlab = "Years",
30 | ylab = "Survival probability",
31 | lwd = 1) {
32 | if (class(basekm) != "survfit") {
33 | stop(
34 | "Argument to basekm must be of class survfit"
35 | )
36 | }
37 |
38 | if (max(at) > max(basekm$time)) {
39 | stop(
40 | paste(
41 | "Argument to at specifies value(s) outside the range of observed times;",
42 | "the maximum observed time is", round(max(basekm$time), 2)
43 | )
44 | )
45 | }
46 |
47 | nt <- length(at)
48 |
49 | fitkm <- list()
50 | fitkmdat <- list()
51 |
52 | for (i in 1:nt) {
53 | fitkm[[i]] <- survival::survfit(
54 | formula = stats::as.formula(basekm$call$formula),
55 | data = eval(basekm$call$data),
56 | start.time = at[i]
57 | )
58 |
59 | fitkmdat[[i]] <- tibble::tibble(
60 | timept = fitkm[[i]]$time,
61 | prob = fitkm[[i]]$surv
62 | )
63 | }
64 |
65 | condsurvdat <- fitkmdat %>%
66 | purrr::map_df(`[`, .id = "which_at") %>%
67 | dplyr::mutate(condtime = factor(which_at, levels = seq(1, nt), labels = at))
68 |
69 | ggplot2::ggplot(
70 | condsurvdat,
71 | ggplot2::aes(x = timept, y = prob, color = condtime)
72 | ) +
73 | ggplot2::geom_step(lwd = lwd) +
74 | ggplot2::ylim(0, 1) +
75 | ggplot2::labs(
76 | x = xlab,
77 | y = ylab,
78 | title = main
79 | ) +
80 | ggplot2::labs(color = "x") +
81 | ggplot2::theme_bw()
82 | }
83 |
--------------------------------------------------------------------------------
/vignettes/estimate_cs.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Estimate conditional survival"
3 | author: "Emily C. Zabor"
4 | date: "Last updated: `r Sys.Date()`"
5 | output: rmarkdown::html_vignette
6 | vignette: >
7 | %\VignetteIndexEntry{Estimate conditional survival}
8 | %\VignetteEngine{knitr::rmarkdown}
9 | %\VignetteEncoding{UTF-8}
10 | ---
11 |
12 | ```{r, include = FALSE}
13 | knitr::opts_chunk$set(
14 | collapse = TRUE,
15 | comment = "#>"
16 | )
17 | ```
18 |
19 | ```{r setup, message = FALSE, warning = FALSE}
20 | library(condsurv)
21 | library(dplyr)
22 | library(survival)
23 | ```
24 |
25 |
26 | If $S(t)$ represents the survival function at time $t$, then conditional survival is defined as
27 |
28 | $$S(y|x) = \frac{S(x + y)}{S(x)}$$
29 |
30 | where $y$ is the number of additional survival years of interest and $x$ is the number of years a subject has already survived.
31 |
32 |
33 | ## Generating conditional survival estimates
34 |
35 | The `conditional_surv_est` function will generate this estimate along with 95\% confidence intervals.
36 |
37 | The `lung` dataset from the `survival` package will be used to illustrate.
38 |
39 | ```{r message = FALSE}
40 | # Scale the time variable to be in years rather than days
41 | lung2 <-
42 | mutate(
43 | lung,
44 | os_yrs = time / 365.25
45 | )
46 | ```
47 |
48 | First generate a single conditional survival estimate. This is the conditional survival of surviving to 1 year conditioned on already having survived 6 months ($0.5$ year). This returns a list, where `cs_est` is the conditional survival estimate, `cs_lci` is the lower bound of the 95\% confidence interval and `cs_uci` is the upper bound of the 95\% confidence interval.
49 |
50 | ```{r}
51 | myfit <- survfit(Surv(os_yrs, status) ~ 1, data = lung2)
52 |
53 | conditional_surv_est(
54 | basekm = myfit,
55 | t1 = 0.5,
56 | t2 = 1
57 | )
58 | ```
59 |
60 | You can easily use `purrr::map_df` to get a table of estimates for multiple timepoints. For example we could get the conditional survival estimate of surviving to a variety of different time points given that the subject has already survived for 6 months (0.5 years).
61 |
62 | ```{r}
63 | prob_times <- seq(1, 2.5, 0.5)
64 |
65 | purrr::map_df(
66 | prob_times,
67 | ~conditional_surv_est(
68 | basekm = myfit,
69 | t1 = 0.5,
70 | t2 = .x)
71 | ) %>%
72 | dplyr::mutate(years = prob_times) %>%
73 | dplyr::select(years, everything()) %>%
74 | knitr::kable()
75 | ```
76 |
77 | ## A note on confidence interval estimation
78 |
79 | The confidence intervals are based on a variation of the log-log transformation, also known as the "exponential" Greenwood formula, where the conditional survival estimate is substituted in for the traditional survival estimate in constructing the confidence interval.
80 |
81 | If $\hat{S}(y|x)$ is the estimated conditional survival to $y$ given having already survived to $x$, then
82 |
83 | $$\hat{S}(y|x)^{exp(\pm1.96\sqrt{\hat{L}(y|x)})}$$
84 |
85 | where
86 |
87 | $$\hat{L}(y|x)=\frac{1}{\log(\hat{S}(y|x))^2}\sum_{j:x \leq \tau_j \leq y}\frac{d_j}{(r_j-d_j)r_j}$$
88 |
89 | and
90 |
91 | $\tau_j$ = distinct death time $j$
92 |
93 | $d_j$ = number of failures at death time $j$
94 |
95 | $r_j$ = number at risk at death time $j$
96 |
--------------------------------------------------------------------------------
/docs/pkgdown.js:
--------------------------------------------------------------------------------
1 | /* http://gregfranko.com/blog/jquery-best-practices/ */
2 | (function($) {
3 | $(function() {
4 |
5 | $('.navbar-fixed-top').headroom();
6 |
7 | $('body').css('padding-top', $('.navbar').height() + 10);
8 | $(window).resize(function(){
9 | $('body').css('padding-top', $('.navbar').height() + 10);
10 | });
11 |
12 | $('[data-toggle="tooltip"]').tooltip();
13 |
14 | var cur_path = paths(location.pathname);
15 | var links = $("#navbar ul li a");
16 | var max_length = -1;
17 | var pos = -1;
18 | for (var i = 0; i < links.length; i++) {
19 | if (links[i].getAttribute("href") === "#")
20 | continue;
21 | // Ignore external links
22 | if (links[i].host !== location.host)
23 | continue;
24 |
25 | var nav_path = paths(links[i].pathname);
26 |
27 | var length = prefix_length(nav_path, cur_path);
28 | if (length > max_length) {
29 | max_length = length;
30 | pos = i;
31 | }
32 | }
33 |
34 | // Add class to parent
, and enclosing if in dropdown
35 | if (pos >= 0) {
36 | var menu_anchor = $(links[pos]);
37 | menu_anchor.parent().addClass("active");
38 | menu_anchor.closest("li.dropdown").addClass("active");
39 | }
40 | });
41 |
42 | function paths(pathname) {
43 | var pieces = pathname.split("/");
44 | pieces.shift(); // always starts with /
45 |
46 | var end = pieces[pieces.length - 1];
47 | if (end === "index.html" || end === "")
48 | pieces.pop();
49 | return(pieces);
50 | }
51 |
52 | // Returns -1 if not found
53 | function prefix_length(needle, haystack) {
54 | if (needle.length > haystack.length)
55 | return(-1);
56 |
57 | // Special case for length-0 haystack, since for loop won't run
58 | if (haystack.length === 0) {
59 | return(needle.length === 0 ? 0 : -1);
60 | }
61 |
62 | for (var i = 0; i < haystack.length; i++) {
63 | if (needle[i] != haystack[i])
64 | return(i);
65 | }
66 |
67 | return(haystack.length);
68 | }
69 |
70 | /* Clipboard --------------------------*/
71 |
72 | function changeTooltipMessage(element, msg) {
73 | var tooltipOriginalTitle=element.getAttribute('data-original-title');
74 | element.setAttribute('data-original-title', msg);
75 | $(element).tooltip('show');
76 | element.setAttribute('data-original-title', tooltipOriginalTitle);
77 | }
78 |
79 | if(ClipboardJS.isSupported()) {
80 | $(document).ready(function() {
81 | var copyButton = " ";
82 |
83 | $("div.sourceCode").addClass("hasCopyButton");
84 |
85 | // Insert copy buttons:
86 | $(copyButton).prependTo(".hasCopyButton");
87 |
88 | // Initialize tooltips:
89 | $('.btn-copy-ex').tooltip({container: 'body'});
90 |
91 | // Initialize clipboard:
92 | var clipboardBtnCopies = new ClipboardJS('[data-clipboard-copy]', {
93 | text: function(trigger) {
94 | return trigger.parentNode.textContent.replace(/\n#>[^\n]*/g, "");
95 | }
96 | });
97 |
98 | clipboardBtnCopies.on('success', function(e) {
99 | changeTooltipMessage(e.trigger, 'Copied!');
100 | e.clearSelection();
101 | });
102 |
103 | clipboardBtnCopies.on('error', function() {
104 | changeTooltipMessage(e.trigger,'Press Ctrl+C or Command+C to copy');
105 | });
106 | });
107 | }
108 | })(window.jQuery || window.$)
109 |
--------------------------------------------------------------------------------
/docs/bootstrap-toc.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/)
3 | * Copyright 2015 Aidan Feldman
4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */
5 | (function() {
6 | 'use strict';
7 |
8 | window.Toc = {
9 | helpers: {
10 | // return all matching elements in the set, or their descendants
11 | findOrFilter: function($el, selector) {
12 | // http://danielnouri.org/notes/2011/03/14/a-jquery-find-that-also-finds-the-root-element/
13 | // http://stackoverflow.com/a/12731439/358804
14 | var $descendants = $el.find(selector);
15 | return $el.filter(selector).add($descendants).filter(':not([data-toc-skip])');
16 | },
17 |
18 | generateUniqueIdBase: function(el) {
19 | var text = $(el).text();
20 | var anchor = text.trim().toLowerCase().replace(/[^A-Za-z0-9]+/g, '-');
21 | return anchor || el.tagName.toLowerCase();
22 | },
23 |
24 | generateUniqueId: function(el) {
25 | var anchorBase = this.generateUniqueIdBase(el);
26 | for (var i = 0; ; i++) {
27 | var anchor = anchorBase;
28 | if (i > 0) {
29 | // add suffix
30 | anchor += '-' + i;
31 | }
32 | // check if ID already exists
33 | if (!document.getElementById(anchor)) {
34 | return anchor;
35 | }
36 | }
37 | },
38 |
39 | generateAnchor: function(el) {
40 | if (el.id) {
41 | return el.id;
42 | } else {
43 | var anchor = this.generateUniqueId(el);
44 | el.id = anchor;
45 | return anchor;
46 | }
47 | },
48 |
49 | createNavList: function() {
50 | return $('');
51 | },
52 |
53 | createChildNavList: function($parent) {
54 | var $childList = this.createNavList();
55 | $parent.append($childList);
56 | return $childList;
57 | },
58 |
59 | generateNavEl: function(anchor, text) {
60 | var $a = $(' ');
61 | $a.attr('href', '#' + anchor);
62 | $a.text(text);
63 | var $li = $(' ');
64 | $li.append($a);
65 | return $li;
66 | },
67 |
68 | generateNavItem: function(headingEl) {
69 | var anchor = this.generateAnchor(headingEl);
70 | var $heading = $(headingEl);
71 | var text = $heading.data('toc-text') || $heading.text();
72 | return this.generateNavEl(anchor, text);
73 | },
74 |
75 | // Find the first heading level (``, then ``, etc.) that has more than one element. Defaults to 1 (for ``).
76 | getTopLevel: function($scope) {
77 | for (var i = 1; i <= 6; i++) {
78 | var $headings = this.findOrFilter($scope, 'h' + i);
79 | if ($headings.length > 1) {
80 | return i;
81 | }
82 | }
83 |
84 | return 1;
85 | },
86 |
87 | // returns the elements for the top level, and the next below it
88 | getHeadings: function($scope, topLevel) {
89 | var topSelector = 'h' + topLevel;
90 |
91 | var secondaryLevel = topLevel + 1;
92 | var secondarySelector = 'h' + secondaryLevel;
93 |
94 | return this.findOrFilter($scope, topSelector + ',' + secondarySelector);
95 | },
96 |
97 | getNavLevel: function(el) {
98 | return parseInt(el.tagName.charAt(1), 10);
99 | },
100 |
101 | populateNav: function($topContext, topLevel, $headings) {
102 | var $context = $topContext;
103 | var $prevNav;
104 |
105 | var helpers = this;
106 | $headings.each(function(i, el) {
107 | var $newNav = helpers.generateNavItem(el);
108 | var navLevel = helpers.getNavLevel(el);
109 |
110 | // determine the proper $context
111 | if (navLevel === topLevel) {
112 | // use top level
113 | $context = $topContext;
114 | } else if ($prevNav && $context === $topContext) {
115 | // create a new level of the tree and switch to it
116 | $context = helpers.createChildNavList($prevNav);
117 | } // else use the current $context
118 |
119 | $context.append($newNav);
120 |
121 | $prevNav = $newNav;
122 | });
123 | },
124 |
125 | parseOps: function(arg) {
126 | var opts;
127 | if (arg.jquery) {
128 | opts = {
129 | $nav: arg
130 | };
131 | } else {
132 | opts = arg;
133 | }
134 | opts.$scope = opts.$scope || $(document.body);
135 | return opts;
136 | }
137 | },
138 |
139 | // accepts a jQuery object, or an options object
140 | init: function(opts) {
141 | opts = this.helpers.parseOps(opts);
142 |
143 | // ensure that the data attribute is in place for styling
144 | opts.$nav.attr('data-toggle', 'toc');
145 |
146 | var $topContext = this.helpers.createChildNavList(opts.$nav);
147 | var topLevel = this.helpers.getTopLevel(opts.$scope);
148 | var $headings = this.helpers.getHeadings(opts.$scope, topLevel);
149 | this.helpers.populateNav($topContext, topLevel, $headings);
150 | }
151 | };
152 |
153 | $(function() {
154 | $('nav[data-toggle="toc"]').each(function(i, el) {
155 | var $nav = $(el);
156 | Toc.init($nav);
157 | });
158 | });
159 | })();
160 |
--------------------------------------------------------------------------------
/docs/articles/index.html:
--------------------------------------------------------------------------------
1 |
2 | Articles • condsurv
6 |
7 |
8 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/docs/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Page not found (404) • condsurv
9 |
10 |
11 |
12 |
13 |
14 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
84 |
85 |
88 |
89 | Content not found. Please use links in the navbar.
90 |
91 |
92 |
93 |
97 |
98 |
99 |
100 |
101 |
102 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/archive/condKMapp.R:
--------------------------------------------------------------------------------
1 | #' Interactive conditional survival Shiny app
2 | #'
3 | #' \code{condKMapp} produces an interactive Shiny app containing output from
4 | #' both the \code{condKMest} function and the \code{condKMplot} function.
5 | #'
6 | #' @param .basekm \code{survfit} object
7 | #'
8 | #' @return The output includes 1) a plot with one overall Kaplan-Meier survival
9 | #' curve and an additional Kaplan-Meier survival curve for each time on which
10 | #' you are conditioning and 2) a table with conditional survival estimates at
11 | #' the specified survival time along with their associated confidence intervals,
12 | #' with one row for each time on which you are conditioning.
13 | #'
14 | #' @export
15 | #'
16 |
17 | condKMapp <- function(.basekm) {
18 | library(shiny)
19 | library(survival)
20 |
21 | shinyApp(
22 | ui = fluidPage(
23 | pageWithSidebar(
24 | headerPanel("Conditional survival estimates"),
25 | sidebarPanel(
26 | helpText("This interactive interface allows you to adjust the
27 | survival time of interest (using the numeric entry box below)
28 | and to select a single survival time, or range of survival times, on
29 | which to condition (using the slider bar below)."),
30 | numericInput(
31 | inputId = "survtime", label = "Enter the survival time of interest (in years)",
32 | value = 5, min = 0, max = 10
33 | ),
34 | sliderInput(
35 | inputId = "condtime", label = "Select the range of times on which to condition (in years)",
36 | value = c(1, 4), min = 1, max = 10, step = 1
37 | ),
38 | helpText("The output includes 1) a plot with one overall Kaplan-Meier
39 | survival curve and an additional Kaplan-Meier survival curve for
40 | each time on which you are conditioning and 2) a table
41 | with conditional survival estimates at the specified
42 | survival time along with their associated confidence intervals,
43 | with one row for each time on which you are conditioning.")
44 | ),
45 | mainPanel(
46 | plotOutput(outputId = "condplot"),
47 | tableOutput(outputId = "condtab")
48 | )
49 | )
50 | ),
51 |
52 | server = function(input, output) {
53 | output$condplot <- renderPlot({
54 | if (class(.basekm) != "survfit") {
55 | stop("Argument to .basekm must be of class survfit")
56 | }
57 | if (max(input$condtime) > max(.basekm$time)) {
58 | stop(paste(
59 | "The range of times on which to condition specifies value(s) outside the range of observed times;",
60 | "the maximum observed time is", round(max(.basekm$time), 2)
61 | ))
62 | }
63 |
64 | plot(.basekm,
65 | conf.int = F, xlab = "Years", ylab = "Survival probability",
66 | lwd = 1, mark.time = F, main = "Conditional survival curves"
67 | )
68 | at <- seq(from = min(input$condtime), to = max(input$condtime), by = 1)
69 | nt <- length(at)
70 | fitkm <- list()
71 | for (i in 1:nt) {
72 | fitkm[[i]] <- survfit(
73 | formula = as.formula(.basekm$call$formula),
74 | data = eval(.basekm$call$data),
75 | start.time = at[i]
76 | )
77 | lines(fitkm[[i]], conf.int = F, col = i + 1, lwd = 1, mark.time = F)
78 | abline(v = i, lty = 3)
79 | }
80 | }, height = 400, width = 500)
81 |
82 | output$condtab <- renderTable({
83 | if (class(.basekm) != "survfit") {
84 | stop("Argument to .basekm must be of class survfit")
85 | }
86 | if (max(input$condtime) > max(.basekm$time)) {
87 | stop(paste(
88 | "The range of times on which to condition specifies value(s) outside the range of observed times;",
89 | "the maximum observed time is", round(max(.basekm$time), 2)
90 | ))
91 | }
92 | if (max(input$survtime) > max(.basekm$time)) {
93 | stop(paste(
94 | "The survival time of interest specifies a value outside the range of observed times;",
95 | "the maximum observed time is", round(max(.basekm$time), 2)
96 | ))
97 | }
98 |
99 | at <- seq(from = min(input$condtime), to = max(input$condtime), by = 1)
100 | nt <- length(at)
101 | cstab <- NULL
102 | for (i in 1:nt) {
103 | if (at[i] < input$survtime) {
104 | cs <- summary(.basekm, times = c(at[i], input$survtime))$surv[2] /
105 | summary(.basekm, times = c(at[i], input$survtime))$surv[1]
106 | cs.sq <- cs^2
107 | d <- .basekm$n.event[.basekm$time >= at[i] & .basekm$time <= input$survtime &
108 | .basekm$n.event > 0]
109 | r <- .basekm$n.risk[.basekm$time >= at[i] & .basekm$time <= input$survtime &
110 | .basekm$n.event > 0]
111 | dr <- d / (r * (r - d))
112 | var.cs <- cs.sq * sum(dr)
113 | ci <- cs + c(-1, 1) * (qnorm(0.975) * sqrt(var.cs))
114 | if (ci[1] < 0) {
115 | warning("Lower bound of CI has been truncated to 0")
116 | }
117 | if (ci[2] > 1) {
118 | warning("Upper bound of CI has been truncated to 1")
119 | }
120 | ci.cs <- round(ci, 2)
121 | if (ci.cs[1] < 0) {
122 | ci.cs[1] <- 0
123 | }
124 | if (ci.cs[2] > 1) {
125 | ci.cs[2] <- 1
126 | }
127 | cstab <- data.frame(rbind(cstab, c(round(at[i]), round(cs, 2), ci.cs[1], ci.cs[2])),
128 | stringsAsFactors = FALSE
129 | )
130 | }
131 | }
132 | colnames(cstab) <- c("Conditional time", "Conditional survival", "Lower 95% CI", "Upper 95% CI")
133 | cstab
134 | }, digits = c(0, 0, 2, 2, 2))
135 | }
136 | )
137 | }
138 |
--------------------------------------------------------------------------------
/docs/reference/index.html:
--------------------------------------------------------------------------------
1 |
2 | Function reference • condsurv
6 |
7 |
8 |
9 |
60 |
61 |
64 |
65 |
66 | All functions
67 |
68 |
69 |
70 | conditional_surv_est()
71 |
72 | Estimate conditional survival with a 95% confidence interval
73 |
74 | gg_conditional_surv()
75 |
76 | Generate conditional survival plots using ggplot2
77 |
78 |
79 |
82 |
83 |
84 |
85 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/docs/authors.html:
--------------------------------------------------------------------------------
1 |
2 | Authors and Citation • condsurv
6 |
7 |
8 |
9 |
60 |
61 |
76 |
77 |
78 |
Citation
79 |
80 |
81 |
82 |
83 |
84 |
Zabor E, Gonen M (2022).
85 | condsurv: Conditional survival estimates and plots .
86 | R package version 1.0.0, http://www.emilyzabor.com/condsurv/ .
87 |
88 |
@Manual{,
89 | title = {condsurv: Conditional survival estimates and plots},
90 | author = {Emily C. Zabor and Mithat Gonen},
91 | year = {2022},
92 | note = {R package version 1.0.0},
93 | url = {http://www.emilyzabor.com/condsurv/},
94 | }
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/docs/reference/conditional_surv_est.html:
--------------------------------------------------------------------------------
1 |
2 | Estimate conditional survival with a 95% confidence interval — conditional_surv_est • condsurv
7 |
8 |
9 |
10 |
61 |
62 |
67 |
68 |
69 |
conditional_surv_est estimates the Kaplan-Meier conditional survival at
70 | fixed time points and produces a 95% confidence interval
71 |
72 |
73 |
74 |
conditional_surv_est ( basekm , t1 , t2 )
75 |
76 |
77 |
78 |
Arguments
79 |
basekm
80 | survfit object
81 |
82 |
83 | t1
84 | the time on which to condition
85 |
86 |
87 | t2
88 | the survival time to estimate
89 |
90 |
91 |
92 |
Value
93 |
94 |
95 |
A list where cs_est is the conditional survival estimate,
96 | cs_lci is the lower bound of the 95% confidence interval and
97 | cs_uci is the upper bound of the 95% confidence interval
98 |
99 |
100 |
Details
101 |
For example, if t1 = 2 and t2 = 5, the function
102 | will return the probability of surviving to year 5 conditioned on having
103 | already survived to year 2. See the vignette
104 | at http://www.emilyzabor.com/condsurv/articles/estimate_cs.html for
105 | details on calculations of conditional survival estimates and confidence
106 | intervals, and examples.
107 |
108 |
109 |
110 |
113 |
114 |
115 |
116 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/docs/reference/gg_conditional_surv.html:
--------------------------------------------------------------------------------
1 |
2 | Generate conditional survival plots using ggplot2 — gg_conditional_surv • condsurv
7 |
8 |
9 |
10 |
61 |
62 |
67 |
68 |
69 |
gg_conditional_surv produces a Kaplan-Meier plot for a variety of times on
70 | which to condition using ggplot2
71 |
72 |
73 |
74 |
gg_conditional_surv (
75 | basekm ,
76 | at ,
77 | main = NULL ,
78 | xlab = "Years" ,
79 | ylab = "Survival probability" ,
80 | lwd = 1
81 | )
82 |
83 |
84 |
85 |
Arguments
86 |
basekm
87 | survfit object
88 |
89 |
90 | at
91 | vector of times on which to condition
92 |
93 |
94 | main
95 | plot title
96 |
97 |
98 | xlab
99 | x-axis label
100 |
101 |
102 | ylab
103 | y-axis label, defaults to "Survival probability"
104 |
105 |
106 | lwd
107 | plot line width, defaults to 1
108 |
109 |
110 |
111 |
Value
112 |
113 |
114 |
A ggplot with a line for the overall Kaplan-Meier plot and one
115 | additional line for each value in at
116 |
117 |
118 |
119 |
#' @details See the vignette
120 | at http://www.emilyzabor.com/condsurv/articles/plot_cs.html for
121 | details and examples.
122 |
123 |
124 |
125 |
128 |
129 |
130 |
131 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Conditional survival estimates and plots • condsurv
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
21 |
22 |
23 |
24 |
25 |
85 |
86 |
87 |
89 |
The condsurv package contains a function for generating conditional survival estimates with associated confidence intervals, and a function for plotting conditional survival curves.
90 |
91 |
Installation
92 |
93 |
Install the package using
94 |
96 |
97 |
103 |
104 |
105 |
106 |
133 |
134 |
135 |
136 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/docs/pkgdown.css:
--------------------------------------------------------------------------------
1 | /* Sticky footer */
2 |
3 | /**
4 | * Basic idea: https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/
5 | * Details: https://github.com/philipwalton/solved-by-flexbox/blob/master/assets/css/components/site.css
6 | *
7 | * .Site -> body > .container
8 | * .Site-content -> body > .container .row
9 | * .footer -> footer
10 | *
11 | * Key idea seems to be to ensure that .container and __all its parents__
12 | * have height set to 100%
13 | *
14 | */
15 |
16 | html, body {
17 | height: 100%;
18 | }
19 |
20 | body {
21 | position: relative;
22 | }
23 |
24 | body > .container {
25 | display: flex;
26 | height: 100%;
27 | flex-direction: column;
28 | }
29 |
30 | body > .container .row {
31 | flex: 1 0 auto;
32 | }
33 |
34 | footer {
35 | margin-top: 45px;
36 | padding: 35px 0 36px;
37 | border-top: 1px solid #e5e5e5;
38 | color: #666;
39 | display: flex;
40 | flex-shrink: 0;
41 | }
42 | footer p {
43 | margin-bottom: 0;
44 | }
45 | footer div {
46 | flex: 1;
47 | }
48 | footer .pkgdown {
49 | text-align: right;
50 | }
51 | footer p {
52 | margin-bottom: 0;
53 | }
54 |
55 | img.icon {
56 | float: right;
57 | }
58 |
59 | /* Ensure in-page images don't run outside their container */
60 | .contents img {
61 | max-width: 100%;
62 | height: auto;
63 | }
64 |
65 | /* Fix bug in bootstrap (only seen in firefox) */
66 | summary {
67 | display: list-item;
68 | }
69 |
70 | /* Typographic tweaking ---------------------------------*/
71 |
72 | .contents .page-header {
73 | margin-top: calc(-60px + 1em);
74 | }
75 |
76 | dd {
77 | margin-left: 3em;
78 | }
79 |
80 | /* Section anchors ---------------------------------*/
81 |
82 | a.anchor {
83 | display: none;
84 | margin-left: 5px;
85 | width: 20px;
86 | height: 20px;
87 |
88 | background-image: url(./link.svg);
89 | background-repeat: no-repeat;
90 | background-size: 20px 20px;
91 | background-position: center center;
92 | }
93 |
94 | h1:hover .anchor,
95 | h2:hover .anchor,
96 | h3:hover .anchor,
97 | h4:hover .anchor,
98 | h5:hover .anchor,
99 | h6:hover .anchor {
100 | display: inline-block;
101 | }
102 |
103 | /* Fixes for fixed navbar --------------------------*/
104 |
105 | .contents h1, .contents h2, .contents h3, .contents h4 {
106 | padding-top: 60px;
107 | margin-top: -40px;
108 | }
109 |
110 | /* Navbar submenu --------------------------*/
111 |
112 | .dropdown-submenu {
113 | position: relative;
114 | }
115 |
116 | .dropdown-submenu>.dropdown-menu {
117 | top: 0;
118 | left: 100%;
119 | margin-top: -6px;
120 | margin-left: -1px;
121 | border-radius: 0 6px 6px 6px;
122 | }
123 |
124 | .dropdown-submenu:hover>.dropdown-menu {
125 | display: block;
126 | }
127 |
128 | .dropdown-submenu>a:after {
129 | display: block;
130 | content: " ";
131 | float: right;
132 | width: 0;
133 | height: 0;
134 | border-color: transparent;
135 | border-style: solid;
136 | border-width: 5px 0 5px 5px;
137 | border-left-color: #cccccc;
138 | margin-top: 5px;
139 | margin-right: -10px;
140 | }
141 |
142 | .dropdown-submenu:hover>a:after {
143 | border-left-color: #ffffff;
144 | }
145 |
146 | .dropdown-submenu.pull-left {
147 | float: none;
148 | }
149 |
150 | .dropdown-submenu.pull-left>.dropdown-menu {
151 | left: -100%;
152 | margin-left: 10px;
153 | border-radius: 6px 0 6px 6px;
154 | }
155 |
156 | /* Sidebar --------------------------*/
157 |
158 | #pkgdown-sidebar {
159 | margin-top: 30px;
160 | position: -webkit-sticky;
161 | position: sticky;
162 | top: 70px;
163 | }
164 |
165 | #pkgdown-sidebar h2 {
166 | font-size: 1.5em;
167 | margin-top: 1em;
168 | }
169 |
170 | #pkgdown-sidebar h2:first-child {
171 | margin-top: 0;
172 | }
173 |
174 | #pkgdown-sidebar .list-unstyled li {
175 | margin-bottom: 0.5em;
176 | }
177 |
178 | /* bootstrap-toc tweaks ------------------------------------------------------*/
179 |
180 | /* All levels of nav */
181 |
182 | nav[data-toggle='toc'] .nav > li > a {
183 | padding: 4px 20px 4px 6px;
184 | font-size: 1.5rem;
185 | font-weight: 400;
186 | color: inherit;
187 | }
188 |
189 | nav[data-toggle='toc'] .nav > li > a:hover,
190 | nav[data-toggle='toc'] .nav > li > a:focus {
191 | padding-left: 5px;
192 | color: inherit;
193 | border-left: 1px solid #878787;
194 | }
195 |
196 | nav[data-toggle='toc'] .nav > .active > a,
197 | nav[data-toggle='toc'] .nav > .active:hover > a,
198 | nav[data-toggle='toc'] .nav > .active:focus > a {
199 | padding-left: 5px;
200 | font-size: 1.5rem;
201 | font-weight: 400;
202 | color: inherit;
203 | border-left: 2px solid #878787;
204 | }
205 |
206 | /* Nav: second level (shown on .active) */
207 |
208 | nav[data-toggle='toc'] .nav .nav {
209 | display: none; /* Hide by default, but at >768px, show it */
210 | padding-bottom: 10px;
211 | }
212 |
213 | nav[data-toggle='toc'] .nav .nav > li > a {
214 | padding-left: 16px;
215 | font-size: 1.35rem;
216 | }
217 |
218 | nav[data-toggle='toc'] .nav .nav > li > a:hover,
219 | nav[data-toggle='toc'] .nav .nav > li > a:focus {
220 | padding-left: 15px;
221 | }
222 |
223 | nav[data-toggle='toc'] .nav .nav > .active > a,
224 | nav[data-toggle='toc'] .nav .nav > .active:hover > a,
225 | nav[data-toggle='toc'] .nav .nav > .active:focus > a {
226 | padding-left: 15px;
227 | font-weight: 500;
228 | font-size: 1.35rem;
229 | }
230 |
231 | /* orcid ------------------------------------------------------------------- */
232 |
233 | .orcid {
234 | font-size: 16px;
235 | color: #A6CE39;
236 | /* margins are required by official ORCID trademark and display guidelines */
237 | margin-left:4px;
238 | margin-right:4px;
239 | vertical-align: middle;
240 | }
241 |
242 | /* Reference index & topics ----------------------------------------------- */
243 |
244 | .ref-index th {font-weight: normal;}
245 |
246 | .ref-index td {vertical-align: top; min-width: 100px}
247 | .ref-index .icon {width: 40px;}
248 | .ref-index .alias {width: 40%;}
249 | .ref-index-icons .alias {width: calc(40% - 40px);}
250 | .ref-index .title {width: 60%;}
251 |
252 | .ref-arguments th {text-align: right; padding-right: 10px;}
253 | .ref-arguments th, .ref-arguments td {vertical-align: top; min-width: 100px}
254 | .ref-arguments .name {width: 20%;}
255 | .ref-arguments .desc {width: 80%;}
256 |
257 | /* Nice scrolling for wide elements --------------------------------------- */
258 |
259 | table {
260 | display: block;
261 | overflow: auto;
262 | }
263 |
264 | /* Syntax highlighting ---------------------------------------------------- */
265 |
266 | pre, code, pre code {
267 | background-color: #f8f8f8;
268 | color: #333;
269 | }
270 | pre, pre code {
271 | white-space: pre-wrap;
272 | word-break: break-all;
273 | overflow-wrap: break-word;
274 | }
275 |
276 | pre {
277 | border: 1px solid #eee;
278 | }
279 |
280 | pre .img, pre .r-plt {
281 | margin: 5px 0;
282 | }
283 |
284 | pre .img img, pre .r-plt img {
285 | background-color: #fff;
286 | }
287 |
288 | code a, pre a {
289 | color: #375f84;
290 | }
291 |
292 | a.sourceLine:hover {
293 | text-decoration: none;
294 | }
295 |
296 | .fl {color: #1514b5;}
297 | .fu {color: #000000;} /* function */
298 | .ch,.st {color: #036a07;} /* string */
299 | .kw {color: #264D66;} /* keyword */
300 | .co {color: #888888;} /* comment */
301 |
302 | .error {font-weight: bolder;}
303 | .warning {font-weight: bolder;}
304 |
305 | /* Clipboard --------------------------*/
306 |
307 | .hasCopyButton {
308 | position: relative;
309 | }
310 |
311 | .btn-copy-ex {
312 | position: absolute;
313 | right: 0;
314 | top: 0;
315 | visibility: hidden;
316 | }
317 |
318 | .hasCopyButton:hover button.btn-copy-ex {
319 | visibility: visible;
320 | }
321 |
322 | /* headroom.js ------------------------ */
323 |
324 | .headroom {
325 | will-change: transform;
326 | transition: transform 200ms linear;
327 | }
328 | .headroom--pinned {
329 | transform: translateY(0%);
330 | }
331 | .headroom--unpinned {
332 | transform: translateY(-100%);
333 | }
334 |
335 | /* mark.js ----------------------------*/
336 |
337 | mark {
338 | background-color: rgba(255, 255, 51, 0.5);
339 | border-bottom: 2px solid rgba(255, 153, 51, 0.3);
340 | padding: 1px;
341 | }
342 |
343 | /* vertical spacing after htmlwidgets */
344 | .html-widget {
345 | margin-bottom: 10px;
346 | }
347 |
348 | /* fontawesome ------------------------ */
349 |
350 | .fab {
351 | font-family: "Font Awesome 5 Brands" !important;
352 | }
353 |
354 | /* don't display links in code chunks when printing */
355 | /* source: https://stackoverflow.com/a/10781533 */
356 | @media print {
357 | code a:link:after, code a:visited:after {
358 | content: "";
359 | }
360 | }
361 |
362 | /* Section anchors ---------------------------------
363 | Added in pandoc 2.11: https://github.com/jgm/pandoc-templates/commit/9904bf71
364 | */
365 |
366 | div.csl-bib-body { }
367 | div.csl-entry {
368 | clear: both;
369 | }
370 | .hanging-indent div.csl-entry {
371 | margin-left:2em;
372 | text-indent:-2em;
373 | }
374 | div.csl-left-margin {
375 | min-width:2em;
376 | float:left;
377 | }
378 | div.csl-right-inline {
379 | margin-left:2em;
380 | padding-left:1em;
381 | }
382 | div.csl-indent {
383 | margin-left: 2em;
384 | }
385 |
--------------------------------------------------------------------------------
/docs/articles/plot_cs.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Plot conditional survival curves • condsurv
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
21 |
22 |
23 |
24 |
25 |
85 |
86 |
97 |
98 |
99 |
100 |
107 |
To plot the conditional survival curves at baseline, and for those
108 | who have survived 6 months, 1 year, 1.5 years, and 2 years, we use the
109 | gg_conditional_surv function.
110 |
The lung dataset from the survival package
111 | will be used to illustrate.
112 |
113 | # Scale the time variable to be in years rather than days
114 | lung2 <-
115 | mutate (
116 | lung ,
117 | os_yrs = time / 365.25
118 | )
119 |
120 | myfit <- survfit ( Surv ( os_yrs , status ) ~ 1 , data = lung2 )
121 |
122 | cond_times <- seq ( 0 , 2 , 0.5 )
123 |
124 | gg_conditional_surv (
125 | basekm = myfit ,
126 | at = cond_times ,
127 | main = "Conditional survival in lung data"
128 | )
129 |
130 |
131 |
132 |
135 |
136 |
137 |
138 |
139 |
140 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
--------------------------------------------------------------------------------
/docs/docsearch.css:
--------------------------------------------------------------------------------
1 | /* Docsearch -------------------------------------------------------------- */
2 | /*
3 | Source: https://github.com/algolia/docsearch/
4 | License: MIT
5 | */
6 |
7 | .algolia-autocomplete {
8 | display: block;
9 | -webkit-box-flex: 1;
10 | -ms-flex: 1;
11 | flex: 1
12 | }
13 |
14 | .algolia-autocomplete .ds-dropdown-menu {
15 | width: 100%;
16 | min-width: none;
17 | max-width: none;
18 | padding: .75rem 0;
19 | background-color: #fff;
20 | background-clip: padding-box;
21 | border: 1px solid rgba(0, 0, 0, .1);
22 | box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .175);
23 | }
24 |
25 | @media (min-width:768px) {
26 | .algolia-autocomplete .ds-dropdown-menu {
27 | width: 175%
28 | }
29 | }
30 |
31 | .algolia-autocomplete .ds-dropdown-menu::before {
32 | display: none
33 | }
34 |
35 | .algolia-autocomplete .ds-dropdown-menu [class^=ds-dataset-] {
36 | padding: 0;
37 | background-color: rgb(255,255,255);
38 | border: 0;
39 | max-height: 80vh;
40 | }
41 |
42 | .algolia-autocomplete .ds-dropdown-menu .ds-suggestions {
43 | margin-top: 0
44 | }
45 |
46 | .algolia-autocomplete .algolia-docsearch-suggestion {
47 | padding: 0;
48 | overflow: visible
49 | }
50 |
51 | .algolia-autocomplete .algolia-docsearch-suggestion--category-header {
52 | padding: .125rem 1rem;
53 | margin-top: 0;
54 | font-size: 1.3em;
55 | font-weight: 500;
56 | color: #00008B;
57 | border-bottom: 0
58 | }
59 |
60 | .algolia-autocomplete .algolia-docsearch-suggestion--wrapper {
61 | float: none;
62 | padding-top: 0
63 | }
64 |
65 | .algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column {
66 | float: none;
67 | width: auto;
68 | padding: 0;
69 | text-align: left
70 | }
71 |
72 | .algolia-autocomplete .algolia-docsearch-suggestion--content {
73 | float: none;
74 | width: auto;
75 | padding: 0
76 | }
77 |
78 | .algolia-autocomplete .algolia-docsearch-suggestion--content::before {
79 | display: none
80 | }
81 |
82 | .algolia-autocomplete .ds-suggestion:not(:first-child) .algolia-docsearch-suggestion--category-header {
83 | padding-top: .75rem;
84 | margin-top: .75rem;
85 | border-top: 1px solid rgba(0, 0, 0, .1)
86 | }
87 |
88 | .algolia-autocomplete .ds-suggestion .algolia-docsearch-suggestion--subcategory-column {
89 | display: block;
90 | padding: .1rem 1rem;
91 | margin-bottom: 0.1;
92 | font-size: 1.0em;
93 | font-weight: 400
94 | /* display: none */
95 | }
96 |
97 | .algolia-autocomplete .algolia-docsearch-suggestion--title {
98 | display: block;
99 | padding: .25rem 1rem;
100 | margin-bottom: 0;
101 | font-size: 0.9em;
102 | font-weight: 400
103 | }
104 |
105 | .algolia-autocomplete .algolia-docsearch-suggestion--text {
106 | padding: 0 1rem .5rem;
107 | margin-top: -.25rem;
108 | font-size: 0.8em;
109 | font-weight: 400;
110 | line-height: 1.25
111 | }
112 |
113 | .algolia-autocomplete .algolia-docsearch-footer {
114 | width: 110px;
115 | height: 20px;
116 | z-index: 3;
117 | margin-top: 10.66667px;
118 | float: right;
119 | font-size: 0;
120 | line-height: 0;
121 | }
122 |
123 | .algolia-autocomplete .algolia-docsearch-footer--logo {
124 | background-image: url("data:image/svg+xml;utf8, ");
125 | background-repeat: no-repeat;
126 | background-position: 50%;
127 | background-size: 100%;
128 | overflow: hidden;
129 | text-indent: -9000px;
130 | width: 100%;
131 | height: 100%;
132 | display: block;
133 | transform: translate(-8px);
134 | }
135 |
136 | .algolia-autocomplete .algolia-docsearch-suggestion--highlight {
137 | color: #FF8C00;
138 | background: rgba(232, 189, 54, 0.1)
139 | }
140 |
141 |
142 | .algolia-autocomplete .algolia-docsearch-suggestion--text .algolia-docsearch-suggestion--highlight {
143 | box-shadow: inset 0 -2px 0 0 rgba(105, 105, 105, .5)
144 | }
145 |
146 | .algolia-autocomplete .ds-suggestion.ds-cursor .algolia-docsearch-suggestion--content {
147 | background-color: rgba(192, 192, 192, .15)
148 | }
149 |
--------------------------------------------------------------------------------
/docs/articles/estimate_cs.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Estimate conditional survival • condsurv
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
21 |
22 |
23 |
24 |
25 |
85 |
86 |
97 |
98 |
99 |
100 |
106 |
If \(S(t)\) represents the survival
107 | function at time \(t\) , then
108 | conditional survival is defined as
109 |
\[S(y|x) = \frac{S(x +
110 | y)}{S(x)}\]
111 |
where \(y\) is the number of
112 | additional survival years of interest and \(x\) is the number of years a subject has
113 | already survived.
114 |
115 |
Generating conditional survival estimates
116 |
117 |
The conditional_surv_est function will generate this
118 | estimate along with 95% confidence intervals.
119 |
The lung dataset from the survival package
120 | will be used to illustrate.
121 |
122 | # Scale the time variable to be in years rather than days
123 | lung2 <-
124 | mutate (
125 | lung ,
126 | os_yrs = time / 365.25
127 | )
128 |
First generate a single conditional survival estimate. This is the
129 | conditional survival of surviving to 1 year conditioned on already
130 | having survived 6 months (\(0.5\)
131 | year). This returns a list, where cs_est is the conditional
132 | survival estimate, cs_lci is the lower bound of the 95%
133 | confidence interval and cs_uci is the upper bound of the
134 | 95% confidence interval.
135 |
136 | myfit <- survfit ( Surv ( os_yrs , status ) ~ 1 , data = lung2 )
137 |
138 | conditional_surv_est (
139 | basekm = myfit ,
140 | t1 = 0.5 ,
141 | t2 = 1
142 | )
143 | #> $cs_est
144 | #> [1] 0.58
145 | #>
146 | #> $cs_lci
147 | #> [1] 0.49
148 | #>
149 | #> $cs_uci
150 | #> [1] 0.66
151 |
You can easily use purrr::map_df to get a table of
152 | estimates for multiple timepoints. For example we could get the
153 | conditional survival estimate of surviving to a variety of different
154 | time points given that the subject has already survived for 6 months
155 | (0.5 years).
156 |
169 |
170 |
176 |
177 |
178 | 1.0
179 | 0.58
180 | 0.49
181 | 0.66
182 |
183 |
184 | 1.5
185 | 0.36
186 | 0.27
187 | 0.45
188 |
189 |
190 | 2.0
191 | 0.16
192 | 0.10
193 | 0.25
194 |
195 |
196 | 2.5
197 | 0.07
198 | 0.02
199 | 0.15
200 |
201 |
202 |
203 |
204 |
205 |
A note on confidence interval estimation
206 |
207 |
The confidence intervals are based on a variation of the log-log
208 | transformation, also known as the “exponential” Greenwood formula, where
209 | the conditional survival estimate is substituted in for the traditional
210 | survival estimate in constructing the confidence interval.
211 |
If \(\hat{S}(y|x)\) is the estimated
212 | conditional survival to \(y\) given
213 | having already survived to \(x\) ,
214 | then
215 |
\[\hat{S}(y|x)^{exp(\pm1.96\sqrt{\hat{L}(y|x)})}\]
216 |
where
217 |
\[\hat{L}(y|x)=\frac{1}{\log(\hat{S}(y|x))^2}\sum_{j:x
218 | \leq \tau_j \leq y}\frac{d_j}{(r_j-d_j)r_j}\]
219 |
and
220 |
\(\tau_j\) = distinct death time
221 | \(j\)
222 |
\(d_j\) = number of failures at
223 | death time \(j\)
224 |
\(r_j\) = number at risk at death
225 | time \(j\)
226 |
227 |
228 |
229 |
234 |
235 |
236 |
237 |
238 |
239 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
--------------------------------------------------------------------------------