[译]R语言——Shiny框架之入门(二):如何构建一个Shiny应用

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

水平有限,敬请谅解

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

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

如何构建一个Shiny应用

我们先总览一下构建一个简单Shiny应用的步骤。一个Shiny应用其实只是一个包含一个R语言脚本的目录,这个脚本文件叫做app.R,它的内容包括UI对象和server函数。这个文件夹(目录)当然也可以放很多其他东西,比如任何与项目有关的数据,脚本文件,或者一些你需要的其他东西。

UI & Server

要开始构建Shiny应用,首先你得创建一个新的空白目录,放在哪无所谓。然后在这个目录下创建一个空白的app.R文件。为了讲解起来方便,咱们就假设你在~/shinyapp:目录下创建了这个应用。

~/shinyapp
|-- app.R

现在,我们在这个app.R文件里写下构造一个应用所需的很少的代码。

首先,我们加载shiny包:

library(shiny)

ui

然后,我们调用一个叫做pageWithSidebar的函数来定义ui界面:

# Define UI for miles per gallon app ----
# 为mpg(油耗) app定义UI ----
ui <- pageWithSidebar(

  # App title ----
  # App 标题 ----
  headerPanel("Miles Per Gallon"),

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

  # Main panel for displaying outputs ----
  # 带输出的主面板 ----
  mainPanel()
)

上面的三个函数:headerPanel,sidebarPanel和mainPanel定义了UI界面的不同区域。这个应用叫做“油耗(英里每加仑)”所以我们在定义标题面板的时候直接把名字写上去了,而其他两个函数目前还是空的。

现在,我们来定义一个server的执行结构。为此,我们调用一个shinyServer然后给它一个可以接受input和output两个变量的函数(function):

server

# Define server logic to plot various variables against mpg ----
# 定义可以展示多个mpg变量的server逻辑
server <- function(input, output) {

}

我们的server函数目前还是空的,但是等一下我们就会定义input和output之间的联系了。

app.R

终于,我们可以在shinyApp函数里用刚刚定义的ui object和server function来构建一个Shiny应用对象了!

把它俩放到一起去,我们的app.R脚本文件就变成了这样:

library(shiny)

# Define UI for miles per gallon app ----
ui <- pageWithSidebar(
  
  # App title ----
  headerPanel("Miles Per Gallon"),
  
  # Sidebar panel for inputs ----
  sidebarPanel(),
  
  # Main panel for displaying outputs ----
  mainPanel()
)

# Define server logic to plot various variables against mpg ----
server <- function(input, output) {
  
}

shinyApp(ui, server)

现在我们创建了一个可以执行的最小的Shiny应用,你可以调用runApp函数来执行这个程序:

> library(shiny)
> runApp("~/shinyapp")

或者,如果你还需要使用控制台,你也可以点击RStudio界面上的Run App按钮。

如果没有问题的话,你会在窗口看到:

现在我们获得了一个可以执行的Shiny应用,尽管它没什么功能。接下来的部分我们将要通过增添ui特性和server函数来完成这个应用。

输入和输出

将输入值添加到侧边栏中

我们即将创建的这个应用将使用到R自带数据集中的mtcars数据,用户可以在一个箱线图中观察到英里每加仑(MPG)变量和其他三个变量(气缸数(Cylinders),变速箱形式(Transmission),档位数(gears))间的关系。

我们想要提供一种能够选择MPG对应变量的办法,再加一个选择是否显示极值的选项。为了实现这个想法,我们在侧边栏中添加两个元素:一个selectInput用来指定某个变量,还有一个checkboxInput用来控制极值是否显示。在添加了这两个元素之后,我们的ui代码变成了这样:

ui

# Define UI for miles per gallon app ----
# 为mpg(油耗) app定义UI ----
ui <- pageWithSidebar(
  
  # App title ----
  # App标题 ----
  headerPanel("Miles Per Gallon"),
  
  # Sidebar panel for inputs ----
  # 带输入的侧边栏面板 ----
  sidebarPanel(
  
        # Input: Selector for variable to plot against mpg ----
        # 输入:与mpg对应的变量选择器 ----
      selectInput("variable", "Variable:", 
                c("Cylinders" = "cyl",
                  "Transmission" = "am",
                  "Gears" = "gear")),

      # Input: Checkbox for whether outliers should be included ----
      # 输入:关于极值是否显示的选项 ----
      checkboxInput("outliers", "Show outliers", TRUE)
  
  ),
  
  # Main panel for displaying outputs ----
  # 显示输出的主面板 ----
  mainPanel()
)

如果你在更改完代码之后再次运行这个应用,你就会看到在侧边栏中出现了两个输入的元素:

创建server函数

接下来我们需要定义应用的server端,server端会接收inputs并且运算输出outputs。下面就是我们的server函数,并且列出了几条重要的概念:

  • 在input对象上获取输入信息并通过将其与output对象连接来生成输出信息
  • 在应用启动时获得的数据将能够被应用在它运行的时候一直使用
  • 用响应式语句计算出的值将能够被多个输出变量调用

Shiny的server函数的一个基本作用是定义连接输入与输出之间的某种关系。我们的函数通过对获取的输入信息进行一系列的计算并且将反应式语句指定到输出值来实现这一功能。

下面是完整的server函数代码(其中的注释解释了代码具体实施时的细节):

server

# Data pre-processing ----
# Tweak the "am" variable to have nicer factor labels -- since this
# doesn't rely on any user inputs, we can do this once at startup
# and then use the value throughout the lifetime of the app
----------------------------------------------------------------------
# 数据预处理 ----
# 将"am"变量转换成拥有更好标签的因子变量 -- 由于这个变量不依赖于任何输入,
# 我们可以在一开始就对它进行处理,这样就可以在整个app中使用处理后的变量了
mpgData <- mtcars
mpgData$am <- factor(mpgData$am, labels = c("Automatic", "Manual"))

# Define server logic to plot various variables against mpg ----
# 定义用来展示与mpg相关联的多个变量的server逻辑 ----
server <- function(input, output) {

  # Compute the formula text ----
  # This is in a reactive expression since it is shared by the
  # output$caption and output$mpgPlot functions
------------------------------------------------------------------
  # 生成格式化字符 ----
  # 因为这个格式化字符变量会被output$caption和output$mpgPlot的函数调用,
  # 所以把它放在一个reactive中
  formulaText <- reactive({
    paste("mpg ~", input$variable)
  })

  # Return the formula text for printing as a caption ----
  # 将格式化文本赋值给标题变量 ----
  output$caption <- renderText({
    formulaText()
  })

  # Generate a plot of the requested variable against mpg ----
  # and only exclude outliers if requested
-------------------------------------------------------------------
  # 根据用户所选变量生成一个与mpg相关联的图表 ----
  # 并且仅当选项被勾选时显示极值
  output$mpgPlot <- renderPlot({
    boxplot(as.formula(formulaText()),
            data = mpgData,
            outline = input$outliers,
            col = "#75AADB", pch = 19)
  })

}

使整个应用成为“响应式”的原因是使用了renderText和renderPlot函数来生成输出变量(而非简单的直接指定输出变量)。这些响应式的封装返回了一些特别的语句,这些语句只会在他们的附属发生变化的时候重新执行。正是这样的功能(行为)使得Shiny可以根据输入的变化自动更新输出。

显示输出

server函数为两个输出变量赋值:output$caption和output$mpgPlot

为了能够在我们的ui界面显示这些输出变量,我们需要在主ui面板上添加一些元素。

在下面的代码中,你可以看到我们为更新后的ui定义添加了关于caption的h3元素,用textOutput函数对其赋值,并调用plotOutput函数表达出所需图形:

ui

# Define UI for miles per gallon app ----
ui <- fluidPage(

  # App title ----
  titlePanel("Miles Per Gallon"),

  # Sidebar layout with input and output definitions ----
  sidebarLayout(

    # Sidebar panel for inputs ----
    sidebarPanel(

      # Input: Selector for variable to plot against mpg ----
      selectInput("variable", "Variable:",
                  c("Cylinders" = "cyl",
                    "Transmission" = "am",
                    "Gears" = "gear")),

      # Input: Checkbox for whether outliers should be included ----
      checkboxInput("outliers", "Show outliers", TRUE)

    ),

    # Main panel for displaying outputs ----
    mainPanel(

      # Output: Formatted text for caption ----
      h3(textOutput("caption")),

      # Output: Plot of the requested variable against mpg ----
      plotOutput("mpgPlot")

    )
  )
)

现在运行这个应用,就能在页面上看到包含输入和动态输出的最终形态:

一些细节

shinyApp()函数返回了一个shiny.appobj类的对象。当这个对象被返回到控制台的时候,它会被用print.shiny.appobj()函数输出,而这个函数会启动一个关联前述对象的Shiny应用。

你也可以用类似的技术方法来创建一个不叫app.R而且不在他们自己目录下的文件。举个例子,假如你创建一个叫做test.R的文件然后用shinyApp()来调用它,你可以在控制台里这样做:

print(source("test.R"))

当对一个文件使用source()方法之后,它会返回一个shiny.appobj对象,但在默认情况下,source()方法的返回值是不会被显示出来的。把它们放在print()中能够让Shiny框架启动并显示这个文件。

以上的办法对于开始一个小实验来说很方便,但是与在文件自己的目录下的app.R相比,这个办法还是要差一些。当你使用runApp("newdir")的时候,Shiny会监控这个文件的变动并且会在你重载浏览器的同时重载这个应用,这个功能对于开发工作是很有用的。但是如果你只是对文件调用source()的话,Shiny就不会这么做了。同样地,Shiny Server和shinyapps.io需要应用在它自己的目录下。所以如果你想要使用你的app,你需要让它待在自己的目录下。

现在,我们有了一个简单的应用,但是我们还想要再做一些改变。下一篇文章的内容会包含编辑,运行和调试Shiny应用的整个流程。

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