1. Get Start
Shiny包有11个内置demo。
library(shiny)
runExample("01_hello")
每一个Shiny APP包含三个部分:
- UI,控制显示外观
- server function,运算
- shinyApp function,构造APP
library(shiny)
# Define UI for app that draws a histogram ----
ui <- fluidPage(
# App title ----
titlePanel("Hello Shiny!"),
# Sidebar layout with input and output definitions ----
sidebarLayout(
# Sidebar panel for inputs ----
sidebarPanel(
# Input: Slider for the number of bins ----
sliderInput(inputId = "bins",
label = "Number of bins:",
min = 1,
max = 50,
value = 30)
),
# Main panel for displaying outputs ----
mainPanel(
# Output: Histogram ----
plotOutput(outputId = "distPlot")
)
)
)
# Define server logic required to draw a histogram ----
server <- function(input, output) {
# Histogram of the Old Faithful Geyser Data ----
# with requested number of bins
# This expression that generates a histogram is wrapped in a call
# to renderPlot to indicate that:
#
# 1. It is "reactive" and therefore should be automatically
# re-executed when inputs (input$bins) change
# 2. Its output type is a plot
output$distPlot <- renderPlot({
x <- faithful$waiting
bins <- seq(min(x), max(x), length.out = input$bins + 1)
hist(x, breaks = bins, col = "#75AADB", border = "white",
xlab = "Waiting time to next eruption (in mins)",
main = "Histogram of waiting times")
})
}
# Create Shiny app ----
shinyApp(ui = ui, server = server)
一个Shiny APP的结构如下
library(shiny)
# See above for the definitions of ui and server
ui <- ...
server <- ...
shinyApp(ui = ui, server = server)
当运行APP时,R session忙着监听APP,所以不会有其他响应。
建议每一个APP都在一个单独的project/directory里,例如如果目录叫my_app,那么可以这样运行它。runApp的第一个参数是路径名,所以必须要在当前工作路径下。
library(shiny)
runApp("my_app")
如果需要将APP代码也显示出来,那么运行
runApp("App-1", display.mode = "showcase")
也可以点击Script工具栏上的Run APP,可以选择在Window、Viewer Pane或者浏览器中运行APP。
2. Build a user interface
Shiny APP的最小结构
library(shiny)
# Define UI ----
ui <- fluidPage(
)
# Define server logic ----
server <- function(input, output) {
}
# Run the app ----
shinyApp(ui = ui, server = server)
fluidPage函数创建了一个随用户窗口自动调整的页面,其余的panel都是在fluidPage()函数里的。
ui <- fluidPage(
titlePanel("title panel"),
sidebarLayout(
sidebarPanel("sidebar panel"),
mainPanel("main panel")
)
)
2.2 GridLayout
- fluidRow()创建行;
- column()创建列;列宽加起来必须等于12;
ui <- fluidPage(
titlePanel("Hello Shiny!"),
fluidRow(
column(4,
wellPanel(
sliderInput("obs", "Number of observations:",
min = 1, max = 1000, value = 500)
)
),
column(8,
plotOutput("distPlot")
)
)
)
library(shiny)
library(ggplot2)
dataset <- diamonds
ui <- fluidPage(
title = "Diamonds Explorer",
plotOutput('plot'),
hr(),
fluidRow(
column(3,
h4("Diamonds Explorer"),
sliderInput('sampleSize', 'Sample Size',
min=1, max=nrow(dataset), value=min(1000, nrow(dataset)),
step=500, round=0),
br(),
checkboxInput('jitter', 'Jitter'),
checkboxInput('smooth', 'Smooth')
),
column(4,
offset = 1,
selectInput('x', 'X', names(dataset)),
selectInput('y', 'Y', names(dataset), names(dataset)[[2]]),
selectInput('color', 'Color', c('None', names(dataset)))
),
column(4,
selectInput('facet_row', 'Facet Row', c(None='.', names(dataset))),
selectInput('facet_col', 'Facet Column', c(None='.', names(dataset)))
)
)
)
- offset用来微调列的间距
- 因为没有titlePanel,所以有个fluidPage的title参数
2.3 Tabsets
可以将一个页面分成多个部分,形成标签
ui <- fluidPage(
titlePanel("Tabsets"),
sidebarLayout(
sidebarPanel(
# Inputs excluded for brevity
),
mainPanel(
tabsetPanel(
tabPanel("Plot", plotOutput("plot")),
tabPanel("Summary", verbatimTextOutput("summary")),
tabPanel("Table", tableOutput("table"))
)
)
)
)
如果想将标签放在页面的下面,可以指定position
tabsetPanel(position = "below",
tabPanel("Plot", plotOutput("plot")),
tabPanel("Summary", verbatimTextOutput("summary")),
tabPanel("Table", tableOutput("table"))
)
3. Add control widgets
控件widgets是用户可以交互的工具,widgets提供将信息传递给APP的途径。
控件函数有:
- actionButton
- checkboxGroupInput: A group of check boxes
- checkboxInput: A single check box
- dateInput: A calendar to aid date selection
- dateRangeInput: A pair of calendars for selecting a date range
- fileInput
- helpText
- numericInput
- radioButtons
- selectInput
- sliderInput
- submitButton
- textInput
添加widgets就像添加HTML标记一样,将widgets放在sidebarPanel或mainPanel里。
每个控件都有多个参数,前两个参数是一样的:
- 控件名,用来获取控件的取值,控件名必须是字符串;
- 标签label,用来显示在控件上,也必须是字符串,可以是空字符串;
各类控件用法案例
library(shiny)
# Define UI ----
ui <- fluidPage(
titlePanel("Basic widgets"),
fluidRow(
column(3,
h3("Buttons"),
actionButton("action", "Action"),
br(),
br(),
submitButton("Submit")),
column(3,
h3("Single checkbox"),
checkboxInput("checkbox", "Choice A", value = TRUE)),
column(3,
checkboxGroupInput("checkGroup",
h3("Checkbox group"),
choices = list("Choice 1" = 1,
"Choice 2" = 2,
"Choice 3" = 3),
selected = 1)),
column(3,
dateInput("date",
h3("Date input"),
value = "2014-01-01"))
),
fluidRow(
column(3,
dateRangeInput("dates", h3("Date range"))),
column(3,
fileInput("file", h3("File input"))),
column(3,
h3("Help text"),
helpText("Note: help text isn't a true widget,",
"but it provides an easy way to add text to",
"accompany other widgets.")),
column(3,
numericInput("num",
h3("Numeric input"),
value = 1))
),
fluidRow(
column(3,
radioButtons("radio", h3("Radio buttons"),
choices = list("Choice 1" = 1, "Choice 2" = 2,
"Choice 3" = 3),selected = 1)),
column(3,
selectInput("select", h3("Select box"),
choices = list("Choice 1" = 1, "Choice 2" = 2,
"Choice 3" = 3), selected = 1)),
column(3,
sliderInput("slider1", h3("Sliders"),
min = 0, max = 100, value = 50),
sliderInput("slider2", "",
min = 0, max = 100, value = c(25, 75))
),
column(3,
textInput("text", h3("Text input"),
value = "Enter text..."))
)
)
# Define server logic ----
server <- function(input, output) {
}
# Run the app ----
shinyApp(ui = ui, server = server)
4. Display reactive output
You can create reactive output with a two step process.
- 在UI里添加R对象
- 在server函数里创建对象
4.1 Add an R object to the UI
下面的函数可以将R对象变为output,
- dataTableOutput DataTable
- htmlOutput raw HTML
- imageOutput image
- plotOutput plot
- tableOutput table
- textOutput text
- uiOutput raw HTML
- verbatimTextOutput text
output可以像html一样加入到UI里的sidbarPanel或mainPanel里。
ui <- fluidPage(
titlePanel("censusVis"),
sidebarLayout(
sidebarPanel(
helpText("Create demographic maps with
information from the 2010 US Census."),
selectInput("var",
label = "Choose a variable to display",
choices = c("Percent White",
"Percent Black",
"Percent Hispanic",
"Percent Asian"),
selected = "Percent White"),
sliderInput("range",
label = "Range of interest:",
min = 0, max = 100, value = c(0, 100))
),
mainPanel(
textOutput("selected_var")
)
)
)
每一个output函数都需要一个字符串参数,即是可变元素的名字。用户看不到该名字,但开发者会用到。
4.2 Provide R code to build the object.
上一步在UI里告诉Shiny在哪里显示output,下一步告诉Shiny如何创建该对象。
server函数创建了一个叫output的list,包含了所有需要时时更新的R对象。可以通过给output里增加元素来创建对象。元素名必须和在UI里创建的output里的参数名相一致。
server <- function(input, output) {
output$selected_var <- renderText({
"You have selected this"
})
}
不需要显式地return output list,R会自动返回output。
每一个output元素的创建都需要一个render函数,根据不同类型的可变对象来选择不同的render函数:
- renderDataTable DataTable
- renderImage images (saved as a link to a source file)
- renderPlot plots
- renderPrint any printed output
- renderTable data frame, matrix, other table like structures
- renderText character strings
- renderUI a Shiny tag object or HTML , 每一个render函数需要一个参数:{}大括号括起来的R表达式。
如果表达式没有返回对象或返回了错误对象类型,则会抛出错误。
4.3 Use widget values
可以在server函数里使用widget的value。
server函数里需要两个参数input和output,input也是一个list,用来储存widgets里的当前值,这些值会被储存在UI里给widgets起的名字的变量里。
例如slider widget里有两个值,min和max,则input$range会储存长度为2的向量。
如果使用了input的值,Shiny会创建响应型的变量。
Shiny会追踪哪个output依赖于哪个widget,一旦用户改变了widget值,Shiny就会重新构建所有的output。
本节重点:
- 在UI函数中使用Output函数来放置对象;
- 在server函数里使用render函数来告诉Shiny怎样创建对象;
- 在每个render函数里,将所有的R表达式放置在大括号{}里;
- 在render表达式里创建output列表的元素,每一个元素都是响应式对象;
- 在render表达式里运用inp使用input列表里的值,其对应各个widget的取值;
5. Use R scripts and data
当Shiny运行server.R时,它会将文件路径默认为与server.R相同的路径。也就是说,保存server.R的路径变成了Shiny APP的工作路径。
所以将helper.R保存在server.R相同路径下时,可以如此引入
source("helpers.R")
将数据保存在data路径下时,可以如此引入
counties <- readRDS("data/counties.rds")
引入包时,还是使用library
library(maps)
library(mapproj)
- 第一次运行APP时,Shiny将运行整个程序;
- 每次有一个新用户时,Shiny将重新运行server,为每一个用户提供独立的区间;
- 当用户改变widgets的值时,对应的render函数里的内容将重新运行;
所以有以下启示:
- 在server函数的外部加载包,导入脚本和数据;
- 在server函数中,render函数外,定义具有用户特征的对象,这些代码只为用户运行一次;
- 在render函数里,放置与widgets值变化有关的代码,shiny将在widgets值变化时重新运行该段代码;
不要在render函数里放置没必要放置在这里的代码。
6. Use reactive expressions
Reactive expressions可以控制APP中哪一部分自动更新,从而加快APP的运行效率。
例如如果将server函数写成这样
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)
})
那么每次改变log的widget时,会重新运行两件事:下载数据和绘图。而事实上只需要重新绘图。
6.1 Reactive expressions
可以通过使用reactive expressions来限制哪些部分重新运行。
reactive expressions使用一个widget的input,并返回一个值。当widget变化时,reactive expressions更新返回值。
reactive函数就像render函数一样,需要将R表达式写到大括号里{},如下。
dataInput <- reactive({
getSymbols(input$symb, src = "yahoo",
from = input$dates[1],
to = input$dates[2],
auto.assign = FALSE)
})
在renderPlot里通过dataInput()来获得数据
output$plot <- renderPlot({
chartSeries(dataInput(), theme = chartTheme("white"),
type = "line", log.scale = input$log, TA = NULL)
})
当第一次运行reactive expression时,server计算结果并将结果储存在内存中。下一次运行时,直接从内存中读取计算结果。它能时刻监听数据是否过时(widget是否改变),当改变时重新计算,并将结果储存在内存中。
所以可以将原server函数写成这样
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)
})
}
不论reactive expression变化还是input变化,render函数都能捕捉到。
reactive expression可以调用另一个reactive expression,就像链条一样将Input和output连起来。只在reactive和render里调用reactive expression。不要在这两种之外调用。