R語言程式設計導論

林嶔 (Lin, Chin)

Lesson 12 簡易網頁App撰寫

什麼是網頁應用程式(WebApp)?

– 前端:使用HTML/CSS/Java Script等技術製作網頁介面(與一般網頁無異)

– 後端:利用預先設定的模組與使用者進行互動,並回傳結果給前端

– 優勢:跨平台、更新方便

– 劣勢:速度較慢、需要穩定的連線

第一節:一個簡單的WebApp(1)

– 應該還記得怎樣安裝套件吧!

F01

第一節:一個簡單的WebApp(2)

F02

第一節:一個簡單的WebApp(3)

library(shiny)

# Define UI for application that plots random distributions 
shinyUI(pageWithSidebar(

  # Application title
  headerPanel("Hello Shiny!"),

  # Sidebar with a slider input for number of observations
  sidebarPanel(
    sliderInput("obs", "Number of observations:", min = 0, max = 1000, value = 500)
  ),

  # Show a plot of the generated distribution
  mainPanel(
    plotOutput("distPlot")
  )
))

F03

第一節:一個簡單的WebApp(4)

library(shiny)

# Define server logic required to generate and plot a random distribution
shinyServer(function(input, output) {
  
  # Expression that generates a plot of the distribution. The expression is
  # wrapped in a call to renderPlot to indicate that:
  # 
  # 1) It is 'reactive' and therefore should be automatically re-executed
  # when inputs change 2) Its output type is a plot
  output$distPlot = renderPlot({
    
    # generate an rnorm distribution and plot it
    dist = rnorm(input$obs)
    hist(dist)
  })
  
})

F04

第一節:一個簡單的WebApp(5)

Fig/F05

F06

練習1:建立自己的WebApp

– 如果你想複製貼上點不同的東西,可以在shiny-examples內找到更多例子。

第二節:簡單介紹shiny app架構(1)

  1. 使用者自ui.R中的給定一個參數。

  2. 這個參數傳到server.R裡面,使用反應函數進行計算。

  3. 反應完成後,再回傳至ui.R輸出反應結果。

第二節:簡單介紹shiny app架構(2)

library(shiny)

# Define UI for application that plots random distributions 
shinyUI(pageWithSidebar(

  # Application title
  headerPanel("Hello Shiny!"),

  # Sidebar with a slider input for number of observations
  sidebarPanel(
    sliderInput("obs", "Number of observations:", min = 0, max = 1000, value = 500)
  ),

  # Show a plot of the generated distribution
  mainPanel(
    plotOutput("distPlot")
  )
))
  1. headerPanel()用來定義網頁標題

  2. sidebarPanel()用來定義的控制選單內含哪些可控參數,本例中只有一個滑動輸入元件sliderInput(),元件為obs

  3. mainPanle()則是用來定義輸出區域的輸出結果,本例中只有一個圖片輸出元件plotOutput(),元件為distPlot

F07

第二節:簡單介紹shiny app架構(3)

library(shiny)

# Define server logic required to generate and plot a random distribution
shinyServer(function(input, output) {

    # Expression that generates a plot of the distribution. The expression is
    # wrapped in a call to renderPlot to indicate that:
    # 
    # 1) It is 'reactive' and therefore should be automatically re-executed
    # when inputs change 2) Its output type is a plot
    output$distPlot = renderPlot({

        # generate an rnorm distribution and plot it
        dist = rnorm(input$obs)
        hist(dist)
    })

})

Note:所有的輸入元件都存取在input這個List內;而所有的輸出元件都存取在output這個List內。

第二節:簡單介紹shiny app架構(4)

obs = 500
M = 170
S = 10
Coler = "blue"
dist = rnorm(obs, mean = M, sd = S)
hist(dist, col = Coler)

第二節:簡單介紹shiny app架構(5)

library(shiny)

# Define UI for application that plots random distributions 
shinyUI(pageWithSidebar(

  # Application title
  headerPanel("Hello Shiny!"),

  # Sidebar with inputs for number of observations, mean, SD, and coler.
  sidebarPanel(
    sliderInput("obs", "Number of observations:", min = 0, max = 1000, value = 500),
    numericInput("M", "Mean of this normal distribution:", min = -200, max = 200, value = 100),
    numericInput("S", "SD of this normal distribution:", min = 0, max = 50, value = 10),
    radioButtons("Color", "Select the color of histogram:", choices = c("Red" = "red", "Blue" = "blue", "Green" = "green"))
  ),

  # Show a plot of the generated distribution
  mainPanel(
    plotOutput("distPlot")
  )
))
library(shiny)

# Define server logic required to generate and plot a random distribution
shinyServer(function(input, output) {

    # Expression that generates a plot of the distribution. The expression is
    # wrapped in a call to renderPlot to indicate that:
    # 
    # 1) It is 'reactive' and therefore should be automatically re-executed
    # when inputs change 2) Its output type is a plot
    output$distPlot = renderPlot({

        # generate an rnorm distribution and plot it
        dist = rnorm(input$obs,mean=input$M,sd=input$S)
        hist(dist,col=input$Color)
    })

})

第三節:自訂函數與shiny app(1)

x = c(1, 1)

for (i in 3:20) {
  x[i] = x[i-1] + x[i-2]
}

x
Fibonacci = function (a, b, last.seq) {
  if (last.seq < 3) {
    cat("last.seq必須大於等於3。")
  } else {
    x = c(a, b)
    
    for (i in 3:last.seq) {
      x[i] = x[i-1] + x[i-2]
    }
    
    x
  }
}

Fibonacci(1, 1, 20)
Fibonacci(2, 4, 2)

第三節:自訂函數與shiny app(2)

library(shiny)

# Define UI for application that calculate the needed sample size 
shinyUI(pageWithSidebar(

  # Application title
  headerPanel("My App"),

  # Sidebar with numeric inputs
  sidebarPanel(
    numericInput("a", "The first value of sequence:", min = -100, max = 100, value = 1),
    numericInput("b", "The second value of sequence:", min = -100, max = 100, value = 1),
    numericInput("last.seq", "The length of sequence:", min = 3, max = 30, value = 20)
  ),

  # Show the outcome
  mainPanel(
    verbatimTextOutput("Seq")
  )
))
library(shiny)

# Your function
Fibonacci = function (a, b, last.seq) {
  if (last.seq < 3) {
    cat("last.seq必須大於等於3。")
  } else {
    x = c(a, b)
    
    for (i in 3:last.seq) {
      x[i] = x[i-1] + x[i-2]
    }
    
    x
  }
}

# Define server logic required to calculate the needed sample size
shinyServer(function(input, output) {

    output$Seq = renderPrint({
      Fibonacci(input$a, input$b, input$last.seq)
    })

})

練習2:找質數的App

prime_number = function (max.x) {
  
  x = 2:max.x
  answer.x = rep(TRUE, max.x-1)
  indexes = 1:(max.x/2-1) * 2
  answer.x[indexes + 1] = FALSE
  
  for (i in indexes) {
    pos = which(answer.x[1:((i-1)/3)])
    for (j in pos) {
      if (x[i] %% x[j] == 0) {
        answer.x[i] = FALSE
        break
      }
    }
  }
  
  x[answer.x]
  
}

prime_number(max.x = 100)
prime_number(max.x = 200)

練習2答案

library(shiny)

shinyUI(pageWithSidebar(
  
  headerPanel("My App"),
  
  sidebarPanel(
    numericInput("max.x", "The search range of prime number:", min = 2, max = 500, value = 100)
  ),
  
  mainPanel(
    verbatimTextOutput("Seq")
  )
))
library(shiny)

prime_number = function (max.x) {
  
  x = 2:max.x
  answer.x = rep(TRUE, max.x-1)
  indexes = 1:(max.x/2-1) * 2
  answer.x[indexes + 1] = FALSE
  
  for (i in indexes) {
    pos = which(answer.x[1:((i-1)/3)])
    for (j in pos) {
      if (x[i] %% x[j] == 0) {
        answer.x[i] = FALSE
        break
      }
    }
  }
  
  x[answer.x]
  
}

shinyServer(function(input, output) {
  
  output$Seq = renderPrint({
    prime_number(input$max.x)
  })
  
})

第四節:conditionalPanel的應用(1)

– conditionalPanel()可以讓我們的控制bar僅在特定條件下出現

– 常態分布必須要有兩個參數(平均數&標準差)決定,而t分布則必須要由一個參數決定(自由度),因此我們就必須設計當選擇常態分不時,控制區要出現的是平均數&標準差;若選擇了t分布,控制區要出現的則是自由度

library(shiny)
shinyUI(pageWithSidebar(
  
  headerPanel("Generating distribution"),
  
  sidebarPanel(
    selectInput("method", "Choose distribution:", choices=c("Normal"="norm", "Student t"="st")),
    helpText("Setting parameter(s) for distribution model"),
    conditionalPanel(condition="input.method=='norm'",
                     numericInput(inputId="mu", label="mean", value=0),
                     numericInput(inputId="sd", label="standard deviation", value=1, min=0)
    ),
    conditionalPanel(condition="input.method=='st'",
                     numericInput(inputId="df", label="Df", value=10, min=1)
    ),
    sliderInput(inputId="obs", 
                label="Number of observations:", 
                min = 1, max = 1000, value = 500)
  ),
  
  mainPanel(
    plotOutput("distPlot")
  )

))
library(shiny)

shinyServer(function(input, output) {
    output$distPlot <- renderPlot({
        if (input$method == "norm") 
            {dist <- rnorm(input$obs, mean = input$mu, sd = input$sd)}
        if (input$method == "st") 
            {dist <- rt(input$obs, df = input$df)}
        hist(dist)
    })
})

練習3:整合不同的功能

練習3答案

library(shiny)

shinyUI(pageWithSidebar(
  
  headerPanel("My App"),
  
  sidebarPanel(
    selectInput("method", "Choose a method:", choices = c("Fibonacci" = "1", "Prime Number" = "2")),
    conditionalPanel(condition="input.method=='1'",
                     numericInput("a", "The first value of sequence:", min = -100, max = 100, value = 1),
                     numericInput("b", "The second value of sequence:", min = -100, max = 100, value = 1),
                     numericInput("last.seq", "The length of sequence:", min = 3, max = 30, value = 20)
    ),
    conditionalPanel(condition="input.method=='2'",
                     numericInput("max.x", "The search range of prime number:", min = 2, max = 500, value = 100)
    )
  ),
  
  mainPanel(
    verbatimTextOutput("Seq")
  )
))
library(shiny)

Fibonacci = function (a, b, last.seq) {
  if (last.seq < 3) {
    cat("last.seq必須大於等於3。")
  } else {
    x = c(a, b)
    
    for (i in 3:last.seq) {
      x[i] = x[i-1] + x[i-2]
    }
    
    x
  }
}

prime_number = function (max.x) {
  
  x = 2:max.x
  answer.x = rep(TRUE, max.x-1)
  indexes = 1:(max.x/2-1) * 2
  answer.x[indexes + 1] = FALSE
  
  for (i in indexes) {
    pos = which(answer.x[1:((i-1)/3)])
    for (j in pos) {
      if (x[i] %% x[j] == 0) {
        answer.x[i] = FALSE
        break
      }
    }
  }
  
  x[answer.x]
  
}

shinyServer(function(input, output) {
  
  output$Seq = renderPrint({
    
    if (input$method == "1") {
      
      Fibonacci(input$a, input$b, input$last.seq)
      
    } else {
      
      prime_number(input$max.x)
      
    }
    
  })
  
})

小結

  1. 撰寫App(ui.R以及server.R)
  2. 在App內使用自創函數
  3. 利用conditionalPanel()增加使用者介面的自由度