学习shiny包

王致远

2019/04/23

1. Get Start

Shiny包有11个内置demo。

library(shiny)
runExample("01_hello")

每一个Shiny 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.1 SidebarLayout

sidebarLayout里包括了sidebarPanel和mainPanel,是它的两个参数。这两个函数用来显示内容,默认来说sidebar在左,main在右,也可以调整position。一般来说,sidebarPanel用来显示输入内容,mainPanel用来显示输出内容。

ui <- fluidPage(
  titlePanel("title panel"),
  
  sidebarLayout(position = "right",
                sidebarPanel("sidebar panel"),
                mainPanel("main panel")
  )
)

在····Panel函数里,可以添加内容,除了字符串外,还可以是HTML标记函数,如下:

  • p 一段落文字
  • h1 一级标题
  • h6 六级标题
  • a 超链接
  • br 一个空行
  • hr 水平线
  • div 文字分割
  • span 行内分割
  • pre Text ‘as is’ in a fixed width font
  • code 一段格式化代码
  • img 图像
  • strong 粗体字
  • em 斜体字

在一个Panel里可以放多个内容,用逗号分隔。

让文字居中显示,可以添加align=“center”参数,h6(“Episode IV”, align = “center”)。

标记函数的使用方法

ui <- fluidPage(
  titlePanel("My Shiny App"),
  sidebarLayout(
    sidebarPanel(),
    mainPanel(
      p("p creates a paragraph of text."),
      p("A new p() command starts a new paragraph. Supply a style attribute to change the format of the entire paragraph.", style = "font-family: 'times'; font-si16pt"),
      strong("strong() makes bold text."),
      em("em() creates italicized (i.e, emphasized) text."),
      br(),
      code("code displays your text similar to computer code"),
      div("div creates segments of text with a similar style. This division of text is all blue because I passed the argument 'style = color:blue' to div", style = "color:blue"),
      br(),
      p("span does the same thing as div, but it works with",
        span("groups of words", style = "color:blue"),
        "that appear inside a paragraph.")
    )
  )
)

div为内容标签,可以设置CSS样式;p为段落标签,用于文章分段。

img函数插入图片,src参数表明图片名

img(src = "my_image.png", height = 72, width = 72)

其中图片必须放在与app.R同级的www文件夹里,该文件夹为浏览器提供呈现的各种素材。

- APP-1
  - app.R
  - www
    - fig01
    - fig02

其他标签的用法见

标签使用作业:

library(shiny)

# Define UI ----
ui <- fluidPage(
  titlePanel("My Shiny App"),
  sidebarLayout(
    sidebarPanel(
      h1("Installation"),
      p("Shiny is available on CRAN, 
        so you can install it in the usual way from your R console:"),
      code("install.packages(\"shiny\")"),
      br(),
      br(),
      br(),
      br(),
      img(src="rstudio.png",height=70,width=200),
      br(),
      "Shiny is a product of ",span("Rstudio",style = "color:blue")
    ),
    mainPanel(
      h1("Introducing Shiny"),
      br(),
      p("Shiny is a new package from RStudio that makes it",
        em("incredibly easy"),
        "to build interactive web applications with R."
      ),
      br(),
      p("For an introduction and live examples, visit the",
        a(href="http://shiny.rstudio.com/","Shiny homepage.")),
      br(),
      h1("Features"),
      p("- Build useful web applications with only a few lines of code - 
        no JavaScript required."),
      p("- Shiny applications are automativcally 'live' in the same way that",
        strong("spreadsheets"),
        " are live. Outputs change instantly as users modify inputs, without 
        requiring a reload of the browser.")
    )
  )
)

# Define server logic ----
server <- function(input, output) {
  
}

# Run the app ----
shinyApp(ui = ui, server = server)

除了sidebarLayout以外,还有多种高级控件,例如:

  • navbarPage可以创建带有导航栏的多页界面;
  • fluidRow通过网格系统创建界面;

具体见:高级页面控件

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的途径。

控件函数有:

添加widgets就像添加HTML标记一样,将widgets放在sidebarPanel或mainPanel里。

每个控件都有多个参数,前两个参数是一样的:

各类控件用法案例

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.

  1. 在UI里添加R对象
  2. 在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)
  1. 第一次运行APP时,Shiny将运行整个程序;
  2. 每次有一个新用户时,Shiny将重新运行server,为每一个用户提供独立的区间;
  3. 当用户改变widgets的值时,对应的render函数里的内容将重新运行;

所以有以下启示:

  1. 在server函数的外部加载包,导入脚本和数据;
  2. 在server函数中,render函数外,定义具有用户特征的对象,这些代码只为用户运行一次;
  3. 在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。不要在这两种之外调用。

7. share APP

library(shiny)
runUrl( "<the weblink>")
runGitHub( "<your repository name>", "<your user name>") 

或以网页形式分享