on assignment of reactiveValues() empty dataframe

2019-08-07 20:16发布

问题:

Reproducable example:

server.R

library(shiny)

shinyServer( function(input, output, session) {

    myDf <- reactiveValues(myData = NULL)

    problematicDf <- reactiveValues(test = NULL)

    observeEvent(input$myButton, {
            myDf$myData <- df
    })


    observe({
            output$myTestUi <- renderUI({
                    selectInput(inputId = 'mySelection', 
                                label = 'Selection', 
                                choices = levels(myDf$myData$z),
                                multiple = T,
                                selected = c(levels(myDf$myData$z)[1])
                    )
            })
    })


    observe({
            problematicDf$test <- subset(myDf$myData, ((myDf$myData$z %in% input$mySelection)))
    })


    observe({
            str(problematicDf$test)
    })

    observe({
            as.matrix(x = problematicDf$test)
    })
})

ui.R

library(shiny)

shinyUI( bootstrapPage( 

    h3("Push the button"),
    actionButton(inputId = "myButton",
                 label = "clickMe"),

    h4("Split Merkmal"),
    uiOutput("myTestUi")

))

global.R

df <- data.frame(x = 1:10, y = 10:1, z = letters[1:10])
df$z <- as.factor(df$z)

This gives me:

NULL
[1] "NULL"
NULL
Warning: Unhandled error in observer: 'data' must be of a vector type, was 'NULL'
observe({
    as.matrix(x = problematicDf$test)
})

Only looking at the output of

observe({
  str(problematicDf$test)
  print(class(problematicDf$test))
  print(problematicDf$test$z)
})

after clicking on the action Button, without the as.matrix, I get:

NULL
[1] "NULL"
NULL
'data.frame':   0 obs. of  3 variables:
 $ x: int 
 $ y: int 
 $ z: Factor w/ 10 levels "a","b","c","d",..: 
[1] "data.frame"
factor(0)
Levels: a b c d e f g h i j
'data.frame':   1 obs. of  3 variables:
 $ x: int 1
 $ y: int 10
 $ z: Factor w/ 10 levels "a","b","c","d",..: 1
[1] "data.frame"
[1] a
Levels: a b c d e f g h i j

That is problematic. As you can see it creates first a df which is sort of empty, with placeholders, of class = NULL. And then, it fills this. However, it seems that other reactive functions, waiting for the problematicDf$test to be created kick in as soon as the empty df is created (with class = NULL). They do not update afterwards anymore. They only update when another selection is made. This causes (in my case) the program to crash since I need to keep on working and subsetting etc. with the so created data.frame.

How to handle this?! I could include an if else and check for class = NULL. But it seems to me that this is an unelegant way.

回答1:

Is there a reason for you to initialize myDf as NULL? If not, you could easily solve your problem assigning df at it's initialization. If you do so, your code won't need the button either.

And you shouldn't declare your renderUI inside observe. Whenever you chance the selected item, the observe is activated and it will set your selectInput to it's initial state (with only item 'a' selected).

Edit 1

# server.R

shinyServer( function(input, output, session) {
   # initialize myDf$myData with df and problematicDf as NULL
   myDf <- reactiveValues(myData = df)
   problematicDf <- reactiveValues(test = NULL)

   # you don't have to observe here. once the selectInput 
   # has been created, you can (un)select how many choices you want
   output$myTestUi <- renderUI({
       selectInput(inputId = 'myTestUi', 
                   label = 'Selection', 
                   choices = levels(myDf$myData$z),
                   multiple = T,
                   # this line determines that ONLY the first item on 
                   # c(levels(myDf$myData$z)[1] is selected on the selectInput initialization!
                   selected = c(levels(myDf$myData$z)[1])
       )
   })

   observe({
       problematicDf$test <- subset(myDf$myData, 
                             (myDf$myData$z %in% input$myTestUi))
   })

   observeEvent(input$myButton, {
       # the code in here will only run when the button is pushed!
       print("you should only put things here that you want to be flushed on the button being clicked!")
   })

})

Edit 2

So do initialize myDf$myData as NULL and assign df inside the observeEvent.

The renderUI will begin as a selectInput without any choices, but as soon as the button is pushed, choices will be assigned to it.

You mention some reactive functions that crash when the data is still NULL. There are lots of ways to deal with that. One way is to just use a if(is.null(myDf$myData)) return(NULL) as you mentioned. Might not be the most elegant way but usually works just fine. Other ways may include isolate or an observeEvent (it doesn't react to NULL inputs by default). It depends on how your app work as a whole.

But it seems you haven't fully understood how reactivity and other shiny elements work. Try reading the rstudio articles, specially the 'reactive programming' part (http://shiny.rstudio.com/articles/).