Shiny app for survival analysis - Error: object 'input' not found

I'm creating a small shiny app to look at survival analysis for insect mortality. It's aim is to produce a model selection table for the analysis and then a Kaplan-Meier plot with fitted survival curves. I've attached here a pared-down version of the app with simple data upload, model selection and plot generation but I continually get the message 'Error: object 'input' not found'.

I'm new to shiny and struggling with the reactive elements but I've managed to produce the wrong sort of plot elsewhere so it's clearly an issue with the packages I'm using, how I'm using them or what's being called where.

Please help!

Edit: I should mention that the app takes a .csv file with 'Individual', 'Treatment', 'Dose', 'Time', 'Status'. Treatment and Dose are both factorised and status is binary - 1 (when fate known), 0 for unknown at that time point. The plot works in the regular R script!

# Load necessary libraries
library(shiny)
library(survival)
library(AICcmodavg)
library(tidyr)
library(survminer)

# Define the User Interface
ui <- fluidPage(
  # Application title
  titlePanel("Survival Analysis Plot"),
  # Layout with sidebar and main panel
  sidebarLayout(
    sidebarPanel(
      # File input for uploading CSV file
      fileInput("file", "Upload CSV File", accept = c(".csv")),
      # Dropdown to select the model
      selectInput("model_select", "Select Model:", choices = NULL)
    ),
    mainPanel(
      # Output plot for survival analysis
      plotOutput("survivalPlot")
    )
  )
)

# Define the server logic
server <- function(input, output, session) {
  # Reactive expression to read and process the uploaded CSV file
  surv_data <- reactive({
    req(input$file)  # Ensure file is uploaded
    data <- read.csv(input$file$datapath)  # Read the CSV file
    data$Group <- paste(data$Treatment, data$Dose, sep = "_")  # Create a new 'Group' variable
    return(data)  # Return the processed data
  })
  
  # Observe changes in surv_data and update model choices
  observe({
    req(surv_data())  # Ensure surv_data is available
    updateSelectInput(session, "model_select", choices = c("Treatment x Dose", "Treatment + Dose", "Treatment", "Dose"))
  })
  
  # Reactive expression to get the selected model
  selected_model <- reactive({
    req(surv_data())  # Ensure surv_data is available
    surv <- surv_data()  # Get the survival data
    # Fit different models based on the input selection
    surv_T_x_D <- survreg(Surv(Time, Status) ~ Treatment * Dose, data = surv)
    surv_T_D <- survreg(Surv(Time, Status) ~ Treatment + Dose, data = surv)
    surv_T <- survreg(Surv(Time, Status) ~ Treatment, data = surv)
    surv_D <- survreg(Surv(Time, Status) ~ Dose, data = surv)
    
    # Store models in a list with names
    models <- list(surv_T_x_D, surv_T_D, surv_T, surv_D)
    names(models) <- c("Treatment x Dose", "Treatment + Dose", "Treatment", "Dose")
    
    # Return the selected model
    models[[input$model_select]]
  })
  
  # Render the survival plot
  output$survivalPlot <- renderPlot({
    surv <- surv_data()  # Get the survival data
    model <- selected_model()  # Get the selected model
    
    # Predict quantiles from the model
    pred <- data.frame(predict(model, type = "quantile", p = seq(0.01, 0.99, by = 0.01)))
    pred$Group <- surv$Group  # Add Group variable to predictions
    pred$Treatment <- surv$Treatment  # Add Treatment variable to predictions
    pred$Dose <- surv$Dose  # Add Dose variable to predictions
    
    # Initialize an array for predicted values
    pred2 <- array(dim = c(nlevels(as.factor(surv$Treatment)) * nlevels(as.factor(surv$Dose)), ncol(pred)))
    for (i in 1:ncol(pred2)) {
      pred2[, i] <- tapply(pred[, i], pred$Group, unique)  # Apply tapply to group predictions
    }
    
    y_val <- seq(0.99, 0.01, by = -0.01)  # Define y values
    pred2 <- data.frame(t(pred2[, 1:length(y_val)]), y_val)  # Transform predictions to dataframe
    pred2_long <- gather(pred2, key = "Group", value = "time", -y_val)  # Gather predictions into long format
    pred2_long$time <- as.numeric(pred2_long$time)  # Convert time to numeric
    pred2_long$Group <- rep(levels(as.factor(surv$Group)), each = length(y_val))  # Repeat Group levels
    pred2_long$Treatment <- rep(rep(levels(as.factor(surv$Treatment)), each = length(y_val)), nlevels(as.factor(surv$Dose)))  # Repeat Treatment levels
    pred2_long$Dose <- rep(rep(levels(as.factor(surv$Dose)), each = length(y_val) * nlevels(as.factor(surv$Treatment))))  # Repeat Dose levels
    pred2_long <- na.omit(pred2_long)  # Remove NA values
    
    # Plot the survival analysis using survminer
    plot_surv <- ggsurvplot(fit = survfit(Surv(Time, Status) ~ Treatment + Dose, data = surv), conf.int = FALSE, col = "Dose")
    
    # Add predicted lines to the plot
    plot_surv$plot +
      geom_line(data = pred2_long, aes(x = time, y = y_val, group = Group, colour = Dose), size = 0.75, alpha = 0.8) +
      xlab("Time (hours)") +
      facet_grid(~Treatment) +
      theme_bw()
  }, {
    surv_data() # Pass the reactive object to renderPlot
  })
}

# Run the Shiny application
shinyApp(ui = ui, server = server)

This part should be deleted since it is unecessary. The rest should work as expected. You are calling surv_data in the renderPlot function - you don't have to pass it explicitly again.

Good point, thanks! Not sure how that's ended up there from me paring things down. Otherwise, still getting the same error. I'm assuming if the rest looks okay it's likely a package issue but struggling to pin point it.

Does input contains an item named model_select?

Edit: I expect this answers my question:

Could you share a sample table that represents the content of a csv file that 1) should work with the app, and 2) produces the same error?

1 Like

Sample dataset here:

| Larvae | Treatment | Dose | Time | Status |
|--------|-----------|------|------|--------|
| 1      | Tea Tree  | 6    | 96   | 1      |
| 2      | Tea Tree  | 6    | 96   | 0      |
| 3      | Tea Tree  | 6    | 96   | 0      |
| 16     | Tea Tree  | 12   | 48   | 1      |
| 17     | Tea Tree  | 12   | 48   | 1      |
| 18     | Tea Tree  | 12   | 48   | 1      |
| 31     | Tea Tree  | 30   | 48   | 1      |
| 32     | Tea Tree  | 30   | 72   | 1      |
| 33     | Tea Tree  | 30   | 72   | 1      |
| 46     | Rosemary  | 6    | 72   | 1      |
| 47     | Rosemary  | 6    | 72   | 1      |
| 48     | Rosemary  | 6    | 96   | 1      |
| 61     | Rosemary  | 12   | 48   | 1      |
| 62     | Rosemary  | 12   | 48   | 1      |
| 63     | Rosemary  | 12   | 72   | 1      |
| 76     | Rosemary  | 30   | 48   | 1      |
| 77     | Rosemary  | 30   | 48   | 1      |
| 78     | Rosemary  | 30   | 48   | 1      |
| 91     | Cedarwood | 6    | 96   | 1      |
| 92     | Cedarwood | 6    | 96   | 0      |
| 93     | Cedarwood | 6    | 96   | 0      |
| 106    | Cedarwood | 12   | 96   | 1      |
| 107    | Cedarwood | 12   | 96   | 1      |
| 108    | Cedarwood | 12   | 96   | 1      |
| 121    | Cedarwood | 30   | 96   | 1      |
| 122    | Cedarwood | 30   | 96   | 1      |
| 123    | Cedarwood | 30   | 96   | 0      |

Yes, think that's the one.

Thanks — could you share this table as a data frame or tibble? Just paste the output of

dput(sample_table)

here in a code block, where sample_table is the data frame or tibble corresponding to the table you shared above.

structure(list(Larvae = c(1L, 2L, 3L, 16L, 17L, 18L, 31L, 32L, 
33L, 46L, 47L, 48L, 61L, 62L, 63L, 76L, 77L, 78L, 91L, 92L, 93L, 
106L, 107L, 108L, 121L, 122L, 123L), Treatment = c("Tea Tree", 
"Tea Tree", "Tea Tree", "Tea Tree", "Tea Tree", "Tea Tree", "Tea Tree", 
"Tea Tree", "Tea Tree", "Rosemary", "Rosemary", "Rosemary", "Rosemary", 
"Rosemary", "Rosemary", "Rosemary", "Rosemary", "Rosemary", "Cedarwood", 
"Cedarwood", "Cedarwood", "Cedarwood", "Cedarwood", "Cedarwood", 
"Cedarwood", "Cedarwood", "Cedarwood"), Dose = c(6L, 6L, 6L, 
12L, 12L, 12L, 30L, 30L, 30L, 6L, 6L, 6L, 12L, 12L, 12L, 30L, 
30L, 30L, 6L, 6L, 6L, 12L, 12L, 12L, 30L, 30L, 30L), Time = c(96L, 
96L, 96L, 48L, 48L, 48L, 48L, 72L, 72L, 72L, 72L, 96L, 48L, 48L, 
72L, 48L, 48L, 48L, 96L, 96L, 96L, 96L, 96L, 96L, 96L, 96L, 96L
), Status = c(1L, 0L, 0L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 0L, 0L, 1L, 1L, 1L, 1L, 1L, 0L)), class = "data.frame", row.names = c(NA, 
-27L))
1 Like

This is the line that triggers the error: I can't speak to exactly how shiny executes the line — specifically, in what environment it executes the line — but it seems that a new environment is created downstream that does not contain the surv table. This means that ggsurvplot() cannot reconstruct the data from the fit value the way it could if this were an R script instead of a shiny app.

My guess is that including the data explicitly by using surv as the value of the data argument may fix the issue — could you try this, instead?

    # Plot the survival analysis using survminer
    surv_fit <- survfit(Surv(Time, Status) ~ Treatment + Dose, data = surv)
    plot_surv <- ggsurvplot(fit = surv_fit, data = surv, conf.int = FALSE, col = "Dose")

(Note that you have hardcoded the RHS of the formula used in the survfit() call, which may be inconsistent with the user's choice of model, but I didn't look into that.)

1 Like

Legend! That's done it. Been going round in circles with this for so long. Great pick up and thank you so much for the help.

(I'd hardcoded that part of the formula just for this simpler version of the app to get a solution to the plotting problem, but will revert now it's fixed!)