| Title: | Tipping Point Analysis for Survival Endpoints |
| Version: | 1.1 |
| Description: | Implements tipping point sensitivity analysis for time-to-event endpoints under different missing data scenarios, as described in Oodally et al. (2025) <doi:10.48550/arXiv.2506.19988>. Supports both model-based and model-free imputation, multiple imputation workflows, plausibility assessment and visualizations. Enables robust assessment for regulatory and exploratory analyses. |
| License: | GPL (≥ 3) |
| Encoding: | UTF-8 |
| RoxygenNote: | 7.3.2 |
| Depends: | R (≥ 3.5) |
| LazyData: | true |
| Imports: | MASS, ggplot2, survival, dplyr, stats, utils, knitr, purrr, rmarkdown |
| VignetteBuilder: | knitr |
| Suggests: | testthat (≥ 3.0.0) |
| Config/testthat/edition: | 3 |
| NeedsCompilation: | no |
| Packaged: | 2025-12-16 12:58:53 UTC; oodalaj1 |
| Author: | Ajmal Oodally |
| Maintainer: | Ajmal Oodally <ajmal.oodally@novartis.com> |
| Repository: | CRAN |
| Date/Publication: | 2025-12-19 20:10:02 UTC |
tipse: Tipping Point Analysis for Survival Endpoints
Description
Implements tipping point sensitivity analysis for time-to-event endpoints under different missing data scenarios, as described in Oodally et al. (2025) doi:10.48550/arXiv.2506.19988. Supports both model-based and model-free imputation, multiple imputation workflows, plausibility assessment and visualizations. Enables robust assessment for regulatory and exploratory analyses.
Author(s)
Maintainer: Ajmal Oodally ajmal.oodally@novartis.com (ORCID)
Authors:
Craig Wang craig.wang@novartis.com (ORCID)
Other contributors:
Zheng Li zheng.li@novartis.com (ORCID) [contributor]
Assess Clinical Plausibility of Imputation Results
Description
This function facilitate the evaluation of clinical plausibility at the tipping point. It provides a text summary comparing event rates, follow-up duration, or hazard ratios between treatment arms depending on the imputation method and arm specified.
Usage
assess_plausibility(tipse, verbose = TRUE)
Arguments
tipse |
A |
verbose |
Logical. If |
Value
A character string summarizing the key information to facilitate clinical plausibility assessment based on the imputation scenario.
Examples
cox1 <- survival::coxph(Surv(AVAL, EVENT) ~ TRT01P, data = codebreak200)
result <- tipping_point_model_free(
dat = codebreak200,
reason = "Early dropout",
impute = "docetaxel",
cox_fit = cox1,
method = "random sampling"
)
assess_plausibility(result)
Average Kaplan-Meier Curves Across Multiple Imputed Datasets
Description
Takes a list of multiply imputed datasets corresponding to a single tipping point parameter and pools the Kaplan-Meier survival curves for a given treatment arm. Uses log(-log) transformation and Rubin's rules for pooling across imputations.
Usage
average_km(km_data, arm, conf_level = 0.95)
Arguments
km_data |
List of data frames, each containing one multiply imputed dataset for a tipping point.
Each data frame must contain columns |
arm |
Character string specifying the treatment arm to pool (must match |
conf_level |
Numeric. Confidence level for CIs (default = 0.95). |
Value
A data frame with the following columns:
- time
Time points of the KM curve.
- survival_comb
Pooled survival probability at each time point.
- survival_lcl_comb
Lower 95% confidence limit of pooled survival.
- survival_ucl_comb
Upper 95% confidence limit of pooled survival.
- stderr
Standard error of the pooled log-log transformed estimate.
Patient level data from dummy trial
Description
Based on re-constructed Kaplan-Meier plot from CodeBreak 200 trial (de Langen et al., 2023)
Usage
codebreak200
Format
A data frame with 345 rows and 5 columns:
- SUBJID
Dummy patient ID
- TRT01P
Treatment arm (Sotorasib or Docetaxel)
- AVAL
PFS time in days
- EVENT
Indicator for PFS event
- CNSRRS
Censoring reason (Early dropout or Other)
- MAXAVAL
Maximum potential survival time, duration between randomization to data cut-off
Source
De Langen, A.J., Johnson, M.L., Mazieres, J., Dingemans, A.M.C., Mountzios, G., Pless, M., Wolf, J., Schuler, M., Lena, H., Skoulidis, F. and Yoneshima, Y., 2023. Sotorasib versus docetaxel for previously treated non-small-cell lung cancer with KRASG12C mutation: a randomised, open-label, phase 3 trial. The Lancet, 401(10378), pp.733-746.
Patient level data from dummy trial
Description
Based on re-constructed Kaplan-Meier plot from ExteNET trial (Martin et al., 2017)
Usage
extenet
Format
A data frame with 2840 rows and 5 columns:
- SUBJID
Dummy patient ID
- TRT01P
Treatment arm (Neratinib or placebo)
- AVAL
iDFS time in days
- EVENT
Indicator for iDFS event
- CNSRRS
Censoring reason (Lost to follow-up or Other)
- MAXAVAL
Maximum potential survival time, duration between randomization to data cut-off
Source
Martin, M., Holmes, F.A., Ejlertsen, B., Delaloge, S., Moy, B., Iwata, H., von Minckwitz, G., Chia, S.K., Mansi, J., Barrios, C.H. and Gnant, M., 2017. Neratinib after trastuzumab-based adjuvant therapy in HER2-positive breast cancer (ExteNET): 5-year analysis of a randomised, double-blind, placebo-controlled, phase 3 trial. The lancet oncology, 18(12), pp.1688-1700.
Fit parametric model for selected subjects
Description
Fit parametric model for selected subjects
Usage
fit_model(dat, reason, impute, imputation_model = c("weibull", "exponential"))
Arguments
dat |
data.frame containing at least 5 columns: TRT01P (treatment arm as factor), AVAL (survival time), EVENT (event indicator), CNSRRS (censoring reason) and MAXAVAL (maximum potential survival time, duration between randomization to data cut-off) |
reason |
a string specifying the censoring reasons which require imputation. It must be one of the reasons from column CNSRRS. |
impute |
a string specifying the treatment arm(s) which require imputation. It must be one of the arms from column TRT01P. |
imputation_model |
a string specifying the parametric distribution used for imputation, can be "Weibull" or "exponential". |
Details
The data.frame contains original columns, plus the following columns appended:
| AVAL4 | Placeholder column to keep imputed survival times |
| EVENT4 | Placeholder column to keep imputed events |
| impute | Flag indicating whether the subject was selected for imputation |
| a | Shape parameter, equal to 1 if exponential |
| b | Scale parameter |
| cdf | Cumulative distribution function |
| ... | Some temporary columns |
Value
data.frame with flags and fitted model parameters to be used for imputation
Model-free imputation via deterministic sampling
Description
patients will be assigned deterministically an event time at the time of censoring or extend the censoring time to the potential maximum follow-up of each patient.
Usage
impute_deterministic(dat, reason, impute, npts, J, seed)
Arguments
dat |
data.frame containing at least 5 columns: TRT01P (treatment arm as factor), AVAL (survival time), EVENT (event indicator), CNSRRS (censoring reason) and MAXAVAL (maximum potential survival time, duration between randomization to data cut-off) |
reason |
a string specifying the censoring reasons which require imputation. It must be one of the reasons from variable CNSRRS. |
impute |
a string specifying the treatment arm(s) which require imputation. It must be one of the arms from variable TRT01P, the first level of TRT01P is considered as the control arm. |
npts |
number of patients to be imputed |
J |
numeric indicating number of imputations. |
seed |
Integer. Random seed for reproducibility. |
Details
patients will be assigned deterministically an event time at the time of censoring or extend the censoring time to the potential maximum follow-up of each patient.
Value
a list of data.frame from each imputation with imputed AVAL and EVENT, where original variables are kept as AVAL and EVENT.
Model-based imputation from parametric distributions
Description
Impute data with Weibull or exponential distribution conditional on follow-up time
Usage
impute_model(
dat,
reason,
impute,
imputation_model = c("weibull", "exponential"),
alpha,
J,
seed = 12345
)
Arguments
dat |
data.frame containing at least 5 columns: TRT01P (treatment arm as factor), AVAL (survival time), EVENT (event indicator), CNSRRS (censoring reason) and MAXAVAL (maximum potential survival time, duration between randomization to data cut-off) |
reason |
a string specifying the censoring reasons which require imputation. It must be one of the reasons from variable CNSRRS. |
impute |
a string specifying the treatment arm(s) which require imputation. It must be one of the arms from variable TRT01P, the first level of TRT01P is considered as the control arm. |
imputation_model |
a string specifying the parametric distribution used for imputation, can be "Weibull" or "exponential". |
alpha |
hazard inflation (if treatment arm is imputed) or deflation (if control arm is imputed) rate |
J |
numeric indicating number of imputations. |
seed |
Integer. Random seed for reproducibility. |
Details
First fit model based on the data without dropout. And then impute the the survival outcome based on exponential or Weibull distribution for those who dropped out.
Value
a list of data.frame from each imputation with imputed AVAL and EVENT, where original variables are kept as AVALo and EVENTo.
Model-free imputation via random sampling
Description
randomly sample from the percentile of best or worst patients (ordered by their observed times regardless of event or censoring) who do not require imputation.
Usage
impute_random(dat, reason, impute, percentile, J, seed = 12345)
Arguments
dat |
data.frame containing at least 5 columns: TRT01P (treatment arm as factor), AVAL (survival time), EVENT (event indicator), CNSRRS (censoring reason) and MAXAVAL (maximum potential survival time, duration between randomization to data cut-off) |
reason |
a string specifying the censoring reasons which require imputation. It must be one of the reasons from variable CNSRRS. |
impute |
a string specifying the treatment arm(s) which require imputation. It must be one of the arms from variable TRT01P, the first level of TRT01P is considered as the control arm. |
percentile |
numeric between 1 and 100, indicating the best (or worst) percentile of subjects to sample from. |
J |
numeric indicating number of imputations. |
seed |
Integer. Random seed for reproducibility. |
Details
We define two sets of subjects to sample from depending on the impute argument:
-
Worst percentile of observations from treatment arm
\forall i \in N \mid \min\{T_i, C_i\} \leq F_{\min\{T_i, C_i\}}^{-1}(\kappa). This set includes all indicesiwhere the minimum ofT_i(event time) andC_i(censoring time) is less than or equal to the\kappa-th percentile of its distribution. -
Best percentile of observations control arm
\forall i \in N \mid \min\{T_i, C_i\} \geq F_{\min\{T_i, C_i\}}^{-1}(\kappa). This set includes all indicesiwhere the minimum ofT_iandC_iis greater than or equal to the\kappa-th percentile of its distribution.
where F(\cdot) denotes the cumulative distribution function (CDF) of the observed times and F^{-1}(\kappa) is the inverse CDF (quantile function) at percentile \kappa.
Value
a list of data.frame from each imputation with imputed AVAL and EVENT, where original variables are kept as AVALo and EVENTo.
Plot Pooled Kaplan–Meier Curves from Model-Free Tipping Point Analysis
Description
Visualizes averaged (pooled) Kaplan-Meier survival curves across multiple tipping point parameters, highlighting the tipping point where the upper CL of the hazard ratio crosses 1.
Usage
## S3 method for class 'tipse'
plot(x, type = c("Kaplan-Meier", "Tipping Point"), ...)
Arguments
x |
An S3 object of class |
type |
Type of plot, either "Kaplan-Meier" or "Tipping Point". |
... |
Additional arguments not used. |
Details
If
type = Kaplan-Meier, then the KM curves from multiply imputed datasets were pooled using Rubin’s rules after complementary log-log transformation as described in Marshall et al. (2009). it can be of interest to visually assess the scenario that tips the result and the shift it causes to the original KM curve, although there is no objective measure to assess the robustness of the result.If
type = Tipping Point, then the HR estimation across the range of tipping point parameters are plotted.
Value
A ggplot2 object displaying pooled Kaplan–Meier curves.
References
Marshall, A., Altman, D.G., Holder, R.L. et al. Combining estimates of interest in prognostic modelling studies after multiple imputation: current practice and guidelines. BMC Med Res Methodol 9, 57 (2009). https://doi.org/10.1186/1471-2288-9-57
Examples
cox1 <- survival::coxph(Surv(AVAL, EVENT) ~ TRT01P, data = codebreak200)
result <- tipping_point_model_based(
dat = codebreak200,
reason = "Early dropout",
impute = "docetaxel",
imputation_model = "weibull",
J = 10,
tipping_range = seq(0.1, 1, by = 0.05),
cox_fit = cox1,
verbose = TRUE,
seed = 12345
)
plot(result, type = "Kaplan-Meier")
plot(result, type = "Tipping Point")
Plot Pooled Kaplan–Meier Curves from Model-Free Tipping Point Analysis
Description
Visualizes averaged (pooled) Kaplan-Meier survival curves across multiple tipping point parameters, highlighting the tipping point where the upper CL of the hazard ratio crosses 1.
Usage
plot_km(tipse)
Arguments
tipse |
An S3 object of class |
Value
A ggplot2 object displaying pooled Kaplan-Meier curves, with:
Colored lines - pooled KM curves for each tipping point parameter
Red line - tipping point where HR upper CL crosses 1
Orange dashed line - original (unimputed) KM curve
Plot Model-Free Tipping Point Results
Description
Visualizes the hazard ratios and confidence intervals across tipping point parameters from model-free analyses, for both random sampling (% best event times) and deterministic sampling (number of patients event-free at DCO). Highlights the tipping point where the upper confidence interval crosses 1.
Usage
plot_tp(tipse)
Arguments
tipse |
An S3 object of class |
Value
A ggplot2 object showing HR and CI across tipping point parameters.
Pooling results using Rubin's Rule
Description
Pooling results from multiple imputations using Rubin's Rule
Usage
pool_results(dat, cox.fit, conf.level = 0.95)
Arguments
dat |
a list of data.frames from multiple imputation using one alpha or kappa parameter |
cox.fit |
a coxph object which is used to compute HRs for each imputed datasets |
conf.level |
confidence level for the returned confidence interval, default to be 0.95. |
Details
The Rubin's rule is applied to the Cox PH model results across imputed datasets as:
-
Compute pooled HR:
\bar{HR}_\lambda = \exp\Bigg(\frac{1}{M} \sum_{m=1}^{M} \log(HR_m)\Bigg) -
Compute pooled variance:
\bar{\sigma}_\lambda^2 = \frac{1}{M} \sum_{m=1}^{M} \sigma_m^2 + \frac{1 + \frac{1}{M}}{M-1} \sum_{m=1}^{M} \big(\log(HR_m) - \overline{\log(HR_\lambda)}\big)^2 -
Compute CI:
\bar{HR}_\lambda \times \exp\big(\pm z_{\alpha/2} \sqrt{\bar{\sigma}_\lambda^2}\big)
Value
a data.frame of pooled hazard ratio and confidence interval estimate using Rubin's Rule
Print method for plausibility assessment
Description
Print method for plausibility assessment
Usage
## S3 method for class 'plausibility_assessment'
print(x, ...)
Arguments
x |
An object of class "plausibility_assessment" |
... |
Further arguments passed to or from other methods. |
Summarize Tipping Point Results (ARD Format)
Description
Creates a concise, analysis-results dataset (ARD) from a tipping point analysis. Identifies the tipping point parameter where the upper CL of the hazard ratio crosses 1 and summarizes key metrics.
Usage
## S3 method for class 'tipse'
summary(object, ...)
Arguments
object |
A |
... |
Additional arguments not used. |
Value
A data frame summarizing:
-
HR- hazard ratio at that tipping point -
CONFINT- 95% CI at tipping point -
METHOD- sampling type used -
ARMIMP- arm imputed -
TIPPT- parameter where upper CL first crosses 1 -
TIPUNIT- parameter meaning -
DESC- textual interpretation
Examples
cox1 <- survival::coxph(Surv(AVAL, EVENT) ~ TRT01P, data = codebreak200)
result <- tipping_point_model_based(
dat = codebreak200,
reason = "Early dropout",
impute = "docetaxel",
imputation_model = "weibull",
J = 10,
tipping_range = seq(0.1, 1, by = 0.05),
cox_fit = cox1,
verbose = TRUE,
seed = 12345
)
summary(result)
Tipping Point Analysis (Model-Based)
Description
Performs a model-based tipping point analysis on time-to-event data by repeatedly imputing censored observations under varying assumptions. The model-based framework assumes that censored patients have a multiple of hazard fitted via a parametric survival model compared to the rest of patients in the same arm (Akinson et al, 2019).
Usage
tipping_point_model_based(
dat,
reason,
impute,
imputation_model = "weibull",
J = 10,
tipping_range = seq(0.05, 1, by = 0.05),
cox_fit = NULL,
verbose = FALSE,
seed = 12345
)
Arguments
dat |
data.frame containing at least 5 columns: TRT01P (treatment arm as factor), AVAL (survival time), EVENT (event indicator), CNSRRS (censoring reason) and MAXAVAL (maximum potential survival time, duration between randomization to data cut-off) |
reason |
Vector specifying censoring reasons to be imputed. |
impute |
a string specifying the treatment arm(s) which require imputation. It must be one of the arms from variable TRT01P, the first level of TRT01P is considered as the control arm. |
imputation_model |
used to fit model to observed data (should be "Weibull" or "exponential") |
J |
numeric indicating number of imputations. |
tipping_range |
Numeric vector. Hazard inflation (>1) for treatment arm imputation or deflation (<1) range for control arm imputation. |
cox_fit |
A Cox model that will be used to calculate HRs on imputed datasets. In case of inclusion of stratification factors or covariates, conditional HR will be used. |
verbose |
Logical. If |
seed |
Integer. Random seed for reproducibility. |
Details
The model-based tipping point analysis provides a reproducible and intuitive framework for exploring the robustness of treatment effects in time-to-event (survival) endpoints when censoring may differ between study arms.
A parametric survival model is fitted using maximum likelihood.
This function applies a hazard deflation on control arm or hazard inflation on treatment
arm, and impute survival times based on the parametric model with additional sampling of the parameters from a multivariate normal distribution.
This imputation procedure is iterated across a range of
tipping point parameters tipping_range. For each parameter value:
Multiple imputed datasets are generated (
Jreplicates), where censored observations in the selected arm are reassigned event times according to the imputation method.A Cox proportional hazards model is fitted to each imputed dataset.
Model estimates are pooled using Rubin’s rules to obtain a combined hazard ratio and confidence interval for that tipping point parameter.
The process yields a series of results showing how the treatment effect changes as increasingly conservative or optimistic assumptions are made about censored observations. The tipping point is defined as the smallest value (hazard inflation) or biggest value (hazard deflation) of the sensitivity parameter for which the upper bound of the hazard ratio confidence interval crosses 1 - i.e., where the apparent treatment benefit is lost.
Value
A tipse object containing:
- original data
Input argument from 'data'.
- imputation_results
A data frame of combined pooled model results across tipping points
- original_HR
The original hazard ratio.
- reason_to_impute
Input argument from 'reason'.
- arm_to_impute
Input argument from 'impute'.
- method_to_impute
Input argument from 'method'.
- imputation_data
A list of imputed datasets for each tipping point value.
References
Atkinson, A., Kenward, M. G., Clayton, T., & Carpenter, J. R. (2019). Reference‐based sensitivity analysis for time‐to‐event data. Pharmaceutical statistics, 18(6), 645-658.
Examples
cox1 <- survival::coxph(Surv(AVAL, EVENT) ~ TRT01P, data = codebreak200)
result <- tipping_point_model_based(
dat = codebreak200,
reason = "Early dropout",
impute = "docetaxel",
imputation_model = "weibull",
J = 10,
tipping_range = seq(0.1, 1, by = 0.05),
cox_fit = cox1,
verbose = TRUE,
seed = 12345
)
Tipping Point Analysis (Model-Free)
Description
Performs a model-free tipping point analysis on time-to-event data by repeatedly imputing censored observations under varying assumptions. The model-free framework assumes that censored patients share similar survival behavior with those from whom they are sampled, without fitting any parametric survival model.
Usage
tipping_point_model_free(
dat,
reason,
impute,
J = 10,
tipping_range = seq(5, 95, by = 5),
cox_fit = NULL,
verbose = FALSE,
method = c("random sampling", "deterministic sampling"),
seed = 12345
)
Arguments
dat |
data.frame containing at least 5 columns: TRT01P (treatment arm as factor), AVAL (survival time), EVENT (event indicator), CNSRRS (censoring reason) and MAXAVAL (maximum potential survival time, duration between randomization to data cut-off) |
reason |
Vector specifying censoring reasons to be imputed. |
impute |
a string specifying the treatment arm(s) which require imputation. It must be one of the arms from variable TRT01P, the first level of TRT01P is considered as the control arm. |
J |
numeric indicating number of imputations. |
tipping_range |
Numeric vector. Percentiles to use when |
cox_fit |
A Cox model that will be used to calculate HRs on imputed datasets. In case of inclusion of stratification factors or covariates, conditional HR will be used. |
verbose |
Logical. If |
method |
Character. Either |
seed |
Integer. Random seed for reproducibility. |
Details
The model-free tipping point analysis provides a reproducible and intuitive framework for exploring the robustness of treatment effects in time-to-event (survival) endpoints when censoring may differ between study arms.
Two sampling modes are supported:
-
method = "random sampling"- performs re-sampling of event times from the best or worst percentile of observed patients ranked by their event or censoring time. Thetipping_rangespecifies the percentiles of the observed data from which event times will be sampled to impute censored patients. For the treatment arm, use the worst percentiles (shortest survival times) from the observed data of both arms. For the control arm, use the best percentiles (longest survival times). -
method = "deterministic sampling"- imputes a fixed number of censored patients deterministically. Thetipping_rangespecifies the number of patients to be imputed. For the treatment arm, it defines the number of patients that will be assumed to have an event at their time of censoring. For the control arm, it defines the number of patients that will be assumed to be event-free at data cut-off, their maximum potential follow-up time.
This function iteratively applies the random- or deterministic-sampling
imputation procedure across a range of
tipping point parameters tipping_range. For each parameter value:
Multiple imputed datasets are generated (
Jreplicates), where censored observations in the selected arm are replaced by sampled or reassigned event times according to the imputation method.A Cox proportional hazards model is fitted to each imputed dataset.
Model estimates are pooled using Rubin’s rules to obtain a combined hazard ratio and confidence interval for that tipping point parameter.
The process yields a series of results showing how the treatment effect changes as increasingly conservative or optimistic assumptions are made about censored observations. The tipping point is defined as the smallest value of the sensitivity parameter (percentile or number of imputed patients) for which the upper bound of the hazard ratio confidence interval crosses 1 - i.e., where the apparent treatment benefit is lost.
Value
A tipse object containing:
- original data
Input argument from 'data'.
- imputation_results
A data frame of combined pooled model results across tipping points
- original_HR
The original hazard ratio.
- reason_to_impute
Input argument from 'reason'.
- arm_to_impute
Input argument from 'impute'.
- method_to_impute
Input argument from 'method'.
- imputation_data
A list of imputed datasets for each tipping point value.
Examples
cox1 <- survival::coxph(Surv(AVAL, EVENT) ~ TRT01P, data = codebreak200)
result <- tipping_point_model_free(
dat = codebreak200,
reason = "Early dropout",
impute = "docetaxel",
J = 10,
tipping_range = seq(5, 95, by = 5),
cox_fit = cox1,
verbose = TRUE,
method = "random sampling",
seed = 12345
)
result2 <- tipping_point_model_free(
dat = codebreak200,
reason = "Early dropout",
impute = "docetaxel",
J = 10,
tipping_range = seq(1, 21, by = 2),
cox_fit = cox1,
verbose = TRUE,
method = "deterministic sampling",
seed = 12345
)