Shiny Basic
2023-04-17
1 Introduction
1.2 创建shinyapp
- R studio:
File-New Project-New Directory-Shiny Web Application - R code: 已有app.R file,输入
shinyapp然后Shift+tab
ui <- fluidPage(
"Hello, world!"
)
server <- function(input, output, session) {
}
shinyApp(ui, server)Figure 1.1: The very basic shiny app you’ll see when you run the code above
1.4 添加UI
ui <- fluidPage(
selectInput("dataset", label = "Dataset", choices = ls("package:datasets")),
verbatimTextOutput("summary"),
tableOutput("table")
)fluidPage()设置页面布局selectInput()控制输入verbatimTextOutput()和tableOutput()控制输出
Figure 1.2: The datasets app with UI
1.5 添加server
server <- function(input, output, session) {
output$summary <- renderPrint({
dataset <- get(input$dataset, "package:datasets")
summary(dataset)
}) #对data的统计结果
output$table <- renderTable({
dataset <- get(input$dataset, "package:datasets")
dataset
})#data数据
}- 赋值操作符(
<-)的左侧,output$ID,表明正在定义具有该ID的Shiny输出 - 赋值的右侧,使用特定的
render{Type}函数来包装代码,每个render{Type}一般会对应一个{type}Output
Figure 1.3: Now that we’ve provided a server function that connects outputs and inputs, we have a fully functional app
1.6 利用reactive expression减少重复代码
- 在上节代码中(Section 1.5),
dataset <- get(input$dataset, "package:datasets")重复出现了两次,每次都需要提取一次数据 - 为了简化代码,可以在
reactive({...})里包装一段代码,然后将值赋给一个变量 - 代码只会运行一次,之后每次调用该变量都是使用之前运行的缓存记录
server <- function(input, output, session) {
# 创建reactive expression
dataset <- reactive({
get(input$dataset, "package:datasets")
})
output$summary <- renderPrint({
# 像调用函数一样调用reactive expression
summary(dataset())
})
output$table <- renderTable({
dataset()
})
}1.7 shiny cheatsheet
更多关于shiny的内容可参见 shiny cheatsheetFigure 1.4: Shiny cheatsheet, available from https://www.rstudio.com/resources/cheatsheets/
2 Basic UI
2.1 输入 Inputs
所有的输入类型包括
- 文本型 (Section 2.1.2)
- 数值型 (Section 2.1.3)
- 日期型 (Section 2.1.4)
- 选择型 (Section 2.1.5)
- 文件型 (Section 2.1.6)
- 操作型 (Section 2.1.7)
2.1.1 基本结构
- 所有的输入的第一个参数都是要定义一个名称
inputId(如name),之后在server段通过input$name调用该输入内容,inputID必须:- 只包含数字、字母和下划线
- 独一无二的
- 大部分输入的第二个参数是
label,在app内提示用户输入信息 - 第三个参数是
value,可以设置默认输入
sliderInput("min", "Limit (minimum)", value = 50, min = 0, max = 100)2.1.2 自由文本类型输入
textInput()输入文本passwordInput()输入密码textAreaInput()输入大段文本
ui <- fluidPage(
textInput("name", "What's your name?"),
passwordInput("password", "What's your password?"),
textAreaInput("story", "Tell me about yourself", rows = 3)
)Figure 2.1: 自由文本类型输入
- 除了
label,也可以采用placeholder提示输入信息
textInput("name",placeholder = 'Your name')Figure 2.2: placeholder提示输入信息
2.1.3 数值输入
numericInput()数值型文本框sliderInput()数值型滑块条
ui <- fluidPage(
numericInput("num", "Number one", value = 0, min = 0, max = 100),
sliderInput("num2", "Number two", value = 50, min = 0, max = 100),
#如果sliderInput的value是两个数值的向量,则指定一个范围
sliderInput("rng", "Range", value = c(10, 20), min = 0, max = 100)
)Figure 2.3: 数值输入
- 查看更多定义sliderInput样式的方法
?sliderInput- Detials for slider Input
#用sliderInput输入日期
sliderInput("deliver", "When should we deliver?",
value = as.Date('2020-09-17'),timeFormat = '%F',
min = as.Date('2020-09-16'), max = as.Date('2020-09-23')),Figure 2.4: 用sliderInput输入日期
#animation sliderInput
sliderInput("animation", "Looping Animation:",
min = 0, max = 100,
value = 5, step = 5,
animate =
animationOptions(interval = 5, loop = TRUE))2.1.4 日期输入
dateInput()输入一个日期dateRangeInput()输入一个日期范围
ui <- fluidPage(
dateInput("dob", "When were you born?"),
dateRangeInput("holiday", "When do you want to go on vacation next?")
)Figure 2.5: 日期输入
2.1.5 选择输入
selectInput()在下拉框里选择radioButtons()在选项按钮里选择
animals <- c("dog", "cat", "mouse", "bird", "other", "I hate animals")
ui <- fluidPage(
selectInput("state", "What's your favourite state?", state.name),
radioButtons("animal", "What's your favourite animal?", animals)
)Figure 2.6: 选择输入
radioButtons()可以使用非文本的选项,用choiceNames设置在app里的展示,choiceValues设置对应的返回值
ui <- fluidPage(
radioButtons("rb", "Choose one:",
choiceNames = list(
icon("angry"),
icon("smile"),
icon("sad-tear")
),
choiceValues = list("angry", "happy", "sad")
)
)Figure 2.7: 非文本的按钮选择
selectInput()设置multiple = TRUE可以同时选择多个选项
ui <- fluidPage(
selectInput(
"state", "What's your favourite state?", state.name,
multiple = TRUE
)
)Figure 2.8: 多个选择的选择输入
selectInput()在选项中设置子集
selectInput("state", "Choose a state:",
list(`East Coast` = list("NY", "NJ", "CT"),
`West Coast` = list("WA", "OR", "CA"),
`Midwest` = list("MN", "WI", "IA"))
)radioButtons()没有办法同时选择多项,但是可以用checkboxGroupInput()进行多项选择
ui <- fluidPage(
checkboxGroupInput("animal", "What animals do you like?", animals)
)Figure 2.9: checkbox多项选择输入
checkboxInput()单项的勾选框
ui <- fluidPage(
checkboxInput("cleanup", "Clean up?", value = TRUE),
checkboxInput("shutdown", "Shutdown?")
)Figure 2.10: checkbox单项选择输入
2.1.7 操作输入
actionButton()操作按钮actionLink()操作链接- 操作链接和按钮常与
server函数中的observeEvent()或eventReactive()配对。
ui <- fluidPage(
actionButton("click", "Click me!"),
actionButton("drink", "Drink me!", icon = icon("cocktail"))
)Figure 2.12: 操作按钮
- 可以通过class参数设置按钮的外观
- 样式设置:
"btn-primary","btn-success","btn-info","btn-warning", or"btn-danger" - 大小设置:
"btn-lg","btn-sm","btn-xs" - 使用
"btn-block"使按钮跨越嵌入元素的整个宽度
- 样式设置:
ui <- fluidPage(
fluidRow(
actionButton("click", "Click me!", class = "btn-danger"),
actionButton("drink", "Drink me!", class = "btn-lg btn-success")
),
fluidRow(
actionButton("eat", "Eat me!", class = "btn-block")
)
)Figure 2.13: 操作按钮样式
2.2 输出 Outputs
所有的输出类型包括
2.2.1 文本输出
textOutput()简单文本输出,和renderText()联用,用于将结果连成简单的字符串verbatimTextOutput()代码和控制输出,和renderPrint()联用,输出R代码的结果
ui <- fluidPage(
textOutput("text"),
verbatimTextOutput("code")
)
server <- function(input, output, session) {
output$text <- renderText({
"Hello friend!"
})#简单文本输出
output$code <- renderPrint({
summary(1:10)
})#代码输出
}Figure 2.14: 文本输出
{}用于包含多条代码的render语句,上述代码可以简化成
server <- function(input, output, session) {
output$text <- renderText("Hello friend!")
output$code <- renderPrint(summary(1:10))
}renderText()和verbatimTextOutput()的区别
ui <- fluidPage(
textOutput("text"),
verbatimTextOutput("print")
)
server <- function(input, output, session) {
output$text <- renderText("hello!")
output$print <- renderPrint("hello!")
}Figure 2.15: 两种文本输出的不同
2.2.2 表格输出
tableOutput()和renderTable()输出静态表格dataTableOutput()和renderDataTable()输出动态表格- reactable包中的
reactableOutput()和renderReactable()提供了更多自定义动态表格的选项
ui <- fluidPage(
tableOutput("static"),
dataTableOutput("dynamic")
)
server <- function(input, output, session) {
output$static <- renderTable(head(mtcars))
output$dynamic <- renderDataTable(mtcars, options = list(pageLength = 5))
}Figure 2.16: 表格输出
2.2.3 图形输出
- 使用
plotOutput()和renderPlot()输出图形
ui <- fluidPage(
plotOutput("plot", width = "400px")
)
server <- function(input, output, session) {
output$plot <- renderPlot(plot(1:5), res = 96,
width = 700,height = 300,alt="a scatterplot of five random numbers")
)
}#建议始终设置res =96,因为这将使您的Shiny图表尽可能接近您在RStudio中看到的。Figure 2.17: 图形输出
2.2.4 下载输出
- 可以使用
downloadButton()或downloadLink()让用户下载文件 - 参见更多信息
3 Basic reactivity
3.1 serve 函数
- 基本的shiny app组成
library(shiny)
ui <- fluidPage(
# front end interface
)
server <- function(input, output, session) {
# back end logic
}
shinyApp(ui, server)ui对象包含html格式的用户界面,每个用户有相同的uiserver函数控制app后台操作,每个用户的server不同。每个新的session开始时,shiny都会唤起一次server函数- server函数包括三个参数:
input,output,sessioninput$ID调用UI输入的内容- 不能在server里直接调用
input$ID,需要在reactive环境中调用如:renderText()或reactive() - 不能给
input$ID赋值,必要时可以采用updateNumericInput()
- 不能在server里直接调用
outnput$ID调用UI定义的输出内容,通常与render{}()系列函数联用设置输出内容代码render{}()函数创建一个reactive环境,可以调用input参数内容render{}()函数将输出结果转化成html格式,便于在web上显示
3.2 reactive 编程
- reactive programming:输入变化时,输出结果也自动跟着变化
- imperative programming 命令式编程,提出命令,执行结果 (R script)
- declarative programmin 声明式编程,表达更高层次的目标或描述重要的限制,并依靠其他人来决定如何和/或何时将其转化为行动 (shiny)
- 执行顺序不是逐条执行,例如,下述代码不会报错:
server <- function(input, output, session) {
output$greeting <- renderText(string())
#注意这里要像调用函数一样调用reactive expression
string <- reactive(paste0("Hello ", input$name, "!"))
#string定义在调用后
}- 上述代码的执行顺序(reactive graph)
Figure 3.1: A reactive expression is drawn with angles on both sides because it connects inputs to outputs
3.3 reactive expression
响应表达式使用reative()函数创建。可以简化代码(避免重复代码,参见Section 1.6和Section 3.3.3,同时具有inputs和outputs的特性
- 与input一样,可以在output中使用reactive expression的结果。
- 与output一样,reactive expression依赖于input,并自动知道何时需要更新。
Figure 3.2: Inputs and expressions are reactive producers; expressions and outputs are reactive consumers
3.3.1 一个简单的EDA(Exploratory Data Analysis)例子
下面的例子分析两组数据的分布,并用T检验判断它们的差异是否显著
- 创建函数绘图和输出结果
library(ggplot2)
freqpoly <- 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, colour = g)) +
geom_freqpoly(binwidth = binwidth, size = 1) +
coord_cartesian(xlim = xlim)
}
t_test <- function(x1, x2) {
test <- t.test(x1, x2)
# use sprintf() to format t.test() results compactly
sprintf(
"p value: %0.3f\n[%0.2f, %0.2f]",
test$p.value, test$conf.int[1], test$conf.int[2]
)
}- 创建两组随机数使用上述函数
x1 <- rnorm(100, mean = 0, sd = 0.5)
x2 <- rnorm(200, mean = 0.15, sd = 0.9)
freqpoly(x1, x2)cat(t_test(x1, x2))
## p value: 0.053
## [-0.31, 0.00]
#> p value: 0.005
#> [-0.39, -0.07]3.3.2 将常规代码转化成shiny app
- 创建UI界面
ui <- fluidPage(
#输入布局
fluidRow(
#第一个变量数据设置
column(4,
"Distribution 1",
numericInput("n1", label = "n", value = 1000, min = 1),
numericInput("mean1", label = "µ", value = 0, step = 0.1),
numericInput("sd1", label = "σ", value = 0.5, min = 0.1, step = 0.1)
),
#第二个变量数据设置
column(4,
"Distribution 2",
numericInput("n2", label = "n", value = 1000, min = 1),
numericInput("mean2", label = "µ", value = 0, step = 0.1),
numericInput("sd2", label = "σ", value = 0.5, min = 0.1, step = 0.1)
),
#输出图形设置
column(4,
"Frequency polygon",
numericInput("binwidth", label = "Bin width", value = 0.1, step = 0.1),
sliderInput("range", label = "range", value = c(-3, 3), min = -5, max = 5)
)
),
#输出布局
fluidRow(
#输出分布图
column(9, plotOutput("hist")),
#输出t-test结果
column(3, verbatimTextOutput("ttest"))
)
)- 创建server函数
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)
#t-test检验
output$ttest <- renderText({
x1 <- rnorm(input$n1, input$mean1, input$sd1)
x2 <- rnorm(input$n2, input$mean2, input$sd2)
t_test(x1, x2)
})
}Figure 3.3: A Shiny app that lets you compare two simulated distributions with a t-test and a frequency polygon See live at https://hadley.shinyapps.io/ms-case-study-1
- 上述代码的reactive graph如下图所示,具有如下缺点
- 这个应用程序很难理解,因为有太多的连接。
- 这个应用的效率很低,因为它做了太多不必要的工作。例如,如果您更改了绘图的breaks,则会重新计算数据;如果你改变n1的值,x2也会被更新(在
hist和ttest两个地方!)Figure 3.4: The reactive graph shows that every output depends on every input
3.3.3 用reative expression简化app
- 之前的
server函数里x1和x2都重复计算了两次,可以使用reactive expression中简化代码
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())
})
}- 此时的reactive graph如下图所示
Figure 3.5: Using reactive expressions considerably simplifies the graph, making it much easier to understand
在Shiny中,应该考虑一个规则:
每当复制和粘贴一次内容时,应该考虑将重复的代码提取到reactive expression中。
因为reactive expression不仅使人类更容易理解代码,而且还提高了Shiny有效重新运行代码的能力。
3.4 控制app更新
3.4.1 一个app例子
下面是一个简单的app例子,输入两个lambda分布的参数lambda1,lambda2和n,使用rpoins()生成随机的两组数,输出它们的分布图
ui <- fluidPage(
fluidRow(
column(3,
numericInput("lambda1", label = "lambda1", value = 3),
numericInput("lambda2", label = "lambda2", value = 5),
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({
freqpoly(x1(), x2(), binwidth = 1, xlim = c(0, 40))
}, res = 96)
}Figure 3.6: A simpler app that displays a frequency polygon of random numbers drawn from two Poisson distributions. See live at https://hadley.shinyapps.io/ms-simulation-2.
Figure 3.7: The reactive graph
3.4.2 reactiveTimer()定时更新
reactiveTimer()是一个响应式表达式,它依赖于一个隐藏的输入:当前时间。- 下面的例子通过
reactiveTimer()设置每0.5s更新一次
server <- function(input, output, session) {
timer <- reactiveTimer(500)#引入reactiveTimer
x1 <- reactive({
#调用创建的reactiveTimer,使得x1和x2响应表达式依赖于timer
timer()
rpois(input$n, input$lambda1)
})
x2 <- reactive({
timer()
rpois(input$n, input$lambda2)
})
output$hist <- renderPlot({
freqpoly(x1(), x2(), binwidth = 1, xlim = c(0, 40))
}, res = 96)
}- 此时的reactive graph为
Figure 3.8: reactiveTimer(500) introduces a new reactive input that automatically invalidates every half a second
3.4.3 添加操作按钮手动更新
- 使用
actionButton引入操作按钮(Section 2.1.7) - 将其引入
x1,x2的响应表达式中,每次点击时激活一次更新
ui <- fluidPage(
fluidRow(
column(3,
numericInput("lambda1", label = "lambda1", value = 3),
numericInput("lambda2", label = "lambda2", value = 5),
numericInput("n", label = "n", value = 1e4, min = 0),
actionButton("simulate", "Simulate!")#引入actionbutton
),
column(9, plotOutput("hist"))
)
)
server <- function(input, output, session) {
x1 <- reactive({
input$simulate #actionbutton加入reactive expression
rpois(input$n, input$lambda1)
})
x2 <- reactive({
input$simulate
rpois(input$n, input$lambda2)
})
output$hist <- renderPlot({
freqpoly(x1(), x2(), binwidth = 1, xlim = c(0, 40))
}, res = 96)
}Figure 3.9: App with action button. See live at https://hadley.shinyapps.io/ms-action-button.
x1和x2随着simulate,lambda1,lambda2和n的变化更新,这意味者上述任意一个发生变化都会影响输出结果Figure 3.10: This reactive graph doesn’t accomplish our goal; we’ve added a dependency instead of replacing the existing dependencies.
3.4.4 eventReactive()事件触发
- 如果只想当
simulate点击时才触发响应表达式,需要采用eventReactive()函数 eventReactive()有两个参数:第一个参数指定要依赖什么(这里是silumate()),第二个参数指定要计算什么(这里是rpois()。
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({
freqpoly(x1(), x2(), binwidth = 1, xlim = c(0, 40))
}, res = 96)
}x1和x2只依赖于simulate,`这意味者只有点击simulate按钮才会改变输出结果Figure 3.11: eventReactive() makes it possible to separate the dependencies (black arrows) from the values used to compute the result (pale gray arrows).
3.4.5 observerEvent()响应输入变化
observeEvent()与eventReactive()非常相似。它有两个重要参数:eventExpr和handlerExpr。第一个参数是要依赖的输入或表达式;第二个参数是将要运行的代码。- 下列代码在input$name发生变化时,输出message到控制台
ui <- fluidPage(
textInput("name", "What's your name?"),
textOutput("greeting")
)
server <- function(input, output, session) {
string <- reactive(paste0("Hello ", input$name, "!"))
output$greeting <- renderText(string())
observeEvent(input$name, {
message("Greeting performed")
})
}Figure 3.12: In the reactive graph, an observer looks the same as an output
- 注意:
observeEvent()不能赋给一个变量,也不能在其他的响应表达式中使用它