Merck / gsdmvn

The goal of gsdmvn is to enable group sequential trial design for time-to-event endpoints under non-proportional hazards assumptions.
http://merck.github.io/gsdmvn/
GNU General Public License v3.0
3 stars 5 forks source link

wlr under ratio != 1 #23

Open LittleBeannie opened 2 years ago

LittleBeannie commented 2 years ago

The output for ratio 1:1 is similar to the output from other functions/software. However, in the output for ratio 2:1, the events are much smaller than we had expected, different from the production of other software, and power is very different from simulation. I had expected larger number of events under 2:1 compared to 1:1. Also, AHR does not seem reasonable.

library(gsdmvn)
library(gsDesign2)
library(gt)
enrollRates<-tibble::tibble(Stratum="All",duration=6,rate=1)
failRates<-tibble::tibble(Stratum="All",period=1:2,duration=c(4,20)
                          ,failRate=c(0.05,0.2)/12
                          ,hr=c(0.0375/0.05,0.1/0.2)
                          ,dropoutRate=c(0.1,0.2)/12)

studyDuration<-sum(failRates$duration)

# randomization ratio = 1
gs_design_wlr(enrollRates = enrollRates,
                  failRates = failRates,
                  analysisTimes = studyDuration,
                  ratio=1,
                  upar = qnorm(.975),
                  lpar = qnorm(.975))$bounds %>% select(N, Events, AHR)%>%gt()

# randomization ratio = 2
gs_design_wlr(enrollRates = enrollRates,
                  failRates = failRates,
                  analysisTimes = studyDuration,
                  ratio=2,
                  upar = qnorm(.975),
                  lpar = qnorm(.975))$bounds %>% select(N, Events, AHR)%>%gt()

The calculation in the new version need to be verified by simulations.

elong0527 commented 2 years ago

It is an interesting case. Please conduct a simulation for a simplified proportional hazard case for fixed design below.

When randomization ratio is 1, both gsDesign and npSurvSS provide similar results.

But when randomization ratio is 2, the event driven, generalized schoenfeld and asymptotic from npSurvSS provide very different results. This is surprising as I have double checked the implementation of npSurvSS:::npsurvSS:::delta_wlr that follow exactly the formulas in Section 2.3.3 of Yang & Liu (2019). And the results is also different from gsDesign::nSurv.

Input

alpha <- 0.025
beta <- 0.2
power <- 1 - beta
surv_scale <- 0.02
loss_scale <- 0.01
hr <- 0.6
random_ratio <- 2 # 1
total_time <- 18
follow_time <- 12
accr_time <- 6

gsDesign::nSurv(
  lambdaC = surv_scale,
  hr = hr,
  eta = loss_scale,
  alpha = alpha,
  beta = beta,
  T = total_time,
  minfup = follow_time, 
  ratio = random_ratio
)

arm0 <- npsurvSS::create_arm(
  size = 1, accr_time = accr_time, surv_scale = surv_scale,
  loss_scale = loss_scale, follow_time = follow_time
)

arm1 <- npsurvSS::create_arm(
  size = random_ratio, accr_time = accr_time, surv_scale = hr * surv_scale,
  loss_scale = loss_scale, follow_time = follow_time
)

# Sample size for logrank test
npsurvSS::size_two_arm(arm0, arm1,
                       power = power, alpha = alpha,
                       test = list(
                         test = "weighted logrank", weight = "1",
                         mean.approx = "event driven"
                       )
)

npsurvSS::size_two_arm(arm0, arm1,
                       power = power, alpha = alpha,
                       test = list(
                         test = "weighted logrank", weight = "1",
                         mean.approx = "generalized schoenfeld"
                       )
)

npsurvSS::size_two_arm(arm0, arm1,
                       power = power, alpha = alpha,
                       test = list(
                         test = "weighted logrank", weight = "1",
                         mean.approx = "asymptotic"
                       )
)

Output with randomization ratio = 2

> alpha <- 0.025
> beta <- 0.2
> power <- 1 - beta
> surv_scale <- 0.02
> loss_scale <- 0.01
> hr <- 0.6
> random_ratio <- 2 # 1
> total_time <- 18
> follow_time <- 12
> accr_time <- 6
> 
> gsDesign::nSurv(
+   lambdaC = surv_scale,
+   hr = hr,
+   eta = loss_scale,
+   alpha = alpha,
+   beta = beta,
+   T = total_time,
+   minfup = follow_time, 
+   ratio = random_ratio
+ )
Fixed design, two-arm trial with time-to-event
outcome (Lachin and Foulkes, 1986).
Solving for:  Accrual rate 
Hazard ratio                  H1/H0=0.6/1
Study duration:                   T=18
Accrual duration:                   6
Min. end-of-study follow-up: minfup=12
Expected events (total, H1):        130.6948
Expected sample size (total):       716.7034
Accrual rates:
    Stratum 1
0-6  119.4506
Control event rates (H1):
      Stratum 1
0-Inf      0.02
Censoring rates:
      Stratum 1
0-Inf      0.01
Power:                 100*(1-beta)=80%
Type I error (1-sided):   100*alpha=2.5%
Randomization (Exp/Control):  ratio= 2 
> 
> arm0 <- npsurvSS::create_arm(
+   size = 1, accr_time = accr_time, surv_scale = surv_scale,
+   loss_scale = loss_scale, follow_time = follow_time
+ )
> 
> arm1 <- npsurvSS::create_arm(
+   size = random_ratio, accr_time = accr_time, surv_scale = hr * surv_scale,
+   loss_scale = loss_scale, follow_time = follow_time
+ )
> 
> # Sample size for logrank test
> npsurvSS::size_two_arm(arm0, arm1,
+                        power = power, alpha = alpha,
+                        test = list(
+                          test = "weighted logrank", weight = "1",
+                          mean.approx = "event driven"
+                        )
+ )
       n0        n1         n        d0        d1         d 
247.42005 494.84011 742.26016  59.63000  75.72517 135.35517 
> 
> npsurvSS::size_two_arm(arm0, arm1,
+                        power = power, alpha = alpha,
+                        test = list(
+                          test = "weighted logrank", weight = "1",
+                          mean.approx = "generalized schoenfeld"
+                        )
+ )
       n0        n1         n        d0        d1         d 
252.37099 504.74198 757.11297  60.82321  77.24045 138.06366 
> 
> npsurvSS::size_two_arm(arm0, arm1,
+                        power = power, alpha = alpha,
+                        test = list(
+                          test = "weighted logrank", weight = "1",
+                          mean.approx = "asymptotic"
+                        )
+ )
       n0        n1         n        d0        d1         d 
218.36848 436.73696 655.10545  52.62836  66.83367 119.46203 

Output with randomization ratio = 1

> gsDesign::nSurv(
+   lambdaC = surv_scale,
+   hr = hr,
+   eta = loss_scale,
+   alpha = alpha,
+   beta = beta,
+   T = total_time,
+   minfup = follow_time, 
+   ratio = random_ratio
+ )
Fixed design, two-arm trial with time-to-event
outcome (Lachin and Foulkes, 1986).
Solving for:  Accrual rate 
Hazard ratio                  H1/H0=0.6/1
Study duration:                   T=18
Accrual duration:                   6
Min. end-of-study follow-up: minfup=12
Expected events (total, H1):        121.6416
Expected sample size (total):       617.4126
Accrual rates:
    Stratum 1
0-6  102.9021
Control event rates (H1):
      Stratum 1
0-Inf      0.02
Censoring rates:
      Stratum 1
0-Inf      0.01
Power:                 100*(1-beta)=80%
Type I error (1-sided):   100*alpha=2.5%
Equal randomization:          ratio=1
> 
> arm0 <- npsurvSS::create_arm(
+   size = 1, accr_time = accr_time, surv_scale = surv_scale,
+   loss_scale = loss_scale, follow_time = follow_time
+ )
> 
> arm1 <- npsurvSS::create_arm(
+   size = random_ratio, accr_time = accr_time, surv_scale = hr * surv_scale,
+   loss_scale = loss_scale, follow_time = follow_time
+ )
> 
> # Sample size for logrank test
> npsurvSS::size_two_arm(arm0, arm1,
+                        power = power, alpha = alpha,
+                        test = list(
+                          test = "weighted logrank", weight = "1",
+                          mean.approx = "event driven"
+                        )
+ )
       n0        n1         n        d0        d1         d 
305.34136 305.34136 610.68272  73.58945  46.72626 120.31570 
> 
> npsurvSS::size_two_arm(arm0, arm1,
+                        power = power, alpha = alpha,
+                        test = list(
+                          test = "weighted logrank", weight = "1",
+                          mean.approx = "generalized schoenfeld"
+                        )
+ )
       n0        n1         n        d0        d1         d 
305.68353 305.68353 611.36705  73.67191  46.77862 120.45053 
> 
> npsurvSS::size_two_arm(arm0, arm1,
+                        power = power, alpha = alpha,
+                        test = list(
+                          test = "weighted logrank", weight = "1",
+                          mean.approx = "asymptotic"
+                        )
+ )
       n0        n1         n        d0        d1         d 
314.56677 314.56677 629.13355  75.81284  48.13802 123.95086