Description

The One Arm Expected Events calculator computes an estimate of the expected events for a planned one-arm study. The program assumes uniform accrual and exponential survival. It allows the user to specify a follow-up period after the close of study accrual. The program default presents a table of expected event totals at timepoints spanning the duration of the study. Alternatively, user can calculate the expected proportion of events at a given time, or the time at which a given proportion of events have occurred. The program will allow the user to specify a percentage of patients that have no risk (‘Percent cured’).

Input Items

The user is prompted for values to the following items. For items that have initial default values set, the values are given in parentheses.

Output Items

For calculations of expected events at given analysis time(s):

For calculations of analysis time for a given number of expected events:

Calculations

Hazard rate: The hazard rate \(\lambda\) for a survival probability S(t) at time t is computed as follows:

\[ \lambda = \frac{-ln(S(t))}{t} \]

Total probability of event: The probability \(P_{tot}\) of an event for a study with hazard rate \(\lambda\), accrual time \(t_{acc}\), and follow-up time \(t_{fu}\), is as follows:

\[ P_{tot} = 1 - \frac{e^{-\lambda t_{fu}}(1-e^{-\lambda t_{acc}})}{\lambda t_{acc}} \]

Fraction of the total events at time t: At times before the completion of accrual, the probability of an event at analysis time t is

\[P(Event|t) =\frac{ P_{d}|t}{P_{tot}}\] At times before the completion of accrual, \(P_{d}|t\) is as follows:

\[ P_{d}|t = \frac{(\frac{1}{\lambda}e^{-\lambda t})+t-\frac{1}{\lambda}}{t_{acc}} \]

At all other times, \(P_{d}|t\) is as follows:

\[ P_{d}|t = 1 - \frac{e^{-\lambda (t-t_{acc})}(1-e^{-\lambda t_{acc}})}{\lambda t_{acc}} \]

Statistical Code

The program is written in R.

View Analysis Time for a Given Proportion of Total Events Code


function(hazard, medsurv, survtime, survprob, accrual_time, fu_time, N, pct_events, survival_input, pct_cured) {

    if (survival_input == "Median Survival") {
        hazard = -1 * log(0.5) / medsurv
    }
    if (survival_input == "Survival Probability") {
        hazard = -1 * log(survprob) / survtime
    }

    perdeath = function(hazard, accrual_time, fu_tume, time) {
        ptot = 1 - exp(-1 * hazard * fu_tume) * (1 - exp(-1 * hazard * accrual_time)) / (hazard * accrual_time)
        if (time < accrual_time) {
            pd = ((1 / hazard) * exp(-1 * hazard * time) + time - 1 / hazard) / accrual_time
        }
        else {
            pd = 1 - exp(-1 * hazard * (time - accrual_time)) * (1 - exp(-1 * hazard * accrual_time)) / (hazard * accrual_time)
        }
        return(pd / ptot)
    }

    aperdeath = function(hazard, accrual, follow, p) {
        v = .5 
        dv = .5 
        x = 0
        while (dv > 1e-6) {
            x = 1 / v - 1
            dv = dv / 2
            if (perdeath(hazard, accrual, follow, x) > p) {
                v = v + dv
            } else {
                v = v - dv
            }
        }
        return(x)
    }

    eea = (1 - pct_cured) * N * (1 - exp(-1 * hazard * fu_time) * (1 - exp(-1 * hazard * accrual_time)) / (hazard * accrual_time))
    analysis_time = aperdeath(hazard, accrual_time, fu_time, pct_events)
    eet = eea * perdeath(hazard, accrual_time, fu_time, analysis_time)

    result = list(hazard = signif(hazard, 2),
                  analysis_time = round(analysis_time, 2),
                  eet = floor(eet),
                  eea = floor(eea))
    return(jsonlite::toJSON(result, pretty = TRUE))
}


View Expected Events for Given Analysis Time Code


function(hazard, medsurv, survtime, survprob, accrual_time, fu_time, N, analysis_time, survival_input, pct_cured) {

    if (survival_input == "Median Survival") {
        hazard = -1 * log(0.5) / medsurv
    }
    if (survival_input == "Survival Probability") {
        hazard = -1 * log(survprob) / survtime
    }

    perdeath = function(hazard, accrual_time, fu_tume, time) {
        ptot = 1 - exp(-1 * hazard * fu_tume) * (1 - exp(-1 * hazard * accrual_time)) / (hazard * accrual_time)
        if (time < accrual_time) {
            pd = ((1 / hazard) * exp(-1 * hazard * time) + time - 1 / hazard) / accrual_time
        } else {
            pd = 1 - exp(-1 * hazard * (time - accrual_time)) * (1 - exp(-1 * hazard * accrual_time)) / (hazard * accrual_time)
        }
        return(pd / ptot)
    }

    pct_eet = perdeath(hazard, accrual_time, fu_time, analysis_time)
    pct_eet_100 = pct_eet * 100
    eea = (1 - pct_cured) * N * (1 - exp(-1 * hazard * fu_time) * (1 - exp(-1 * hazard * accrual_time)) / (hazard * accrual_time))
    eet = pct_eet * eea

    result = list(hazard = signif(hazard, 2),
                  pct_eet_100 = round(pct_eet_100, 1),
                  eet = floor(eet),
                  eea = floor(eea))
    return(jsonlite::toJSON(result, pretty = TRUE))
}


View Expected Event Table Code


function(hazard, medsurv, survtime, survprob, accrual_time, fu_time, N, time_unit, survival_input, pct_cured) {
  
  if (survival_input == "Median Survival") {
    hazard = -1 * log(0.5) / medsurv
  }
  if (survival_input == "Survival Probability") {
    hazard = -1 * log(survprob) / survtime
  }
  
  end_time = (accrual_time + fu_time) * (ifelse(time_unit == 'Years', 12, 1))
  follow_time = (fu_time) * (ifelse(time_unit == 'Years', 12, 1))
  acc_time = (accrual_time) * (ifelse(time_unit == 'Years', 12, 1))
  hazard_months = hazard / ifelse(time_unit == 'Years', 12, 1)
  
  bymonth = matrix(nrow = end_time, ncol = 5)
  
  perdeath = function(hazard, accrual_time, fu_tume, time) {
    ptot = 1 - exp(-1 * hazard * fu_tume) * (1 - exp(-1 * hazard * accrual_time)) / (hazard * accrual_time)
    if (time < accrual_time) {
      pd = ((1 / hazard) * exp(-1 * hazard * time) + time - 1 / hazard) / accrual_time
    } else {
      pd = 1 - exp(-1 * hazard * (time - accrual_time)) * (1 - exp(-1 * hazard * accrual_time)) / (hazard * accrual_time)
    }
    return(pd / ptot)
  }
  
  p_t = perdeath(hazard_months, acc_time, follow_time, end_time)
  eea = (1 - pct_cured) * N * (1 - exp(-1 * hazard_months * follow_time) * (1 - exp(-1 * hazard_months * acc_time)) / (hazard_months * acc_time))
  eet = p_t * eea
  
  for (i in 1:end_time) {
    Ni = floor(min(N, i * (N / acc_time)))
    p_i = perdeath(hazard_months, acc_time, follow_time, i)
    
    ee_i = (1 - pct_cured) * eet * p_i
    pct_i = ee_i / eet * 100
    pct_accr = Ni * 100 / N
    
    bymonth[i, ] = c(i, round(ee_i,1), round(pct_i,1), Ni, round(pct_accr,1))
  } 
  
  bymonth = as.data.frame(bymonth)
  names(bymonth) = c("TimeinMonths", "TotalEvents", "PercentTotalEvents", "TotalAccrual", "PercentAccrual")
  bymonth$PercentTotalEvents = paste0(bymonth$PercentTotalEvents, "%")
  bymonth$PercentAccrual = paste0(bymonth$PercentAccrual, "%")
  
  result = list(hazard = signif(hazard, 2),
                event_table = bymonth)
  return(jsonlite::toJSON(result, pretty = TRUE))
}