基于R语言的shiny网页工具开发基础系列-06
The following article is from 生信技能树 Author 生信技能树
L6-反应表达式
用反应表达式,快速构建,模块化app
⚠️此篇的线上数据可能有时无法顺利抓取,要多试几次
使用反应表达式
用户会赞叹快速的app,但是你的app有大量运算影响速度了该怎么办呢?
此篇将教你如何用反应表达式精简你的app
反应表达式使你能控制何时更新何处的代码,防止不必要的运算拖慢app的速度
准备工作
在工作目录创建一个名为stockVis的文件夹 下载这两个文件并放到stockVis文件夹中 使用命令 runApp("stockVis")
启动app
StockVis 用R的quantmod
包,如果没有应该安装install.packages("quantmod")
一个新的app-stockVis
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函数中你可以使用inputadjust找到他们。(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不再调用inputsymb的变化已经使得图过期了吗?
当然,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,使用反应表达式模块化代码
一个反应表达式从input取值或者来自其他反应表达式,并返回新的值 反应表达式会保存他们的结果,只有在输入改变时重新运算 构建反应表达式使用 reactive({ })
调用反应表达式可以用表达式名字加圆括号的形式 只在其他反应表达式内部或者render*函数内部调用反应表达式
我的练习答案
# 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