开发一个Rshiny应用(基础)

Introduction

Shiny 是一个 R 包,可轻松地直接从 R 构建交互式 Web 应用程序。还可以在网页上托管独立应用程序或将它们嵌入 R Markdown 文档或构建仪表板,还可以使用 CSS 主题、html 小部件和 JavaScript 操作扩展您的 Shiny 应用程序。

#first app
library(shiny)
ui <- fluidPage(
  "Hello, world!"
)
server <- function(input, output, session) {
}
shinyApp(ui, server)

整个app可分为两个部分,ui与sever,可以认为是前端与后端的关系,Rshiny提供了很多内置的小组件帮我们将前后端联系起来,非常方便好用。只需要有一点点的HTML/CSS/Javascript的知识就可以设计出更好看的界面和更多有趣的交互。

这里是shiny的记忆手册,浓缩了大部分的操作。

Methods

UI设计

Layout

最基础的布局就是sidebarLayout,可以分成sidebarPanel,mainPanel两个部分。

ui <- fluidPage(
  titlePanel("title panel"),

  sidebarLayout(
    sidebarPanel("sidebar panel"),
    mainPanel("main panel")
  )
)

可以通过将内容放在 *Panel 函数中来向Shiny 应用程序添加内容,shiny提供了R代码方便插入HTML (Table <a href="#tab:html">1</a>),更多标签可以查看tag-glossary

Control widgets

这些是常用shiny内置的小控件,提供一种向 Shiny serve发送消息的方式。


可以想想怎么获取更多控件,比如一个color panel等等。

更多:

使用 textInput() 收集少量文本,使用 passwordInput()3 收集密码,使用 textAreaInput() 收集文本段落。

要收集数值,请使用 numericInput() 创建一个受约束的文本框或使用 sliderInput() 创建一个滑块。如果您为 sliderInput() 的默认值提供一个长度为 2 的数值向量,您将得到一个具有两端的“范围”滑块。

使用 dateInput() 收集一天或使用 dateRangeInput() 收集两天的范围。这些提供了一个方便的日历选择器,并且诸如 datesdisabled 和 daysofweekdisabled 之类的附加参数允许您限制有效输入的集合。

有两种不同的方法允许用户从一组预先指定的选项中进行选择:selectInput()(还可以设置 multiple = TRUE 以允许用户选择多个元素)和 radioButtons(); 可以使用checkboxGroupInput()形成多选。

ui <- fluidPage(
  textInput("name", "What's your name?"),
  passwordInput("password", "What's your password?"),
  textAreaInput("story", "Tell me about yourself", rows = 3)
)

ui <- fluidPage(
  numericInput("num", "Number one", value = 0, min = 0, max = 100),
  sliderInput("num2", "Number two", value = 50, min = 0, max = 100),
  sliderInput("rng", "Range", value = c(10, 20), min = 0, max = 100)
)

ui <- fluidPage(
  dateInput("dob", "When were you born?"),
  dateRangeInput("holiday", "When do you want to go on vacation next?")
)

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),
  checkboxGroupInput("animal", "What animals do you like?", animals)
)

让用户使用 actionButton() 或 actionLink() 执行操作,可以使用“btn-primary”、“btn-success”、“btn-info”、“btn-warning”或“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")
  )
)

连接sever

general

  1. 添加输出对象在ui中

Shiny 提供了一系列函数,可以将 R 对象转换为用户界面的输出。每个函数创建特定类型的输出。

  1. 编写构建R对象的代码在serve中


  1. 使用input,output连接

以下代码就可以将selectInput选择的值var通过input$var的形式传递到renderText(),再render成output$selected_var的形式,最后通过textOutput()输出到ui界面。

library(shiny)

ui <- fluidPage(
  titlePanel("censusVis"),
  
  sidebarLayout(
    sidebarPanel(
      
      selectInput("var", 
                  label = "Choose a variable to display",
                  choices = c("Percent White", 
                              "Percent Black",
                              "Percent Hispanic", 
                              "Percent Asian"),
                  selected = "Percent White")
    ),
    
    mainPanel(
      textOutput("selected_var")
    )
  )
)

server <- function(input, output) {
  output$selected_var <- renderText({ 
    paste("You have selected", input$var)
  })
}

shinyApp(ui, server)

更多:

使用 textOutput() 输出常规文本,使用 verbatimTextOutput() 输出固定代码和控制台输出。
renderText() 将结果组合成一个字符串,通常与 textOutput() 配对
renderPrint() 打印结果,就像您在 R 控制台中一样,并且通常与 verbatimTextOutput() 配对。

tableOutput() 和 renderTable() 呈现静态数据表,同时显示所有数据。
dataTableOutput() 和 renderDataTable() 呈现一个动态表,显示固定数量的行以及用于更改哪些行可见的控件。
tableOutput() 对于小型、固定的摘要(例如模型系数)最有用;如果您想向用户公开完整的数据框,则 dataTableOutput() 是最合适的。

默认情况下,plotOutput() 将占据其容器的整个宽度(稍后会详细介绍),并且高度为 400 像素。您可以使用高度和宽度参数覆盖这些默认值。我们建议始终设置 res = 96,因为这将使您的 Shiny 图与您在 RStudio 中看到的尽可能接近。

reactive expressions

This difference between commands and recipes is one of the key differences between two important styles of programming:

In imperative programming, you issue a specific command and it’s carried out immediately. This is the style of programming you’re used to in your analysis scripts: you command R to load your data, transform it, visualise it, and save the results to disk.

In declarative programming, you express higher-level goals or describe important constraints, and rely on someone else to decide how and/or when to translate that into action. This is the style of programming you use in Shiny.

Shiny 中声明式编程的优势之一是它允许应用程序非常懒惰。 Shiny 应用程序只会执行更新您当前可以看到的输出控件所需的最少工作量,优点是快速,缺点是如果你不运行所有代码,可能不会发现错误。

反应式表达式比常规 R 函数更聪明。它们缓存值并知道它们的值何时会变。第一次运行反应式表达式时,表达式会将其结果保存在计算机的内存中。下次调用反应式表达式时,它可以返回保存的结果而不进行任何计算(这将使您的应用程序更快)。

如果反应式表达式知道结果是最新的,它只会返回保存的结果。如果反应式表达式得知结果已过时(因为小部件已更改),则表达式将重新计算结果。然后它返回新结果并保存一个新副本。反应式表达式将使用这个新副本,直到它也变得过时为止。
让我们总结一下这种行为:

  1. 反应式表达式会在您第一次运行时保存其结果。

  2. 下次调用反应式表达式时,它会检查保存的值是否已过时(即,它所依赖的小部件是否已更改)。

  3. 如果该值已过期,反应对象将重新计算它(然后保存新结果)。

  4. 如果该值是最新的,反应式表达式将返回保存的值而不进行任何计算。

建议把文件导入等不需要每次更新的代码放在reactive里。

在 Shiny 中,应该考虑一个规则:每当复制和粘贴一次东西时,就应该考虑将重复的代码提取到一个反应表达式中,因为反应式表达式不仅让人类更容易理解代码,它们还提高了 Shiny 高效重新运行代码的能力。

考虑以下代码,reactive()确保只有在改变n或lambda时才会重新计算:

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

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

shinyApp(ui,server)

控制reactive行为:

  1. 计时器
    reactiveTimer() 是一个响应式表达式,它依赖于隐藏的输入:当前时间。
    将server里的reactive修改成下列形式可以让图形每500ms刷新一次。
server <- function(input, output, session) {
  timer <- reactiveTimer(500)
  
  x1 <- reactive({
    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)
}
  1. 点击刷新
    当你的serve运行一次需要庞大计算和时间时,可能希望要求用户通过单击按钮来选择执行昂贵的计算。这是 actionButton() 的一个很好的用例:

并且我们需要 eventReactive(),它有两个参数:第一个参数指定依赖什么,第二个参数指定计算什么。

修改ui和serve,添加了按键,用户点击按键即可出现新的模拟结果。

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!")
    ),
    column(9, plotOutput("hist"))
  )
)
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)
}

observeEvent() 与 eventReactive() 非常相似。它有两个重要的参数:eventExpr 和 handlerExpr。第一个参数是要依赖的输入或表达式;第二个参数是将要运行的代码。例如,对 server() 的以下修改意味着每次更新该名称时,都会向控制台发送一条消息:

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

file up/download

使用fileInput在ui中上传文件后,得到的input是一个列表,其中的datapath是文件上传后的路径,需要使用read.csv等函数读取file$datapath。

ui <- fluidPage(
    sidebarLayout(
        sidebarPanel(
            fileInput("file1", "Choose CSV File", accept = ".csv"),
            checkboxInput("header", "Header", TRUE)
        ),
        mainPanel(
            tableOutput("contents")
        )
    )
)

server <- function(input, output) {
    output$contents <- renderTable({
        file <- input$file1
        ext <- tools::file_ext(file$datapath)
        
        req(file)
        validate(need(ext == "csv", "Please upload a csv file"))
        
        read.csv(file$datapath, header = input$header)
    })
}

shinyApp(ui, server)

您可以让用户使用 downloadButton() 或 downloadLink() 下载文件。这些都需要服务器功能中的新技术,因此我们将在第 9 章中回过头来讨论。

Share

文件形式

任何拥有 R 的人都可以运行Shiny 应用程序,分享你的app.R 文件副本,以及您的应用程序中使用的任何补充材料(例如,www 文件夹或 helpers.R 文件)即可,最好写上代码运行的依赖包安装代码。

  • runUrl() will download and launch a Shiny app straight from a weblink.
  • runGitHub( "<your repository name>", "<your user name>")

网页形式

上述方法要求用户在他们的计算机上安装 R 和 Shiny。但如果我们自己搭建好了服务器,也可以直接用浏览器使用我们的APP。

  1. Shinyapps.io

将 Shiny 应用程序转换为网页的最简单方法是使用 shinyapps.io,这是 RStudio 为 Shiny 应用程序提供的托管服务。

  1. Shiny Server

  2. RStudio Connect

Reference

官方教程:Shiny Learning Resources

参考书:Mastering Shiny

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容