后起之秀奔涌而至,欢迎大家在《生信技能树》的舞台分享自己的心得体会!
上面是shiny团队的稿件
用反应表达式,快速构建,模块化app
⚠️此篇的线上数据可能有时无法顺利抓取,要多试几次
用户会赞叹快速的app,但是你的app有大量运算影响速度了该怎么办呢?
此篇将教你如何用反应表达式精简你的app
反应表达式使你能控制何时更新何处的代码,防止不必要的运算拖慢app的速度
准备工作
runApp("stockVis")
启动appStockVis 用R的quantmod
包,如果没有应该安装install.packages("quantmod")
stockVis应用程序通过股票代码查找股票价格,并将结果显示为折线图
1.选择一个股票进行考察
2.选择日期范围进行检查
3.选择是画股票价格还是log后的股票价格
4.选择是否为通胀修正价格
注意 “Adjust prices for inflation” 选择框还不能用
此篇接下来的目标就是修复这个选择框
默认情况,stockVis展示SPY股票(S&P 500),可以换上其他yahoo金融识别的代码。
stockVis主要依赖两个来自quantmod包的函数
1.使用getSymbols
直接从网站下载数据到R,比如Yahoo finance,Federal Reserve Bank of St. Louis
2.使用chartSeries
来绘价格图
stockVis也依赖于helpers.R, 包含适应通货膨胀调整股票价格的函数
stockVis 包含一些新的小工具
dateRangeInput
创建checkboxInput
创建,选择框小工具很简单,被勾上会返回TRUE
,反之FALSE
在ui
对象中,选择框的name
参数是log
和adjust
,意味着在server函数中你可以使用input
adjust找到他们。(l3和l4讲过)
stockVis app有一个问题
当你点击“Plot y axis on the log scale.”会发生检查,input$log的值会发生改变,会引发renderPlot
部分的表达式重新运行:
output$plot <- renderPlot({
data <- getSymbols(input$symb, src = "yahoo",
from = input$dates[1],
to = input$dates[2],
auto.assign = FALSE)
chartSeries(data, theme = chartTheme("white"),
type = "line", log.scale = input$log, TA = NULL)
})
每次renderPlot
重新运行,将会
1.重新使用 getSymbols 从雅虎金融抓取数据
2.重新用正确的坐标轴画图
这不好,因为你不需要重新抓取数据重新画图. 事实上,雅虎金融会切断你的连接,如果过于频繁的抓取数据。当然主要还是不必要的步骤,会拖慢app的速度,消耗服务器带宽。
反应表达式使你能限制重新运行哪个部分。
一个反应表达式是 一个使用 小工具的输入 返回 一个值 的R表达式。每当小工具发生改变,反应表达式就会更新这个值。
创建反应表达式使用reactive
函数,把R表达式用花括号括起来,就喝render*
函数一样
例如,获取数据的反应表达式
dataInput <- reactive({getSymbols(input$symb, src = "yahoo",
from = input$dates[1],
to = input$dates[2],
auto.assign = FALSE)})
但你运行表达式,他会使用getSymbols
然后返回结果,一个价格数据框。
在renderPlot
中调用dataInput
()你能用表达式获取价格数据。
output$plot <- renderPlot({
chartSeries(dataInput(), theme = chartTheme("white"),
type = "line", log.scale = input$log, TA = NULL)
})
反应表达式比常规的R函数聪明一点点,他们能缓存他们的值,也知道他们的值何时过时。
也就是说,这意味着第一次运行反应表达式,表达式将会把结果存到计算机的内存中,下次调用反应表达式的时候,就能不做运算的返回这个保存好的结果,也就加速了app
反应表达式将只返回更新的结果,当反应表达式知道结果淘汰了时(小工具发生改变),才会重新计算一个结果,并返回新的结果并保存,直到下次更新。
梳理一下上述行为过程
此举能够被用作防止shiny重新运行不必要的代码
思考如下stockVis app中,反应表达式如何生效
server <- function(input, output) {
dataInput <- reactive({
getSymbols(input$symb, src = "yahoo",
from = input$dates[1],
to = input$dates[2],
auto.assign = FALSE)
})
output$plot <- renderPlot({
chartSeries(dataInput(), theme = chartTheme("white"),
type = "line", log.scale = input$log, TA = NULL)
})
}
当你单击“Plot y axis on the log scale”,input$log 将会改变,renderPlot 将会重新处理
renderPlot
将会调用dataInput()
dataInput
将会检查dates
和 symb
小工具没有变化dataInput
将会返回它保存的数据,没有重新到网站抓取renderPlot
将会重新画图,使用正确的坐标如果用户改变了symb 小工具的股票代码会发生什么?
这将会使renderPlot 画的图过期,但是renderPlot不再调用input
symb的变化已经使得图过期了吗?
当然,shiny会知道并且会重新作图。shiny会持续追踪output所依赖的那个反应表达式,也包括那个小工具。shiny会重建对象,一旦:
render*
函数中,input值改变了render*
函数中,反应表达式过期了将反应表达式作为一条链中的连接,把input值和output对象连了起来。output中的对象会响应链中任何下游的更改(你可能会塑造一个长链,因为反应表达式可能包含其他反应表达式)
为何仅仅从reactive或者render*调用反应表达式,只有这些R函数能处理反应输出,没有警告的改变。事实上,shiny会防止你在这些函数之外使用反应表达式
是时候修复损坏的选择框,“Adjust prices for inflation.”,让用户能切换价格是否适应通货膨胀
helper.R 中的adjust
函数使用由圣路易斯联邦储备银行提供的Consumer Price Index 数据,将历史价格转为当前价格,是如何用代码实现呢?
下面是一个解决方法,但是不理想,请指出为什么?再次提醒应该使用input$log
server <- function(input, output) {
dataInput <- reactive({
getSymbols(input$symb, src = "yahoo",
from = input$dates[1],
to = input$dates[2],
auto.assign = FALSE)
})
output$plot <- renderPlot({
data <- dataInput()
if (input$adjust) data <- adjust(dataInput())
chartSeries(data, theme = chartTheme("white"),
type = "line", log.scale = input$log, TA = NULL)
})
}
我的答案:
上述代码中的adjust部分可以不用在renderPlot中
参考答案:
Adjust在renderPlot内部被调用。如果选中了调整框,则每次您从正常y刻度切换到已记录的y刻度时,应用都会重新调整所有价格。这种调整是不必要的工作。
通过加新的反应表达式到app能解决这个问题,反应表达式应该从dataInput取值,然后返回一个数据副本(要不要adjust视情况而定)。
你能加快你的app,使用反应表达式模块化代码
reactive({ })
我的练习答案
# Load packages ----
library(shiny)
library(quantmod)
# Source helpers ----
source("helpers.R")
# User interface ----
ui <- fluidPage(
titlePanel("stockVis"),
sidebarLayout(
sidebarPanel(
helpText("Select a stock to examine.
Information will be collected from Yahoo finance."),
textInput("symb", "Symbol", "SPY"),
dateRangeInput("dates",
"Date range",
start = as.Date('2013-01-01',format = '%Y-%m-%d'),
end = as.character(Sys.Date())),
br(),
br(),
checkboxInput("log", "Plot y axis on log scale",
value = FALSE),
checkboxInput("adjust",
"Adjust prices for inflation", value = FALSE)
),
mainPanel(plotOutput("plot"))
)
)
# Server logic
server <- function(input, output) {
dataInput <- reactive({
getSymbols(input$symb, src = "yahoo",
from = input$dates[1],
to = input$dates[2],
auto.assign = FALSE)
})
dataAdjust <- reactive({
data <- dataInput()
if (input$adjust) data <- adjust(dataInput())
data
})
output$plot <- renderPlot({
chartSeries(dataAdjust(), theme = chartTheme("white"),
type = "line", log.scale = input$log, TA = NULL)
})
}
# Run the app
shinyApp(ui, server)
参考答案
server <- function(input, output) {
dataInput <- reactive({
getSymbols(input$symb, src = "yahoo",
from = input$dates[1],
to = input$dates[2],
auto.assign = FALSE)
})
finalInput <- reactive({
if (!input$adjust) return(dataInput())
adjust(dataInput())
})
output$plot <- renderPlot({
chartSeries(finalInput(), theme = chartTheme("white"),
type = "line", log.scale = input$log, TA = NULL)
})
}
Reference:
Shiny - Use reactive expressions