[译]R语言——Shiny框架之入门(一):Shiny应用的基本构成

 

注:以下内容是我个人翻译自Rstudio官网的Shiny教程,原文地址:http://shiny.rstudio.com/articles/basics.html

水平有限,敬请谅解

在开始之前,先简单介绍一下Shiny框架,以下内容引用于百度百科:

‘Shiny是R中的一种Web开发框架,使得R的使用者不必太了解css、js只需要了解一些html的知识就可以快速完成web开发,且shiny包集成了bootstrap、jquery、ajax等特性,极大解放了作为统计语言的R的生产力。使得非传统程序员的R使用者不必依赖于前端、后端工程师就可以自己依照业务完成一些简单的数据可视化工作,快速验证想法的可靠性。’

Shiny应用的基本构成

Shiny框架自带十一个例子,本文将使用其中的前三个来展示Shiny应用的基本结构。

例一:Hello Shiny

 

Hello Shiny是一个展示基于R内置的数据集faithful的基本图形,这个应用包含一个可以调节区间数(bins)的滑动条。要运行这个例子,请输入代码:

library(shiny)
runExample("01_hello")

Shiny应用有两个组成部分,一个用户界面对象(user interface object)和一个服务功能函数(server function),这两个部分被作为参数传输到ShinyApp功能函数,再根据这一UI/server对生成一个Shiny应用对象。下面列出了这两部分的源代码。

在以后的内容中,我们会将Shiny代码详细分解并解释生成输出值的“响应式”表示的用法。至于现在,你只需要把玩一下这些简单的应用然后看一看它们的源代码,从而获得一些‘直觉’就好。请一定好好读读注释内容。

用户界面(UI)部分定义如下:

# Define UI for app that draws a histogram ----
#为应用(app)定义生成直方图的UI ----
ui <- fluidPage(

  # App title ----
  # App 标题 ----
  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")

    )
  )
)

接下来是应用的服务端(server)。从一个角度来说,它(指服务端)很简单,其内容仅为按要求的区间数把随机分布用直方图的形式展示出来。但是你一定注意到了,那部分生成直方图的代码被打包在一个叫做renderPlot 的函数当中。这个函数上方的注释能给出一些解释,但是如果你还是不明白,不用担心,我们马上就会详细的讲解这个概念。

server

# Define server logic required to draw a histogram ----
# 定义生成直方图所需的server逻辑 ----
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
------------------------------------------------------------------
  # 根据Old Faithful Geyser(老实泉)数据生成的直方图 ----
  # 区间数为用户定义 ----
  # 这个生成直方图的语句被封装在一个叫做renderPlot的函数中
  # 它说明了:
  #
  # 1. 它是“响应式”的,因而应该随着inputs (input$bins)的
  #    改变而自动执行
  # 2. 它输出的格式是一个图表

  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")

    })

}

ShinyApp

终于,我们可以使用shinyApp函数,通过我们定义的上述一对UI/server生成了一个Shiny应用对象

shinyApp(ui, server)

我们保存下所有的代码,包括ui object,server function还有对shinyApp函数的调用,在一个R语言脚本中被命名为app.R.

这种结构是所有Shiny应用的基本结构。

接下来的例子会展示更多关于输入控制和用于生成文本对象的响应式函数的使用方法。

 

例二:Shiny Text

这个Shiny Text应用直接输出了R中的对象,并以HTML表格的方式显示了数据框(data frame)。要运行这个例子,请输入:

library(shiny)
runExample("02_text")

在第一个例子里,有一个以滑动条方式控制的数字格式的输入,还有一个单一的图像输出。这个例子的内容更多了一些:有两个输入值和两种类型的文本输出。

如果你把行数(the number of observations)改成别的数字,你就会看到Shiny应用的一个很重要的特性:输入与输出的连接是“实时”的,其中的改变会迅速“扩散”(像电子表格一样)。在本例中,当输入变化的时候,刷新的仅仅是表格视图而不是整个页面。

下面是该应用的UI Object。需要特别注意的是,sidebarPanelmainPanel函数现在调用两个参数(对应两个输入值和两个输出):

UI

# Define UI for dataset viewer app ----
# 定义访问数据集app的UI ----
ui <- fluidPage(

  # App title ----
  # App 标题 ----
  titlePanel("Shiny Text"),

  # Sidebar layout with a input and output definitions ----
  # 定义侧边栏布局,包含一个输入和输出 ----
  sidebarLayout(

    # Sidebar panel for inputs ----
    # 输入的侧边栏面板 ----
    sidebarPanel(

      # Input: Selector for choosing dataset ----
      # 输入:下拉菜单选择数据集 ----
      selectInput(inputId = "dataset",
                  label = "Choose a dataset:",
                  choices = c("rock", "pressure", "cars")),

      # Input: Numeric entry for number of obs to view ----
      # 输入:输入数字作为表格行数 ----
      numericInput(inputId = "obs",
                   label = "Number of observations to view:",
                   value = 10)
    ),

    # Main panel for displaying outputs ----
    # 显示输出的主面板 ----
    mainPanel(

      # Output: Verbatim text for data summary ----
      # 输出:概括性统计量文本 ----
      verbatimTextOutput("summary"),

      # Output: HTML table with requested number of observations ----
      # 输出:根据输入行数生成的HTML表格 ----
      tableOutput("view")

    )
  )
)

这个应用的server端同样变得复杂了一些。现在我们新建:

  • 一个根据用户选择返回对应数据集的响应式语句
  • 两个其他rendering语句(renderPrintrenderTable)返回值为output$summaryoutput$view

这些语句与第一个例子中的renderPlot语句的作用方法很类似:在声明一个rendering语句的时候,你告诉Shiny这个语句应该只在其依赖(附属)变化的时候被执行。在本例中,依赖为用户输入值得其中一个(input$dataset或者input$obs

server

# Define server logic to summarize and view selected dataset ----
# 定义选择数据集并获得概括统计量的server逻辑 ----
server <- function(input, output) {

  # Return the requested dataset ----
  # 返回指定的数据集 ----
  datasetInput <- reactive({
    switch(input$dataset,
           "rock" = rock,
           "pressure" = pressure,
           "cars" = cars)
  })

  # Generate a summary of the dataset ----
  # 生成该数据集的概括统计量 ----
  output$summary <- renderPrint({
    dataset <- datasetInput()
    summary(dataset)
  })

  # Show the first "n" observations ----
  # 显示前n行 ----
  output$view <- renderTable({
    head(datasetInput(), n = input$obs)
  })

}

我们展示了更多的响应式语句但并没有真正解释它们是如何运作的。下一个例子会以本例为基础,并在此之上极大地扩展Shiny中响应式语句的应用。

例三:Reactivity

这个Reactivity应用与Hello Text应用很类似,但是在响应式编程概念上更进一步。要运行这个例子,请输入:

library(shiny)
runExample("03_reactivity")

之前的例子让你看到了一个Shiny应用的基本样式。我们也多多少少介绍了一些响应式的知识,但是基本上没有涉及细节上的内容。在这一部分,我们会在这个概念上更加深入,如果你想要彻底地了解和学习响应式的细节,请参照“理解响应式”这一部分,其入门:响应式总览

什么是响应式

从根本上说,Shiny web框架旨在让“从web页面获取输入,使其能被R轻松获取和操作,最后又输出在web页面上”这件事变得简单。

input values => R code => output values

因为Shiny web应用是交互式的,输入值随时可能改变,而且输出值需要根据输入值的变化即时变化。

Shiny自带一个响应式编程的library,你可以用它来构建你的应用逻辑。通过使用这个library,input的改变会很自然地使你的R代码中相关的部分重新执行,从而更新变化后的输出。

响应式编程基础

响应式编程是一种代码风格,这种风格以响应值开始(响应值根据用户的操作或随时间进行变化)并在此之上以响应式语句(响应式语句可以获得响应值并能够执行其他响应式语句)的形式实现。

关于响应式语句有趣的一点是:无论何时,当它们被执行的时候,都会自动地与响应值和它们引用的响应式语句保持联系。如果它们发现它们的“附属”(“dependencies”)过时了(out of date),那他们也就同样知道自己的返回值也过时了。由于有这样的“附属”追踪机制,当改变一个响应值的时候,所有直接或间接与之相联系的响应式表达式都会被重新执行。

在Shiny中,面对一个响应值的最常见解决办法是使用input对象。input对象能够让你用一个list-like语法获取web页面上用户的输入信息并且传输到你的shinyServer函数。从代码的角度来说,这个操作看起来就像是你从一个list或者数据框(dataframe)中抓取一个数据一样,但实际上是你读取了一个响应值。所以在Shiny里,我们不需要为了监视输入值的变化单独写一些代码,只要写好读取输入值的响应式语句,然后让Shiny来操心什么时候该调取他们就好了。

要写一条响应式语句很简单:你只需要把一个普通的语句(当然是R语句了!)放到reactive中就好。在这个应用里的一个例子是:响应式语句根据用户选择的数据集返回对应的R数据框(dataframe):

datasetInput <- reactive({
   switch(input$dataset,
          "rock" = rock,
          "pressure" = pressure,
          "cars" = cars)
})

为了能从响应值得到放在web页面上的输出结果,我们要把这些响应值对应到output对象上(同样被传输到shinyServer函数)。下面的例子展示了如何将两个输入(datasetInput和input$obs)的响应式表达式指定到一个输出上:

output$view <- renderTable({
   head(datasetInput(), n = input$obs)
})

无论什么时候,只要datasetInput和input$obs之一发生了变化,表达式就会被重新执行一遍(它所对应的输出值也会被重新提交给浏览器窗口)。

回到代码上来

我们刚刚对“响应式”这个核心概念有了更深入一些的了解,现在咱们重新来看看Reactivity这个例子,试着理解的更彻底一些。UI对象多出来了一个定义“标题”的文本输入框。除此之外,和前一例非常像:

UI

# Define UI for dataset viewer app ----
# 定义访问数据集app的UI ----
ui <- fluidPage(

  # App title ----
  # App 标题 ----
  titlePanel("Reactivity"),

  # Sidebar layout with input and output definitions ----
  # 定义侧边栏布局,包含一个输入和输出 ----
  sidebarLayout(

    # Sidebar panel for inputs ----
    # 输入的侧边栏面板 ----
    sidebarPanel(

      # Input: Text for providing a caption ----
      # Note: Changes made to the caption in the textInput control
      # are updated in the output area immediately as you type
-----------------------------------------------------------------
      # 输入:定义一个标题的文本 ----
      # 注释:输入框里的文本发生的改变会立刻反应在输出区域中
      textInput(inputId = "caption",
                label = "Caption:",
                value = "Data Summary"),

      # Input: Selector for choosing dataset ----
      # 输入:下拉菜单选择数据集 ----
      selectInput(inputId = "dataset",
                  label = "Choose a dataset:",
                  choices = c("rock", "pressure", "cars")),

      # Input: Numeric entry for number of obs to view ----
      # 输入:输入数字作为表格行数 ----
      numericInput(inputId = "obs",
                   label = "Number of observations to view:",
                   value = 10)

    ),

    # Main panel for displaying outputs ----
    # 显示输出的主面板 ----

    mainPanel(

      # Output: Formatted text for caption ----
      # 输出:对应标题的格式化文本
      h3(textOutput("caption", container = span)),

      # Output: Verbatim text for data summary ----
      # 输出:概括性统计量文本 ----
      verbatimTextOutput("summary"),

      # Output: HTML table with requested number of observations ----
      # 输出:根据输入行数生成的HTML表格 ----
      tableOutput("view")

    )
  )
)

server

server函数声明了datasetInput的响应式表达式还有三个响应式输出值。注释中详细描述了各个定义的细节并解释了它们在响应式系统中是如何工作的:

# Define server logic to summarize and view selected dataset ----
# 定义可以根据用户选择的数据集来获取概括性统计量和表格的server逻辑 ----
server <- function(input, output) {

  # Return the requested dataset ----
  # By declaring datasetInput as a reactive expression we ensure
  # that:
  #
  # 1. It is only called when the inputs it depends on changes
  # 2. The computation and result are shared by all the callers,
  #    i.e. it only executes a single time
---------------------------------------------------------------------
  # 返回用户选择的数据集 ----
  # 将datasetInput声明成一个响应式表达式,这样我们可以保证:
  #
  # 1. 只有当输入发生改变的时候它才会被调用
  # 2. 这个表达式的计算结果可以被随意调用,也就是说:它只执行一遍。
  datasetInput <- reactive({
    switch(input$dataset,
           "rock" = rock,
           "pressure" = pressure,
           "cars" = cars)
  })

  # Create caption ----
  # The output$caption is computed based on a reactive expression
  # that returns input$caption. When the user changes the
  # "caption" field:
  #
  # 1. This function is automatically called to recompute the output
  # 2. New caption is pushed back to the browser for re-display
  #
  # Note that because the data-oriented reactive expressions
  # below don't depend on input$caption, those expressions are
  # NOT called when input$caption changes
------------------------------------------------------------------------
  # 创建标题 ----
  # output$caption是通过一个能够返回input$caption的响应式语句被计算出来的。
  # 当用户改变“标题(caption)”区域值的时候:
  # 
  # 1. 这个函数会自动的被调用来重新计算结果
  # 2. 新的结果会被推送回页面重新显示
  #
  # 注意:由于下面的一些响应式表达式是面向数据的,他们和input$caption没什么关系,也就不会在
  # input$caption发生改变的时候被调用。

  output$caption <- renderText({
    input$caption
  })

  # Generate a summary of the dataset ----
  # The output$summary depends on the datasetInput reactive
  # expression, so will be re-executed whenever datasetInput is
  # invalidated, i.e. whenever the input$dataset changes
-----------------------------------------------------------------------
  # 生成该数据集的概括统计量 ----
  # 由于output$summary依附于datasetInput的响应式语句,所以当datasetInput(原来的值)
  # 无效的时候就会被重新执行一遍,也就是说:无论何时,只要datasetInput发生了改变,
  # 该语句就会重新执行一遍。
  output$summary <- renderPrint({
    dataset <- datasetInput()
    summary(dataset)
  })

  # Show the first "n" observations ----
  # The output$view depends on both the databaseInput reactive
  # expression and input$obs, so it will be re-executed whenever
  # input$dataset or input$obs is changed
--------------------------------------------------------------------------------
  # 显示前n行 ----
  # output$view同时附属于datasetInput响应式语句和input$obs变量,
  # 所以当其中一个发生变化的时候,input$obs就会跟着改变。
  output$view <- renderTable({
    head(datasetInput(), n = input$obs)
  })

}

在前三个例子里,我们看了很多代码,讲解了很多概念性的背景知识。下一篇文章我们将关注如何从零开始建立起一个Shiny应用。

If you have questions about this article or would like to discuss ideas presented here, please post on RStudio Community. Our developers monitor these forums and answer questions periodically. See help for more help with all things Shiny.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章