关于RShiny中反应式表达式与普通函数的代码差异疑问
为什么用普通函数替代Shiny反应式表达式会导致不同样本被使用?
我正在通过Hadley Wickham所著《Mastering R Shiny》学习R Shiny。在第3章中,作者用代码示例阐释反应式表达式(注:freqpoly与t_test为作者预定义函数,并非问题重点):
初始代码
server <- function(input, output, session) { output$hist <- renderPlot({ x1 <- rnorm(input$n1, input$mean1, input$sd1) x2 <- rnorm(input$n2, input$mean2, input$sd2) freqpoly(x1, x2, binwidth = input$binwidth, xlim = input$range) }, res = 96) output$ttest <- renderText({ x1 <- rnorm(input$n1, input$mean1, input$sd1) x2 <- rnorm(input$n2, input$mean2, input$sd2) t_test(x1, x2) }) }
使用反应式表达式的优化代码
作者提出使用反应式表达式可让freqpoly与t_test共用同一数据集:
server <- function(input, output, session) { x1 <- reactive(rnorm(input$n1, input$mean1, input$sd1)) x2 <- reactive(rnorm(input$n2, input$mean2, input$sd2)) output$hist <- renderPlot({ freqpoly(x1(), x2(), binwidth = input$binwidth, xlim = input$range) }, res = 96) output$ttest <- renderText({ t_test(x1(), x2()) }) }
用普通函数替代反应式的反例
随后作者指出不应以普通函数替代反应式表达式,原因是“任何输入变更都会导致所有输出重新计算,且t检验与频率多边形将使用不同样本”,对应的代码如下:
server <- function(input, output, session) { x1 <- function() rnorm(input$n1, input$mean1, input$sd1) x2 <- function() rnorm(input$n2, input$mean2, input$sd2) output$hist <- renderPlot({ freqpoly(x1(), x2(), binwidth = input$binwidth, xlim = input$range) }, res = 96) output$ttest <- renderText({ t_test(x1(), x2()) }) }
我的疑问是:在第三段代码中,我认为输入更新时x1与x2仅会计算一次,随后传递给renderPlot()与renderText(),因此两者应使用相同的模拟数据集,这与作者的表述矛盾,恳请帮忙解释其中的问题。
核心原因:普通函数与反应式表达式的执行逻辑差异
问题出在普通函数每次被调用时都会重新执行内部代码,而反应式表达式在同一反应周期内会缓存计算结果,只执行一次。
具体到第三段代码:
- 当某个输入(比如
input$n1)发生变化时,Shiny会触发所有依赖该输入的输出重新计算——也就是output$hist和output$ttest都会重新运行。 - 对于
output$hist,它会调用x1()和x2(),这两个函数会各自执行rnorm()生成新的随机样本,然后传给freqpoly。 - 紧接着,
output$ttest也会调用x1()和x2(),这时候函数又会重新执行rnorm(),生成另一组全新的随机样本,传给t_test。
也就是说,renderPlot和renderText是两个独立的计算过程,各自调用普通函数时都会重新生成随机数,自然会得到不同的样本。
而反应式表达式的机制是:
- 当输入变化触发反应时,
x1()和x2()会先计算一次,结果被缓存下来。 - 不管
renderPlot还是renderText调用它们,都会直接使用缓存的结果,不会重新执行rnorm()。 - 只有当依赖的输入再次变化时,反应式表达式才会重新计算并更新缓存。
验证方式
你可以在普通函数里加个打印语句来验证:
server <- function(input, output, session) { x1 <- function() { cat("生成x1样本\n") rnorm(input$n1, input$mean1, input$sd1) } x2 <- function() { cat("生成x2样本\n") rnorm(input$n2, input$mean2, input$sd2) } output$hist <- renderPlot({ freqpoly(x1(), x2(), binwidth = input$binwidth, xlim = input$range) }, res = 96) output$ttest <- renderText({ t_test(x1(), x2()) }) }
当你修改输入时,控制台会打印四次:生成x1样本、生成x2样本(来自renderPlot),然后又生成x1样本、生成x2样本(来自renderText),这直接证明了普通函数被调用了多次,每次都生成新样本。
内容的提问来源于stack exchange,提问作者InTheSearchForKnowledge
相关产品推荐
相关产品推荐

