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的功能
- 自動更新input和output
- 把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的流程圖。比方說剛才的例子就可以變成觅赊,
我們可以說greeting
和 name
之間有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ù)有點多勋陪,其實把變量之間的關系稍做整理然后可視化一下就會清楚很多贪磺。
稍做觀察就不難看出變量之間的關系很密切。這就造成了兩個問題诅愚。
- 因為關系網太密寒锚,所以導致這個app比較難理解。沒法單獨提取app里面的變量進行分析
- 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")
})
}