256 get cox pairwise df strata in formulas#258
Conversation
Signed-off-by: Jan Szczypiński <jan.szczypinski@gmail.com>
Signed-off-by: Jan Szczypiński <jan.szczypinski@gmail.com>
Signed-off-by: Jan Szczypiński <jan.szczypinski@gmail.com>
Unit Tests Summary 1 files 240 suites 3m 13s ⏱️ Results for commit 3049777. ♻️ This comment has been updated with latest results. |
Unit Test Performance Difference
Additional test case details
Results for commit a94398a ♻️ This comment has been updated with latest results. |
Code Coverage SummaryDiff against mainResults for commit: 3049777 Minimum allowed coverage is ♻️ This comment has been updated with latest results |
| #' \itemize{ | ||
| #' \item `HR`: The Hazard Ratio formatted to two decimal places. | ||
| #' \item `95% CI`: The 95\% confidence interval as `"(lower, upper)"`. | ||
| #' \item `95% CI`: The 95% Wald confidence interval as `"(lower, upper)"`. |
There was a problem hiding this comment.
can you change the method if you want? If not we need to be upfront about it
There was a problem hiding this comment.
By default, coxph() calculates the variance using the standard Fisher information matrix, and the resulting CIs extracted via summary() or confint() are Wald confidence intervals.
If you want to change the underlying variance method to calculate Robust (Sandwich) Confidence Intervals, you need to use the robust = TRUE argument inside your model call.
So we can add an argument robust and additionally specify the confidence limit in another argument?
However these will be two additional arguments in the function call. What is your take on this?
There was a problem hiding this comment.
Could we use ... to pass these additional arguments consistently to each method?
There was a problem hiding this comment.
I have added the code to do that.
| formula_str <- paste(deparse(model_formula), collapse = " ") | ||
| if (grepl("\\b[a-zA-Z][a-zA-Z0-9.]*::", formula_str)) { | ||
| cli::cli_abort( | ||
| "{.arg model_formula} must be specified without namespace.", | ||
| call = get_cli_abort_call() | ||
| ) | ||
| } | ||
|
|
There was a problem hiding this comment.
I got the same from AI in another PR (avot01). I wonder if we should make an utility for it
There was a problem hiding this comment.
I will put it import-standalone-checks.R
There was a problem hiding this comment.
It's called check_formula_for_namespace()
There was a problem hiding this comment.
That is not the place for this. see header:
# Standalone file: do not edit by hand
# Source: https://github.com/insightsengineering/standalone/blob/HEAD/R/standalone-checks.R
# Generated by: usethis::use_standalone("insightsengineering/standalone", "checks")
# ----------------------------------------------------------------------There was a problem hiding this comment.
Good call. I have put it in utils.R
| #' Check if formula contains namespace | ||
| #' @keywords internal | ||
| #' @noRd | ||
| check_formula_for_namespace <- function(formula) { |
There was a problem hiding this comment.
I think we need a test for this directly (to add into utils.R)
There was a problem hiding this comment.
Added a test for it
| @@ -40,18 +38,20 @@ test_that( | |||
| expect_no_error( | |||
| suppressWarnings( | |||
There was a problem hiding this comment.
I would aim at removing all suppressWarnings (I may also have added some tbh)
There was a problem hiding this comment.
I removed all from get_cox_pairwise_df()
| if (test == "log-rank") { | ||
| # --- 1. Standard Log-Rank via survival (Matches SAS & rtables) --- | ||
| sdiff <- survival::survdiff(formula, data = data) | ||
|
|
||
| # Calculate the asymptotic p-value using the chi-square statistic | ||
| # Degrees of freedom is (number of groups - 1) | ||
| p_value <- stats::pchisq(sdiff$chisq, df = length(sdiff$n) - 1, lower.tail = FALSE) | ||
| } else if (test == "likelihood-ratio") { | ||
| # --- 2. Likelihood-Ratio Test via survival --- | ||
| # Fit the full Cox model | ||
| fit_full <- survival::coxph(formula, data = data, ties = ties) | ||
|
|
||
| # Safely create the reduced formula by dropping the arm variable | ||
| reduced_formula <- stats::update(formula, paste(". ~ . -", arm)) | ||
|
|
||
| # Fit the reduced model explicitly to avoid drop1 environment scope errors | ||
| fit_reduced <- survival::coxph(reduced_formula, data = data, ties = ties) |
There was a problem hiding this comment.
I have reverted to survival for computing log-rank p value, since coin is using permutation test for this. This resulted in discrepancy between crane and tern results.
There was a problem hiding this comment.
We need to talk about the LRT engine change (survreg → coxph)
Just flagging this because I think it deserves a note in the PR description / NEWS.
The old LRT used survreg(dist = "exponential") to match SAS's PROC LIFETEST LR test. The new code uses nested coxph() anova (Cox partial-likelihood LRT, equivalent to SAS PROC PHREG). The switch was necessary because survreg() errors on strata() terms.
However, we could preserve both: use the original survreg exponential LRT when the formula has no strata() terms (keeping backward compatibility and SAS PROC LIFETEST alignment), and fall back to the Cox LRT only when strata() is present. This way existing non-stratified results stay unchanged, and stratified formulas get a valid test. Both approaches have SAS equivalents — they just come from different procedures.
Either way, worth a note in NEWS since test = "likelihood-ratio" behavior is changing for at least the stratified case.
Currently get_cox_pairwise_df do not accept formulas with strata() term, which is quite common.
Also formulas in R should not use namespace
https://stat.ethz.ch/pipermail/r-devel/2025-April/083989.html
Closes #256
Pre-review Checklist (if item does not apply, mark is as complete)
usethis::pr_merge_main()devtools::test_coverage()Reviewer Checklist (if item does not apply, mark is as complete)
pkgdown::build_site(). Check the R console for errors, and review the rendered website.devtools::test_coverage()When the branch is ready to be merged:
NEWS.mdwith the changes from this pull request under the heading "# cards (development version)". If there is an issue associated with the pull request, reference it in parentheses at the end update (seeNEWS.mdfor examples).