1

I am trying to create a way to track opening and closing of the breathing organ (i.e. a mouth) of several animals at the same time over the course of 45 minutes. The goal is to be able to calculate the total open time and frequency of opening for each animal. Basically, the idea is to have several stopwatches operating in parallel, while tracking two lists of values per animal: open time and close time.

The experiment would ideally go like this: I start the experiment and therefore the stopwatch. Every time animal 1 opens its breathing organ, I press open, and once it closes its breathing organ, I press close. The time of each, relative to the stopwatch started at the beginning of the experiment, are recorded in a dataframe for animal 1. This process repeats 10-15 times throughout 45 minutes. At the same time, another animal is opening and closing its breathing organ, and a separate dataframe for animal 2 is created using a different set of buttons. I would like to have this be possible for up to 10 animals simultaneously.

I have been able to make the stopwatches (example code below) using a watch function, as well as include action buttons that output text corresponding to the difference in system time between start time of the experiment and time of pressing the open or close buttons. However, I am unsure of how to store these values in a dataframe for each animal.

I have looked around stackoverflow and found nothing that works, including this thread: r Shiny action button and data table output and this one: Add values to a reactive table in shiny

Let me know if you need any more info! Thanks in advance.

library(lubridate)
library(shiny)
library(DT)

# stopwatch function ----

stop_watch = function() {
  start_time = stop_time = open_time = close_time = NULL
  start = function() start_time <<- Sys.time()
  stop = function() {
    stop_time <<- Sys.time()
    as.numeric(difftime(stop_time, start_time))
  }
  open = function() {
    open_time <<- Sys.time()
    as.numeric(difftime(open_time, start_time))
  }
  close = function() {
    close_time <<- Sys.time()
    as.numeric(difftime(close_time, start_time))
  }
  list(start=start, open=open, close=close, stop=stop)
}
watch = stop_watch()

# ui ----

ui <- fluidPage(
  titlePanel("Lymnaea stopwatch"),

  sidebarLayout(
    sidebarPanel(

      selectInput(
        "select",
        label = "Number of animals",
        choices = c(1,2,3,4,5,6,7,8,9,10),
        selected = c("1")
      )
  # action button conditionals ----      
    ),
    mainPanel(
      h4("Start/Stop Experiment:"),
      actionButton('start1',"Start"),
      actionButton('stop1', "Stop"),
      textOutput('initial1'),
      textOutput('start1'),
      textOutput('stop1'),
      textOutput('stoptime1'),

     conditionalPanel(
       h4("Animal 1"),
      condition = "input.select == '1'||input.select == '2'||input.select == '3'||input.select == '4'||input.select == '5'||input.select == '6'||input.select == '7'||input.select == '8'||input.select == '9'||input.select == '10'",
       actionButton('open1', "Open"),
       actionButton('close1', "Close"),
       textOutput('open1'),
       textOutput('opentime1'),
       textOutput('close1'),
       textOutput('closetime1'),

     )

  )
)
)

# server ----

server <- function(input, output, session) {

values <- reactiveValues()

values$df <- data.frame(colnames(c("Open", "Close")))

newEntry <- observe({
  if(input$open1 > 0) {
    newLine <- isolate(c(({watch$start()})))
    isolate(values$df <- rbind(values$df, newLine))
  }
})

output$table <- renderTable({values$df})

  # n = 1 animal  ----
  observeEvent(input$start1, {
    watch$start()
    output$initial1 <- renderText(
      "Timer started."
      )
  })

  observeEvent(input$open1, {
    watch$open()
    output$open1 <- renderText(
      "Time of opening:"
    )
    output$opentime1 <- renderText({
      watch$open()
    })
 })

  observeEvent(input$close1, {
    watch$close()
    output$close1 <- renderText({
      "Time of closing:"
    })
    output$closetime1 <- renderText({
      watch$close()
    })
  })

}

shinyApp(ui, server)
jbandura
  • 127
  • 1
  • 6
  • 2
    That's quite a code block ... are you able to provide a reduced problem? That much code can be daunting for some (like me), since it takes more time to understand all of the components involved. – r2evans Feb 19 '20 at 18:53
  • It might make sense to limit your example to 2 animals to begin with for development and focus? Also, can you clarify the behavior you want in this application? Maybe give a specific example, describing what happens in order of events? – Ben Feb 19 '20 at 19:18
  • Hi, I've edited the code and text to better describe my experiment and desired outcome. The stopwatches work even for 10 animals, I'm just not sure how to store the outputted values from `watch$open` and `watch$close`. – jbandura Feb 19 '20 at 20:15
  • there's a missing parenthesis at the end of the ```ui```, can't edit because it does not make enough characters – bretauv Feb 19 '20 at 20:23
  • @bretauv fixed, thanks! – jbandura Feb 19 '20 at 20:24

1 Answers1

1

I think there may be a number of ways you could set this up differently.

One recommendation I have is to avoid putting output inside of your observers.

Another is calling your stopwatch functions only once - for data integrity, to make sure your display and data collected are the same.

In addition, it might be helpful to have a single data table store all of your open and close events, with an additional column for animal number. It would be relatively easy to work with a table like this for future analyses.

Here is a quick example you can try out, just to get a sense of the behavior. Please also add tableOutput('table') to your ui after your conditionalPanel to view the data frame.

# ui ----

ui <- fluidPage(
  titlePanel("Lymnaea stopwatch"),

  sidebarLayout(
    sidebarPanel(

      selectInput(
        "select",
        label = "Number of animals",
        choices = c(1,2,3,4,5,6,7,8,9,10),
        selected = c("1")
      )
      # action button conditionals ----      
    ),
    mainPanel(
      h4("Start/Stop Experiment:"),
      actionButton('start1',"Start"),
      actionButton('stop1', "Stop"),
      textOutput('initial1'),
      textOutput('start1'),
      textOutput('stop1'),
      textOutput('stoptime1'),

      conditionalPanel(
        h4("Animal 1"),
        condition = "input.select == '1'||input.select == '2'||input.select == '3'||input.select == '4'||input.select == '5'||input.select == '6'||input.select == '7'||input.select == '8'||input.select == '9'||input.select == '10'",
        actionButton('open1', "Open"),
        actionButton('close1', "Close"),
        textOutput('open1'),
        textOutput('opentime1'),
        textOutput('close1'),
        textOutput('closetime1'),
      ),
      tableOutput('table')
    )
  )
)

# server ----

server <- function(input, output, session) {

  values <- reactiveValues(df = data.frame(Animal = integer(),
                                           Event = character(),
                                           Time = as.POSIXct(character()),
                                           stringsAsFactors = FALSE),
                           timer = "Timer Off")

  output$initial1 <- renderText({
    values$timer
  })

  output$opentime1 <- renderText({
    paste("Opened at:", tail(values$df[values$df[["Animal"]] == 1 & values$df[["Event"]] == "Open", "Time"], 1))
  })

  output$closetime1 <- renderText({
    paste("Closed at:", tail(values$df[values$df[["Animal"]] == 1 & values$df[["Event"]] == "Close", "Time"], 1))
  })

  output$table <- renderTable({
    values$df
  })

  observeEvent(input$start1, {
    watch$start()
    values$timer <- "Timer Started"
  })

  observeEvent(input$open1, {
    values$df <- rbind(values$df, data.frame(Animal = 1, Event = "Open", Time = watch$open()))
  })

  observeEvent(input$close1, {
    values$df <- rbind(values$df, data.frame(Animal = 1, Event = "Close", Time = watch$close()))
  })

}

This could be scaled up for 10 animals, and there are alternative ways to provide feedback to user on data.

Let me know what you think, and if this is in the direction you had in mind.

Ben
  • 28,684
  • 5
  • 23
  • 45
  • 1
    thank you so much! This is exactly what I had in mind. I just tried scaling to 2 animals and it works perfectly. I'll let you know if it works in the experimental setting! – jbandura Feb 20 '20 at 18:01
  • Glad to hear! Just an FYI - as you scale up, you might want to consider a dynamic approach to creating buttons and observers, like in [this example](https://stackoverflow.com/questions/38950886/generate-observers-for-dynamic-number-of-inputs). This would be a more DRY approach, more flexible, and could be more efficient, so you don't need to duplicate all of your `server` `output` and `observeEvent` 10 times. Just a thought. – Ben Feb 20 '20 at 22:15