R Shiny 基礎. 2 reactivity

Shiny是一個網頁端app,所以得同時滿足多個用戶的獨立操作。不能因為小A修改了輸入導致小B想要看的結果出現(xiàn)了錯誤数焊。所以會用到reactivity來保證流程獨立谓苟。

2.1 服務器

服務器兩個重要也是最基本的功能就是輸入和輸出。

2.1.1 Input 輸入

ui <- fluidPage(
  numericInput("count", label = "Number of values", value = 100)
)

這是一個最常見的輸入模式。默認值是100。

但是server端的話就不能給input指定數(shù)值了,因為在server端input是只讀參數(shù)唧躲。強行寫入數(shù)值的話會返回錯誤。

server <- function(input, output, session) {
  input$count <- 10  
}

shinyApp(ui, server)
#> Error: Can't modify read-only reactive value 'count'

這個錯誤是因為input只會反饋瀏覽器的數(shù)值,當你強行修改server內部input的時候就會造成混亂弄痹。需要用到類似updateNumericInput()的函數(shù)來讓sever自動更新饭入。

2.1.2 Output輸出

和輸入很相似。一定也要用render函數(shù)肛真。

ui <- fluidPage(
  textOutput("greeting")
)

server <- function(input, output, session) {
  output$greeting <- renderText("Hello human!")
}
  • render的功能
  1. 自動更新input和output
  2. 把R code轉換成html格式

和input一樣谐丢,如果忘了render函數(shù)或者是圖直接讀取的話會報錯。

server <- function(input, output, session) {
  output$greeting <- "Hello human"
}
shinyApp(ui, server)
#> Error: Unexpected character object for output$greeting
#> Did you forget to use a render function?
server <- function(input, output, session) {
  message("The greeting is ", output$greeting)
}
shinyApp(ui, server)
#> Error: Reading from shinyoutput object is not allowed.

2.2 Reactive程序

ui <- fluidPage(
  textInput("name", "What's your name?"), #注意到有個逗號
  textOutput("greeting")
)

server <- function(input, output, session) {
  output$greeting <- renderText({
    paste0("Hello ", input$name, "!")
  })
}

textInput是設置UI輸入界面蚓让,并且給輸入指定變量”name”乾忱。下面一行textOutput是給輸出指定變量。這里的變量名就是”greeting”历极。如果沒有textInput的話那每次顯示的東西都變成了固定的窄瘟,沒法進行UI互動了。

對于reactive趟卸,書里的定義比較復雜蹄葱。理解成可以用來生成介于input和output之間的中間變量的函數(shù)。

2.3 Reactive Graph

原著中給出了reactive graph的概念衰腌,就是reactive的流程圖。比方說剛才的例子就可以變成觅赊,

我們可以說greetingname 之間有reactive依存關系右蕊。

可以接下去看一個例子

server <- function(input, output, session) {
  string <- reactive(paste0("Hello ", input$name, "!"))
  output$greeting <- renderText(string()) ## 注意string的屬性是reactive, 所以是string()
}

這個例子在簡單的reactive里看不出什么作用吮螺,等以后編寫復雜的reactive的時候就可以大幅度減少代碼重復饶囚,提高效率了。

  • 練習

把下面的代碼黏貼給ui, 然后修改4個server的錯誤

ui <- fluidPage(
  textInput("name", "What's your name?"),
  textOutput("greeting")
)
server1 <- function(input, output, server) {
  input$greeting <- renderText(paste0("Hello ", name))
}

server2 <- function(input, output, server) {
  greeting <- paste0("Hello ", input$name)
  output$greeting <- renderText(greeting)
}

server3 <- function(input, output, server) {
  output$greting <- paste0("Hello", input$name)
}

2.3 Reactivity 表現(xiàn)

文中舉了一個比較復雜的例子鸠补。比較兩組數(shù)據(jù)的和密度圖萝风,并進行t-test。試著把代碼改編成Shiny App.

下面是正常R的實現(xiàn)方法紫岩。

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

t_test <- function(x1, x2) {
  test <- t.test(x1, x2)
  
  # use sprintf() to format t.test() results compactly
  sprintf(
    "p value: %0.3f\n[%0.2f, %0.2f]",
    test$p.value, test$conf.int[1], test$conf.int[2]
  )
}
x1 <- rnorm(100, mean = 0, sd = 0.5)
x2 <- rnorm(200, mean = 0.15, sd = 0.9)

freqpoly(x1, x2)
cat(t_test(x1, x2))
#> p value: 0.016
#> [-0.35, -0.04]

然后是改成shiny规惰。

首先是ui端,可以從文面大概猜到fluidRow column 的大概用法泉蝌,之后會花篇幅詳細介紹歇万。

ui <- fluidPage(
  fluidRow(
    column(4, 
      "Distribution 1",
      numericInput("n1", label = "n", value = 1000, min = 1),
      numericInput("mean1", label = "μ", value = 0, step = 0.1),
      numericInput("sd1", label = "σ", value = 0.5, min = 0.1, step = 0.1)
    ),
    column(4, 
      "Distribution 2",
      numericInput("n2", label = "n", value = 1000, min = 1),
      numericInput("mean2", label = "μ", value = 0, step = 0.1),
      numericInput("sd2", label = "σ", value = 0.5, min = 0.1, step = 0.1)
    ),
    column(4,
      "Frequency polygon",
      numericInput("binwidth", label = "Bin width", value = 0.1, step = 0.1),
      sliderInput("range", label = "range", value = c(-3, 3), min = -5, max = 5)
    )
  ),
  fluidRow(
    column(9, plotOutput("hist")),
    column(3, verbatimTextOutput("ttest"))
  )
)
server <- function(input, output, session) {
  output$hist <- renderPlot({
    x1 <- rnorm(input$n1, input$mean1, input$sd1)
    x2 <- rnorm(input$n2, input$mean2, input$sd2)
    
    freqpoly(x1, x2, binwidth = input$binwidth, xlim = input$range)
  }, res = 96)

  output$ttest <- renderText({
    x1 <- rnorm(input$n1, input$mean1, input$sd1)
    x2 <- rnorm(input$n2, input$mean2, input$sd2)
    
    t_test(x1, x2)
  })
}

https://hadley.shinyapps.io/ms-case-study-1 部署在云端服務器的效果。

代碼行數(shù)有點多勋陪,其實把變量之間的關系稍做整理然后可視化一下就會清楚很多贪磺。


稍做觀察就不難看出變量之間的關系很密切。這就造成了兩個問題诅愚。

  1. 因為關系網太密寒锚,所以導致這個app比較難理解。沒法單獨提取app里面的變量進行分析
  2. app計算效率不高。每修改一個變量都會導致整體計算全部重來刹前。

所以可以對這個app進行優(yōu)化泳赋。在前面套一個reactive函數(shù),讓x1,x2變成了可活動的變量腮郊。這樣就不會在x1或者x2發(fā)生改變的時候重新計算整個流程摹蘑,而是僅更新發(fā)生變化的地方。

Reactivity里的變量需要加(),表示是活動的函數(shù)轧飞,不是固定的value衅鹿。

server <- function(input, output, session) {
  x1 <- reactive(rnorm(input$n1, input$mean1, input$sd1))
  x2 <- reactive(rnorm(input$n2, input$mean2, input$sd2))

  output$hist <- renderPlot({
    freqpoly(x1(), x2(), binwidth = input$binwidth, xlim = input$range)
  }, res = 96)

  output$ttest <- renderText({
    t_test(x1(), x2())
  })
}

其實這里還涉及到了組快化的概念。如下圖所示


其實x1, x2都被組塊話了过咬,組塊話這個概念在之后的篇幅里會詳細介紹大渤。

2.4 Timer功能

Shiny里還有定時激活功能。

在下面的程序里添加Timer也就是定時自動激活功能掸绞。通過觀察代碼可以看出這是一段隨機數(shù)生成程序泵三。自動激活就等于自動重新生成隨機數(shù)。

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

這樣就成功變成了下面的模式衔掸。每隔默認的半秒鐘程序就會自動運行一次烫幕。

也可以添加Action標簽 。只有action標簽被點擊的時候程序才會運行敞映。

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 <- reactive({
    input$simulate
    rpois(input$n, input$lambda1)
  })
  x2 <- reactive({
    input$simulate
    rpois(input$n, input$lambda2)
  })
  output$hist <- renderPlot({
    freqpoly(x1(), x2(), binwidth = 1, xlim = c(0, 40))
  }, res = 96)
}

仔細看一下较曼,其實添加了按鈕只是多此一舉,只要改變了lambda或者n振愿,都會自動更新捷犹。因為從程序圖里可以看出這是一個并聯(lián)的關系,并不是串聯(lián)冕末。要把simulate串聯(lián)在里面才行萍歉。

所以需要進行下面的修改藻肄。用eventReactive ,稍微有點難懂销凑。有點接近If/else的邏輯關系。

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

文章還提到了觀測函數(shù)observer,用來提示命令是否被執(zhí)行毁兆∨线郑可以用來反饋代碼執(zhí)行情況梅桩。這個函數(shù)出的結果不會被保存在任何變量里。但是可以用來Debugg。

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")
  })
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末宋光,一起剝皮案震驚了整個濱河市逛漫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌菩暗,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舌胶,死亡現(xiàn)場離奇詭異,居然都是意外死亡娩井,警方通過查閱死者的電腦和手機扬霜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門材原,熙熙樓的掌柜王于貴愁眉苦臉地迎上來威酒,“玉大人橱赠,你說我怎么就攤上這事尤仍。” “怎么了宰啦?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵暖眼,是天一觀的道長。 經常有香客問我,道長剿干,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任置尔,我火速辦了婚禮幽歼,結果婚禮上,老公的妹妹穿的比我還像新娘谬盐。我一直安慰自己甸私,他們只是感情好,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布设褐。 她就那樣靜靜地躺著颠蕴,像睡著了一般泣刹。 火紅的嫁衣襯著肌膚如雪助析。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天椅您,我揣著相機與錄音外冀,去河邊找鬼。 笑死掀泳,一個胖子當著我的面吹牛雪隧,可吹牛的內容都是我干的西轩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼脑沿,長吁一口氣:“原來是場噩夢啊……” “哼藕畔!你這毒婦竟也來了?” 一聲冷哼從身側響起庄拇,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤注服,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后措近,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體溶弟,經...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年瞭郑,在試婚紗的時候發(fā)現(xiàn)自己被綠了辜御。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡屈张,死狀恐怖擒权,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情阁谆,我是刑警寧澤菜拓,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站笛厦,受9級特大地震影響纳鼎,放射性物質發(fā)生泄漏。R本人自食惡果不足惜裳凸,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一贱鄙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧姨谷,春花似錦逗宁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至捌议,卻和暖如春哼拔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瓣颅。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工倦逐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宫补。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓檬姥,卻偏偏與公主長得像曾我,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子健民,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內容