前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >「R」Shiny:响应式编程(四)执行时间控制与观察器

「R」Shiny:响应式编程(四)执行时间控制与观察器

作者头像
王诗翔呀
发布2020-07-04 17:37:06
1.8K0
发布2020-07-04 17:37:06
举报
文章被收录于专栏:优雅R优雅R

我们通过前面的文章已经对响应式编程的基本思路有所熟悉,这里我们将讨论更加高级的技术,它可以让我们更加合理地使用响应表达式。

为了更好地探索技术的基本思路,这里先对之前创建的模拟 Shiny 应用进行简化。我们将使用只有一个参数的分布,并让分布的样本数 n 保持一致。另外,我们也将移除图形控制。这样,我们用下面代码生成一个更小的 UI 和后端。

代码语言:javascript
复制
library(shiny)
library(ggplot2)

## 绘图函数
histogram <- function(x1, x2, binwidth = 0.1, xlim = c(-3, 3)) {
  df <- data.frame(
    x = c(x1, x2),
    g = c(rep("x1", length(x1)), rep("x2", length(x2)))
  )

  ggplot(df, aes(x, fill = g)) +
    geom_histogram(binwidth = binwidth) +
    coord_cartesian(xlim = xlim)
}

## 用户界面
ui <- fluidPage(
  fluidRow(
    column(3,
      numericInput("lambda1", label = "lambda1", value = 3),
      numericInput("lambda2", label = "lambda2", value = 3),
      numericInput("n", label = "n", value = 1e4, min = 0)
    ),
    column(9, plotOutput("hist"))
  )
)

## 后端
server <- function(input, output, session) {
  x1 <- reactive(rpois(input$n, input$lambda1))
  x2 <- reactive(rpois(input$n, input$lambda2))
  output$hist <- renderPlot({
    histogram(x1(), x2(), binwidth = 1, xlim = c(0, 40))
  })
}

shinyApp(ui, server)

生成的 Shiny 如下:

一个绘制两个泊松分布的简易 Shiny

对应的响应图如下:

响应图

定时失效

想象一下你想要让这个应用持续不断地生成模拟数据,以便于你可以看到一个动态模拟而不是一个静态地图。我们可以使用一个新的函数 reactiveTimer() 来增加更新的频率。

reactiveTimer() 是一个响应表达式,它有一个隐藏的输入:当前时间。该函数用于改变当前的更新定时。例如,下面代码使用了 500ms 作为更新间隔(2 次/秒)。这个速度已经足够的快,但也不至于让我们感到眩晕。

代码语言:javascript
复制
server <- function(input, output, session) {
  timer <- reactiveTimer(500)
  
  x1 <- reactive({
    timer()
    rpois(input$n, input$lambda1)
  })
  x2 <- reactive({
    timer()
    rpois(input$n, input$lambda2)
  })
  
  output$hist <- renderPlot({
    histogram(x1(), x2(), binwidth = 1, xlim = c(0, 40))
  })
}

shinyApp(ui, server)

它对应的响应图如下:

引入一个自动每半秒更新的输入依赖

这里注意在计算 x1()x2() 的响应表达式中使用 timer() 的方法:我们调用它,但不需要使用它的返回值。

点击时更新

在上面的场景中,思考一下如果代码本身的运行需要花费 1 秒钟会发生什么事情?由于我们每 0.5 秒自动更新数据的模拟,Shiny 会产生越来越多未能完成的工作,因此永远也无法处理完。相同的问题在你 Shiny 用户快速点击需要长时间运行的功能时也会出现。这些都可能会对 Shiny 造成很大的压力,而且当它处理这些挤压工作时,它无法对新的请求发出响应。最后,造成很差的用户体验。

这种问题出现时,我们一般会想要用户手动点击按钮来运行计算。这就是 actionButton() 的绝佳使用场景:

代码语言:javascript
复制
ui <- fluidPage(
  fluidRow(
    column(3,
      numericInput("lambda1", label = "lambda1", value = 3),
      numericInput("lambda2", label = "lambda2", value = 3),
      numericInput("n", label = "n", value = 1e4, min = 0),
      # 增加一个按钮
      actionButton("simulate", "Simulate!")
    ),
    column(9, plotOutput("hist"))
  )
)

为了使用上面设置的按钮,我们需要学习一个新的工具。想要知道为什么,我们先使用和上面相同的方法创建 Shiny,直接使用 simulate 为响应表达式引入依赖。

代码语言:javascript
复制
server <- function(input, output, session) {
  x1 <- reactive({
    input$simulate
    rpois(input$n, input$lambda1)
  })
  x2 <- reactive({
    input$simulate
    rpois(input$n, input$lambda2)
  })
  output$hist <- renderPlot({
    histogram(x1(), x2(), binwidth = 1, xlim = c(0, 40))
  })
}

shinyApp(ui, server)

该代码生成了一个带按钮的 Shiny。

带按钮的应用

它对应的响应图如下:

引入按钮的响应图

这个 Shiny 初看实现了我们的目标,点击按钮就可以重新生成模拟数据。然而,当其他输入变化时,结果也马上变化了!响应图也显示了这一点。我们仅仅是引入了新的依赖,而我们实际想要做的是取代之前的依赖。

为了解决这个问题,我们需要一个新的工具:它可以使用输入控件但不施加响应依赖eventReactive() 正是我们需要的,它有两个参数,第 1 个指定了运行的依赖,第二个指定执行的表达式。

让我们来改造下上面的 server 函数:

代码语言:javascript
复制
server <- function(input, output, session) {
  x1 <- eventReactive(input$simulate, {
    rpois(input$n, input$lambda1)
  })
  x2 <- eventReactive(input$simulate, {
    rpois(input$n, input$lambda2)
  })

  output$hist <- renderPlot({
    histogram(x1(), x2(), binwidth = 1, xlim = c(0, 40))
  })
}

shinyApp(ui, server)

这样,x1 将依赖于 simulate,而不依赖于 nlambda1x2 同样如此。

新的响应图如下:

使用 eventReactive 的响应图

灰色箭头显示了 x1x2 需要更新时它的计算依赖,但灰色箭头源头指向的参数已经不再是它的更新依赖,它们被 simulate 替换了!

观察器 observer

目前为止,我们关注的都是在应用内部发生的事情。但有时候我们需要在应用的外部做一些工作,如保存文件到一个共享网盘、发送数据到一个 Web API、更新数据库或向控制台打印调试信息。这些动作都不会影响我们应用的外观,因此我们不能使用输出和 render 函数。相反,我们需要使用观察器 observer

创建 observer 的方式有多种,这里我们看一下如何使用 observeEvent(),它是初学者一个重要的调试工具。

observeEvent()eventReactive() 非常相似。它有 2 个重要的参数:eventExprhandleExpr()。第 1 个参数是依赖的输入和表达式,第 2 个参数是要运行的代码。例如:下面对于 server() 的修改意味着每次 name 更新时,都会向控制台发送一条消息。

代码语言:javascript
复制
server <- function(input, output, session) {
  text <- reactive(paste0("Hello ", input$name, "!"))
  
  output$greeting <- renderText(text())
  observeEvent(input$name, {
    message("Greeting performed")
  })
}

observeEvent()eventReactive() 有两点重要的区别:

  • 我们不能将 observeEvent() 的结果赋值给一个变量
  • 我们不能从其他响应表达式中指向它

观察器和输出非常相关。我们可以认为输出有一个特殊的副作用:更新用户浏览器的 HTML。为了强调这种紧密性,我们将使用响应图相同的方式绘制它。如下图所示:

观察器看起来与输出控件相同

此处结束我们的响应式编程之旅。接下来的文章将通过创建一个大型的数据分析 Shiny 进行实战。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 优雅R 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 定时失效
  • 点击时更新
  • 观察器 observer
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档