--- title: "Testing PH assumptions" date: "`r Sys.Date()`" output: rmarkdown::html_vignette bibliography: references.bib csl: biomedicine.csl vignette: > %\VignetteIndexEntry{Testing PH assumptions} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( fig.dim = c(8, 6), warning = FALSE, message = FALSE ) ``` # Loading R packages ```{r} # install.packages("maicplus") library(maicplus) ``` # Introduction Here we demonstrate briefly how we can test proportional hazards assumptions including weights for time-to-event endpoints. ```{r} data(weighted_sat) data(adtte_sat) data(pseudo_ipd_sat) ``` ## Unanchored case ```{r} result <- maic_unanchored( weights_object = weighted_sat, ipd = adtte_sat, pseudo_ipd = pseudo_ipd_sat, trt_ipd = "A", trt_agd = "B", normalize_weight = FALSE, endpoint_name = "Overall Survival", endpoint_type = "tte", eff_measure = "HR", time_scale = "months", km_conf_type = "log-log" ) fit_surv <- result$inferential$fit$km_after fit_cox <- result$inferential$fit$model_after ``` To assess if the hazards are proportional to each other over time, we can check if the log cumulative hazard lines are parallel. ```{r} # Log-log plot ph_diagplot_lch(fit_surv, time_scale = "months", log_time = TRUE, endpoint_name = "OS", subtitle = "(After Matching)" ) ``` Another way is using the Schoenfeld residual. At the subject’s failure time, the residual measures how the value of $x$ for the subject who fails differs from a weighted average of $x$ values for those still at risk. Weights depend on estimated HR for each subject at risk. If the lines in Schoenfeld plots are flat, covariate effect are constant over time and proportional hazards assumptions are met. Alternatively, a non-significant p-value in Schoenfeld residual test suggests that there is no strong evidence for non-proportionality. ```{r} # Schoenfeld plot ph_diagplot_schoenfeld(fit_cox, time_scale = "months", log_time = FALSE, endpoint_name = "OS", subtitle = "(After Matching)" ) # Or set of diagnostic plots ph_diagplot( weights_object = weighted_sat, tte_ipd = adtte_sat, tte_pseudo_ipd = pseudo_ipd_sat, trt_var_ipd = "ARM", trt_var_agd = "ARM", trt_ipd = "A", trt_agd = "B", trt_common = NULL, endpoint_name = "Overall Survival", time_scale = "week", zph_transform = "log", zph_log_hazard = TRUE ) ``` ## Anchored case Similar tests can be done for the anchored case. ```{r} data(weighted_twt) data(adtte_twt) data(pseudo_ipd_twt) ph_diagplot( weights_object = weighted_twt, tte_ipd = adtte_twt, tte_pseudo_ipd = pseudo_ipd_twt, trt_var_ipd = "ARM", trt_var_agd = "ARM", trt_ipd = "A", trt_agd = "B", trt_common = "C", endpoint_name = "Overall Survival", time_scale = "week", zph_transform = "log", zph_log_hazard = TRUE ) ``` ```{r, echo = FALSE, eval = FALSE} # heuristic check library(dplyr) library(survival) library(survminer) ipd_matched <- weighted_sat$data combined_data_tte <- ipd_matched %>% left_join(adtte_sat, by = c("USUBJID", "ARM")) pseudo_ipd <- pseudo_ipd_sat pseudo_ipd$weights <- 1 combined_data_tte <- rbind( combined_data_tte[, colnames(pseudo_ipd)], pseudo_ipd ) # Change the reference treatment to B combined_data_tte$ARM <- stats::relevel(as.factor(combined_data_tte$ARM), ref = "B") combined_data_tte$TIME <- combined_data_tte$TIME / 30.4375 # change time to months fit_surv <- survfit(Surv(TIME, EVENT == 1) ~ ARM, weights = weights, data = combined_data_tte) fit_cox <- coxph(Surv(TIME, EVENT == 1) ~ ARM, weights = weights, data = combined_data_tte) ggsurvplot(fit_surv, data = combined_data_tte, xlab = "Months", ylab = "Log-log plot OS", main = "Log-log plot OS", fun = "cloglog" ) test.ph <- cox.zph(fit_cox, transform = "log") print(ggcoxzph(test.ph)) ```