開發(fā)一個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提供了很多內置的小組件幫我們將前后端聯(lián)系起來,非常方便好用。只需要有一點點的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發(fā)送消息的方式。


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

更多:

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

要收集數值,請使用 numericInput() 創(chuàng)建一個受約束的文本框或使用 sliderInput() 創(chuàng)建一個滑塊。如果您為 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() 執(zhí)行操作,可以使用“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 對象轉換為用戶界面的輸出。每個函數創(chuàng)建特定類型的輸出。

  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() 輸出常規(guī)文本,使用 verbatimTextOutput() 輸出固定代碼和控制臺輸出。
renderText() 將結果組合成一個字符串,通常與 textOutput() 配對
renderPrint() 打印結果,就像您在 R 控制臺中一樣,并且通常與 verbatimTextOutput() 配對。

tableOutput() 和 renderTable() 呈現(xiàn)靜態(tài)數據表,同時顯示所有數據。
dataTableOutput() 和 renderDataTable() 呈現(xiàn)一個動態(tài)表,顯示固定數量的行以及用于更改哪些行可見的控件。
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 中聲明式編程的優(yōu)勢之一是它允許應用程序非常懶惰。 Shiny 應用程序只會執(zhí)行更新您當前可以看到的輸出控件所需的最少工作量,優(yōu)點是快速,缺點是如果你不運行所有代碼,可能不會發(fā)現(xiàn)錯誤。

反應式表達式比常規(guī) R 函數更聰明。它們緩存值并知道它們的值何時會變。第一次運行反應式表達式時,表達式會將其結果保存在計算機的內存中。下次調用反應式表達式時,它可以返回保存的結果而不進行任何計算(這將使您的應用程序更快)。

如果反應式表達式知道結果是最新的,它只會返回保存的結果。如果反應式表達式得知結果已過時(因為小部件已更改),則表達式將重新計算結果。然后它返回新結果并保存一個新副本。反應式表達式將使用這個新副本,直到它也變得過時為止。
讓我們總結一下這種行為:

  1. 反應式表達式會在您第一次運行時保存其結果。

  2. 下次調用反應式表達式時,它會檢查保存的值是否已過時(即,它所依賴的小部件是否已更改)。

  3. 如果該值已過期,反應對象將重新計算它(然后保存新結果)。

  4. 如果該值是最新的,反應式表達式將返回保存的值而不進行任何計算。

建議把文件導入等不需要每次更新的代碼放在reactive里。

在 Shiny 中,應該考慮一個規(guī)則:每當復制和粘貼一次東西時,就應該考慮將重復的代碼提取到一個反應表達式中,因為反應式表達式不僅讓人類更容易理解代碼,它們還提高了 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運行一次需要龐大計算和時間時,可能希望要求用戶通過單擊按鈕來選擇執(zhí)行昂貴的計算。這是 actionButton() 的一個很好的用例:

并且我們需要 eventReactive(),它有兩個參數:第一個參數指定依賴什么,第二個參數指定計算什么。

修改ui和serve,添加了按鍵,用戶點擊按鍵即可出現(xiàn)新的模擬結果。

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() 的以下修改意味著每次更新該名稱時,都會向控制臺發(fā)送一條消息:

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

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容