• Introduction
  • Documentation
  • Concepts
  • Blog
  • Get FACTS
  • Contact
  1. FACTS Tips and Tricks
  2. Dropout Rate Explanation
  • Concepts
  • Adaptive Trials
    • Goldilocks Designs
    • Longitudinal Modeling in Clinical Trial Design
    • Platform Trials
  • The Bayesian Approach
    • Primer
  • FACTS Tips and Tricks
    • Dropout Rate Explanation
    • Inverse Gamma Distribution in FACTS
    • Linear Regression Multiple Imputation Model

On this page

  • How to specify dropouts in FACTS
    • Dropouts Per Dose
      • Dropout Per Dose Guide
    • Dropouts Per Dose Per Visit
  1. FACTS Tips and Tricks
  2. Dropout Rate Explanation

Dropout Rate Explanation

Deep dive into dropout rate specification.

How to specify dropouts in FACTS

For the continuous and dichotomous engines, and the multiple endpoint engine, the default dropout scenario is that no subjects drop out of the study before observing their final endpoint data. If dropouts are expected, the user can specify either the “Dropouts per Dose,” or “Dropouts per Dose per Visit.”

Dropouts Per Dose

If “Dropouts per Dose” is selected, then each subject has a probability of not having an observable final endpoint value equal to the dropout rate of the dose that subject is randomized to. If each subject has multiple visits and “Dropouts per Dose” is selected, then the conditional probability of dropping out before each visit given that the subject had not dropped out up to the visit before rates are all equal. In other words, if the total dropout rate is π_D, the probability of dropping out between visits i and i+1 given that the subject had not dropped out at visit \(i\) is \[ 1-\left(1-\pi_D\right)^{(\frac{1}{V})}\text{ where } V \text{ is the total number of visits.} \] The following interactive widget takes in the desired dropout rate for a single dose and the number of visits a subject will have during their follow-up, and returns information about the dropout rate that FACTS will simulate by visit for that dose.

The columns in the dynamic output table are:

Visit number

The index of the visit that a subject would observe.

Conditional Visit Dropout Rate

The probability that a subject drops out before row specified visit given that they have not dropped out after the previous visit.

Marginal Visit Dropout Rate

The probability that a subject that has not yet been enrolled in the study will dropout before row specified visit and after the previous row’s visit.

Cumulative Dropout Prob.

The probability that a subject will drop out at the row specified visit or before. At the last visit row this value will equal the Desired Dropout Rate.

Dropout Per Dose Guide

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 450

library(shiny)

ui <- fluidPage(
  
  titlePanel("Dropout Per Dose"),
  sidebarLayout(
    sidebarPanel(
      numericInput("dropoutRate", "Desired Dropout Rate:", value = 0.1, min = 0.0, max = 1.0, step = 0.05),
      sliderInput("numVisits", "Number of visits:", 
                  min = 1, max = 20, value = 5)
    ),
    mainPanel(
      tableOutput("visitsGuide")
    )
  )
)

server <- function(input, output) {
  
  output$visitsGuide <- renderTable({
    
    if(is.na(input$dropoutRate)) {stop(safeError("Please enter a dropout rate."))}
    else if(input$dropoutRate < 0 | input$dropoutRate > 1) {stop(safeError("Dropout rate must be between 0 and 1."))}
    else {
      df = data.frame(Visits = 1:input$numVisits,
                                                     ConditionalDropoutRate = 1-(1-input$dropoutRate)^(1/input$numVisits))
      df$TotalDropoutRate = 1-cumprod(1-df$ConditionalDropoutRate)
      df$MarginalDropoutRate = c(df$TotalDropoutRate[1], df$TotalDropoutRate[-1] - df$TotalDropoutRate[-nrow(df)])
  
      df = df[,c("Visits", "ConditionalDropoutRate", "MarginalDropoutRate", "TotalDropoutRate")]
      colnames(df) = c("Visit number",
                "Conditional Visit Dropout Rate",
                "Marginal Visit Dropout Rate",
                "Cumulative Dropout Prob.")
    df}
  }, digits = 4)
}

shinyApp(ui = ui, server = server)

Dropouts Per Dose Per Visit

If “Dropouts per Dose per Visit” is selected, then each subject has a user specified probability of dropping out before a visit v that is specified as the conditional probability of dropping out before visit v given that that they had not dropped out by visit v-1. This leads to a total dropout rate π_D for a participant that is equal to:

\[ \pi_D = 1-\Pi_{v=0}^V(1-\pi_v) \] When specifying Dropouts Per Dose Per Visit in FACTS, the user inputs the conditional visit dropout rate for each visit. This can make it not completely intuitive to get the exact desired cumulative dropout rate. The below application allows the user to enter either the conditional visit dropout rates or the marginal visit dropout rates for a single dose, and provides the implied dropout profile from those inputs. Again, the columns of the table represent:

Visit number

The index of the visit that a subject would observe.

Conditional Visit Dropout Rate

The probability that a subject drops out before row specified visit given that they have not dropped out after the previous visit.

Marginal Visit Dropout Rate

The probability that a subject that has not yet been enrolled in the study will dropout before row specified visit and after the previous row’s visit.

Cumulative Dropout Prob.

The probability that a subject will drop out at the row specified visit or before. At the last visit row this value will equal the Desired Dropout Rate.

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 600

library(shiny)
library(DT)
library(htmltools)

alignCenter <- function(el) {
  htmltools::tagAppendAttributes(el,
                                 style="margin-left:auto;margin-right:auto;"
  )
}

ui <- fluidPage(
  tags$head(
    # Note the wrapping of the string in HTML()
    tags$style(HTML("
      .my_col_class {
         align-content: center;
      }")
    )
  ),
  
  fluidRow(
    titlePanel(h1("Dropout Per Dose Per Visit", align = "center")),
    alignCenter(sliderInput("numVisits", "Number of visits:", 
            min = 1, max = 20, value = 5)),
    DTOutput("dataInputTable")
  )
)

server <- function(input, output) {
  
  df = data.frame(Visits = 1:5,
                       ConditionalDropoutRate = 0.05)
  df$TotalDropoutRate = 1-cumprod(1-df$ConditionalDropoutRate)
  df$MarginalDropoutRate = c(df$TotalDropoutRate[1], df$TotalDropoutRate[-1] - df$TotalDropoutRate[-nrow(df)])
  df = df[,c("Visits", "ConditionalDropoutRate", "MarginalDropoutRate", "TotalDropoutRate")]
  colnames(df) = c("Visit number",
                   "Conditional Visit Dropout Rate",
                   "Marginal Visit Dropout Rate",
                   "Cumulative Dropout Prob.")
  
  ## Render DF to actually change
  output$dataInputTable = renderDT(datatable(df, options = list(
    pageLength = 20,
    dom = "t",
    autoWidth = TRUE,
    columnDefs = list(list(width = '150px', targets = 1:4, orderable = FALSE))), selection = 'none', editable = "cell") |> formatRound(2:4, digits = 4))
  
  ## Update from Conditional
  
  proxy = dataTableProxy('dataInputTable')
  
  observeEvent(input$dataInputTable_cell_edit, {
    info = input$dataInputTable_cell_edit
    str(info)
    i = info$row
    j = info$col
    v = info$value
    df <<- editData(df, info)
    if(j == 2) {
      ## Update other columns from Conditional
      df[,4] <<- 1-cumprod(1-df[,2])
      df[,3] <<- c(df[1,4], df[-1,4] - df[-nrow(df),4])
    } else if(j == 3) {
      ## Update other columns from Marginal
      df[,4] <<- cumsum(df[,3])
      df[,2] <<- c(df[1,3],
                   df[-1,3]/(1-df[-nrow(df),4]))
    } else if(j == 4) {
      ## Update other columns from Cumulative
      
      df[info$row, 3] <<- df[info$row, 4] - ifelse(info$row == 1, 0, df[info$row-1, 4])
      df[info$row, 2] <<- ifelse(info$row == 1, df[info$row,3], df[info$row,3]/(1-df[info$row-1,4]))
      df[,4] <<- 1-cumprod(1-df[,2])
      df[,3] <<- c(df[1,4], df[-1,4] - df[-nrow(df),4])
    }
    replaceData(proxy, df) 
  })
  
  observe({
    input$numVisits
    nv = input$numVisits
    if(nv > nrow(df)) {
      if(nrow(df) == 0) {
        df <<- data.frame(Visits = 1,
                   ConditionalDropoutRate = 1-(1-.1)^(1))
        df$TotalDropoutRate = 1-cumprod(1-df$ConditionalDropoutRate)
        df$MarginalDropoutRate = c(df$TotalDropoutRate[1], df$TotalDropoutRate[-1] - df$TotalDropoutRate[-nrow(df)])
        df = df[,c("Visits", "ConditionalDropoutRate", "MarginalDropoutRate", "TotalDropoutRate")]
        colnames(df) = c("Visit number",
                         "Conditional Visit Dropout Rate",
                         "Marginal Visit Dropout Rate",
                         "Cumulative Dropout Prob.")
      } else {
        for(i in 1:(nv-nrow(df))) {
          df <<- rbind(df, setNames(data.frame(matrix(c(1+nrow(df), 0.05, 0, 0), nrow = 1)), names(df)))
        }
        df[,4] <<- 1-cumprod(1-df[,2])
        df[,3] <<- c(df[1,4], df[-1,4] - df[-nrow(df),4])
      }
    } else if(nv < nrow(df)) {
      df <<- df[1:nv,]
    }
    replaceData(proxy, df) 
  })
}

shinyApp(ui = ui, server = server)

In order to simulate dropouts from the below profile in FACTS, enter the Conditional Visit Dropout Rate into the FACTS Dropout Rate tab.

© Berry Consultants

Cite