สถาบันข้อมูลขนาดใหญ่ (องค์การมหาชน)

Logo BDI For web

เรียนรู้วิธีการสร้าง Shiny Interactive Web Apps บน R studio

Nov 18, 2020

หลายๆคนคงเคยทำการวิเคราะห์ข้อมูลแต่ไม่สามารถทำ User interface หรือ UI สวย ๆ ให้ได้ใช้และเผยแพร่ผ่านทางเว็บไซต์ได้ ในวันนี้ผมจะพามารู้จักกับแพคเกจบน R studio ที่จะช่วยให้เราสามารถทำทุกอย่างได้ตามที่กล่าวมาข้างต้น นั่นก็คือ Shiny นั่นเองครับ

Shiny คืออะไร?

Shiny คือ Web application framework สำหรับ Program R ซึ่งถือว่าเป็นการเปิดโลกของชาว R people เลยทีเดียว เพราะเราสามารถเขียนคำสั่งต่าง ๆ และแสดงผลลัพธ์ได้ในลักษณะของ interactive web application หรือ แดชบอร์ด ได้ใน Program R และสามารถสั่งรันได้ทั้งบนเซิฟเวอร์ของตัวเองเช่น Digital Ocean หรือบนเว็บไซต์ Shinyapps.io

เราจะได้เรียนรู้อะไรบ้างในบทความนี้

  1. โครงสร้างของ Shiny web application
  2. ตัวอย่าง Shiny web application
  3. ขั้นตอนการเขียน interactive web application

โครงสร้างของ Shiny web application ประกอบด้วย 3 องค์ประกอบหลักได้แก่

1. User interface (ui)

  • UI คือหน้าต่างที่คอยรับข้อมูลจากผู้ใช้งานและหน้าต่างแสดงผล

2. Server function (server)

  • Server คือระบบที่ได้รับค่าจาก UI มาทำการคำนวนและส่งผลลัพธ์เพื่อไปแสดงผลบนเว็บไซต์

3. ShinyApp function (การรวมกันระหว่างองค์ประกอบที่ 1 และ 2)


รูปที่ 1 ภาพโครงสร้างของ Shiny web application (แหล่งที่มาจาก Web Apps in R)

การสร้าง Web application โดยใช้ Shiny นั้นมีตัวอย่างให้ทุกคนได้ศึกษาหรือลองใช้มากมายเลยโดยเข้าได้ผ่านเว็บไซต์ Shiny gallery นี้ได้เลยครับ

รูปที่ 2 หน้าเว็บไซต์ shiny gallery

เรามาชมภาพตัวอย่าง Shiny web application กันดีกว่า

Interactive dashboard ด้านล่างเป็นการสร้าง web application ผ่าน Shiny โดยแสดงปริมาณผู้ติดเชื้อ COVID-19 ทั่วโลก ผู้สร้างคือ Dr. Edward Parker ซึ่งเปิดให้ผู้ใช้งานทั่วไปได้ใช้งานบน Shinyapps.io เพื่อน ๆ สามารถกดที่ Real time Covid-19 Tracker dashboard เพื่อเข้าไปใช้แดชบอร์ดนี้ได้เลยครับ

รูปที่ 3 COVID-19 tracker dashboard จากเว็บไซต์ shiny gallery

ตัวอย่างการเขียน interactive web application

เรามาถึงช่วงที่ทุกคนจะได้สนุกกันแล้วครับ นั่นคือการเขียนคำสั่งเพื่อสร้าง Interactive web application หรือ แดชบอร์ดของตัวเอง web application แรกที่ผมจะเริ่มทำจะเป็นการสร้างกราฟการแจกแจงความถี่หรือ ฮิสโตแกรมโดยกราฟจะเปลี่ยนเมื่อผู้ใช้งานเลือกจำนวน bins หรือแท่งกราฟตามที่ต้องการ ซึ่งเพื่อน ๆ สามารถเขียนโค้ดแล้วรันไปพร้อมกันได้เลยครับ ก่อนอื่นให้สร้างไฟล์ R script ที่เราจะเขียนคำสั่งหลังจากนั้นก็เริ่มกันได้เลย

ข้อมูลที่ผมใช้จะเป็นข้อมูลที่ติดมากับโปรแกรม R อยู่แล้วครับ นั่นคือชุดข้อมูล airquality หรือข้อมูลคุณภาพของอากาศ ก่อนอื่นเราต้องนำเข้าข้อมูลรวมถึงแพคเกจที่เราจำเป็นต้องใช้กันก่อนครับ

library(shiny) # Load R packages
data(airquality)    # Load data

ถ้าผู้ใช้งานใหม่ยังไม่เคยติดตั้งแพคเกจ ให้ใช้คำสั่ง install.package(“ชื่อแพคเกจ”) ก่อนการเรียก library นะครับ

จากโครงสร้างของ Shiny web application ที่กล่าวไปข้างต้นเราต้องเริ่มจากการสร้าง User interface

ui <- fluidPage(                                   # หัวข้อแดชบอร์ด
  titlePanel("Ozone level!"),                      # Sidebar layout ประกอบด้วยการสร้างกล่อง Input และ Output ---- 
  sidebarLayout(                                   # Sidebar panel for inputs ----
      sidebarPanel(                              # Input: Slider สำหรับเลือกจำนวนแท่งของกราฟ ----
            sliderInput(inputId = "bins",
                  label = "Number of bins:",
                  min = 1,
                  max = 50,
                  value = 30)                      # ค่าเริ่มต้นของจำนวน bins
          ),
    mainPanel(                                     # Main panel สำหรับแสดงผลหรือ output ----
      plotOutput(outputId = "distPlot")            # Output: Histogram ----
    )
  )
)

คำสั่งข้างต้นเป็นการกำหนดหน้าตาของ web application ที่เราจะสร้าง ซึ่งหลังเครื่องหมาย # จะเป็นคำอธิบายคำสั่งในบรรทัดนั้น ๆ ครับ โดย input จะมีหน้าตาออกมาเป็น slide bar เพื่อเลือกจำนวน bins ที่เราต้องการโดยมีค่าเริ่มต้นเป็น 30  ส่วน output จะมีหน้าตาเป็นกราฟฮิสโตแกรม โดยทั้งหมดนี้จะถูกเก็บไว้ในตัวแปรที่ชื่อว่า ui ครับ

ขั้นตอนต่อไปคือการสร้าง Server function เป็นการกำหนดว่าเราจะใช้ข้อมูลอะไร คำนวนอย่างไรให้ได้ output ออกมาเป็นฮิสโตแกรมที่เราต้องการ

server <- function(input, output) {
  output$distPlot <- renderPlot({
    x    <- airquality$Ozone                               #กำหนดคอลัมน์ Ozone ใน ข้อมูล airquality เพื่อนำมาพลอต
    x    <- na.omit(x)                                   #ลบข้อมูลที่เป็น na ออก
    bins <- seq(min(x), max(x), length.out = input$bins + 1)  #เก็บค่าที่นับไว้ในตัวแปร bins
    hist(x, breaks = bins, col = "#75AADB", border = "black", #สร้างกราฟจากข้อมูลและ bins ที่กำหนดไว้ด้านบนรวมถึงการกำหนดธีมและสีของกราฟ
         xlab = "Ozone level",                                #ชื่อแกน x
         main = "Histogram of Ozone level")                   #ชื่อหัวข้อกราฟ
  })
}

คำสั่งด้านบนเป็นการเขียนฟังก์ชันคำนวนจำนวน bins และการสร้างกราฟฮิสโตแกรมและเก็บคำสั่งไว้ในตัวแปรที่ชื่อว่า server ครับ

ใกล้จะเสร็จแล้ว ขั้นตอนสุดท้ายง่ายมากเลยครับคือการนำทั้งสองขั้นตอนแรกมารวมกันเพื่อสร้าง web application คำสั่งก็จะมีแค่การนำตัวแปรทั้งสองตัวมาระบุไว้ใน shinyApp นั่นเอง โดยใช้คำสั่งด้านล่าง

shinyApp(ui = ui, server = server) 

เมื่อเราสั่งรันทุกอย่างแล้วนั้นทุกคนก็จะได้แดชบอร์ดแรกที่สามารถเลื่อน slide bar แล้วกราฟแท่งด้านขวาก็จะปรับเปลี่ยนไปตามจำนวน bins ที่เราเลือกได้เลยครับ

รูปที่ 4 แดชบอร์ดแสดงฮิสโตแกรม ozone level จากชุดข้อมูล airquality

ยินดีด้วยครับทุกคน เราได้ web application แรกกันแล้ว

ตัวอย่างการนำโมเดลขึ้น web application

แดชบอร์ดต่อไปจะเริ่มยากขึ้นโดยการประยุกต์นำโมเดลการทำนายมาใส่ไว้ในแดชบอร์ดด้วย เพื่อน ๆ บางคนน่าจะคุ้นชินกันดีกับข้อมูล iris หรือข้อมูลพันธ์ดอกไม้ไอริสที่จะมีลักษณะทางกายภาพของดอกไม้ในแต่ละ species นั้น ๆ โดยส่วนใหญ่ข้อมูลนี้จะเป็นตัวอย่างในการสร้างโมเดลพื้นฐานสำหรับผู้ที่เริ่มต้นทำโมเดลการทำนาย แต่ในสำหรับบทความนี้ผมจะเน้นไปในเรื่องการนำโมเดลที่ทำนายแล้วขึ้นเป็น Interactive web application หรือ แดชบอร์ด

รูปที่ 5 ตัวอย่างแสดงพันธุ์ดอกไม้ของชุดข้อมูล iris ที่มา: สรุป Machine Learning(EP.1)- ว่าด้วยเรื่องชุดข้อมูล (DataSet)

ก่อนอื่นเราต้องติดตั้งและเรียกใช้แพคเกจที่ต้องการรวมถึงข้อมูล iris

library(RCurl)  #สำหรับนำเข้าข้อมูล csv ไฟล์
library(randomForest)   #สร้างโมเดล
library(caret)

นำเข้าข้อมูล iris dataset

iris <- read.csv(text = getURL("https://raw.githubusercontent.com/dataprofessor/data/master/iris.csv"))

ขั้นตอนการสร้างโมเดล

  • แบ่งชุดข้อมูลเป็น 2 ส่วนคือ Train 80% และ Test 20%
TrainingIndex <- createDataPartition(iris$Species, p=0.8, list = FALSE)
TrainingSet <- iris[TrainingIndex,] # Training Set
TestingSet <- iris[-TrainingIndex,] # Test Set
  • Export ทั้งสองชุดข้อมูลลงเครื่อง
write.csv(TrainingSet, "training.csv")
write.csv(TestingSet, "testing.csv")
  • เรียกข้อมูล Train ไว้ในตัวแปรชื่อว่า TrainSet และนำหัวตารางออก
TrainSet <- read.csv("training.csv", header = TRUE,stringsAsFactors = T)
TrainSet <- TrainSet[,-1]
  • สร้างโมเดล Random Forest จากข้อมูล TrainSet แล้วเก็บไว้ในตัวแปรชื่อว่า model
model <- randomForest(Species ~ ., data = TrainSet, ntree = 500, mtry = 4, importance = TRUE)
  • บันทึกข้อมูลลงเครื่องเพื่อนำไปใช้ด้วยสกุลไฟล์ .rds
saveRDS(model, "model.rds")

เมื่อเราได้ไฟล์ข้อมูลที่ถูกแบ่งและไฟล์โมเดล เราจะเอาโมเดลมานี้สร้างแดชบอร์ดกัน โดยไฟล์เหล่านี้ควรจะที่โฟลเดอร์เดียวกันกับ R script ที่ใช้ในการสร้างแดชบอร์ด

เรามาเริ่มสร้างแดชบอร์ดด้วย 3 ขั้นตอนง่าย ๆ ตามตัวอย่างแรกกันเลยครับ

ในกรณีตัวอย่างนี้ผมได้แยกคำสั่งเป็นสองไฟล์โดยการบันทึกโมเดลลงเครื่องก่อนเพื่อสามารถนำไปประยุกต์ใช้กับ web application ได้หลายเว็บเพราะฉะนั้นเราจึงต้อง import ข้อมูลและโมเดลที่เราจะใช้ก่อนการสร้าง web application ทุกครั้ง

  • เรียกใช้แพคเกจต่างๆ
  • นำเข้าโมเดลที่บันทึกไว้เก็บไว้ในตัวแปรชื่อว่า model
  • เรียกข้อมูล Train ไว้ในตัวแปรชื่อว่า TrainSet และนำหัวตารางออก
  • เริ่มคำสั่งสร้าง User interface function หรือ UI
library(shiny)
library(data.table)
model <- readRDS("model.rds")
TrainSet <- read.csv("training.csv", header = TRUE, stringsAsFactors = T)
TrainSet <- TrainSet[,-1]
ui <- pageWithSidebar(
  headerPanel('Iris Predictor'),# กำหนดชื่อแดชบอร์ด
    # สร้างแถบ input ต่าง ๆ
  sidebarPanel(
    HTML("<h3>Input parameters</h3>"),
    sliderInput("Sepal.Length", label = "Sepal Length", value = 5.0,
                min = min(TrainSet$Sepal.Length),
                max = max(TrainSet$Sepal.Length)),
    sliderInput("Sepal.Width", label = "Sepal Width", value = 3.6,
                min = min(TrainSet$Sepal.Width),
                max = max(TrainSet$Sepal.Width)),
    sliderInput("Petal.Length", label = "Petal Length", value = 1.4,
                min = min(TrainSet$Petal.Length),
                max = max(TrainSet$Petal.Length)),
    sliderInput("Petal.Width", label = "Petal Width", value = 0.2,
                min = min(TrainSet$Petal.Width),
                max = max(TrainSet$Petal.Width)),
      actionButton("submitbutton", "Submit", class = "btn btn-primary")), # สร้างปุ่ม submit
    mainPanel(# สร้างแถบแสดงผล
    tags$label(h3('Status/Output')), # Status/Output Text Box
    verbatimTextOutput('contents'),
    tableOutput('tabledata'))) #ผลการทำนาย

เช่นเดิมจากคำสั่งด้านบนจะมีคำอธิบายในแต่ละคำสั่งหลังเครื่องหมาย # นะครับ

คำสั่งการสร้าง ui ด้านบนผมได้สร้างแถบเครื่องมือเป็น slide bar รับข้อมูลของผู้ใช้งานด้วยลักษณะของดอกไม้ที่ใช้ทำนายนั่นคือ Sepal.Length, Sepal.Width, Petal.Length, Petal.Width ส่วนแถบ output ก็จะแสดงตารางซึ่งคือผลของการทำนายนั่นเอง

ขั้นตอนต่อไปเราก็มาสร้าง Server function กันเลย

server<- function(input, output) {
  datasetInput <- reactive({   #กำหนด input data ที่จะถูกป้อนเข้ามาผ่านทาง slide bar
      df <- data.frame(
      Name = c("Sepal Length",
               "Sepal Width",
               "Petal Length",
               "Petal Width"),
      Value = as.character(c(input$Sepal.Length,
                             input$Sepal.Width,
                             input$Petal.Length,
                             input$Petal.Width)),
      stringsAsFactors = FALSE)
    Species <- 0
    df <- rbind(df, Species)
    input <- transpose(df)
    write.table(input,"input.csv", sep=",", quote = FALSE, row.names = FALSE, col.names = FALSE)
    test <- read.csv(paste("input", ".csv", sep=""), header = TRUE)
    Output <- data.frame(Prediction=predict(model,test), round(predict(model,test,type="prob"), 3))     #นำข้อมูลที่ถูกป้อนเข้าโมเดลทำนาย
    print(Output)
  })
  
# สร้างกล่องข้อความในกรณีที่รอการป้อนข้อมูลและคำนวนเรียบร้อยแล้ว
  output$contents <- renderPrint({
    if (input$submitbutton>0) { 
      isolate("Calculation complete.") 
    } else {
      return("Server is ready for calculation.")
    }
  })
  # สร้างตารางผลการทำนายผ่านเงื่อนไขการกดปุ่ม submit
  output$tabledata <- renderTable({
    if (input$submitbutton>0) { 
      isolate(datasetInput()) 
    } 
  })
}

ขั้นตอนสุดท้ายง่ายสุด ๆ เลยครับ คือการรวมทั้งสองขั้นตอนเข้าด้วยกัน

shinyApp(ui = ui, server = server)

แล้วเราก็จะได้ Interactive web application ทำนายพันธุ์ดอกไม้จาก iris dataset ตามรูปภาพด้านล่างเลย

รูปที่ 6 แดชบอร์ดทำนายพันธุ์ดอกไม้ของข้อมูล iris แบบ slide bar

สำหรับคนที่ไม่ชอบ slide bar ในการป้อนข้อมูลก็จะมีวิธีสร้างแถบ input ด้วยการกรอกตัวเลข ง่าย ๆ เลยเราก็แก้ไขคำสั่งในการสร้าง User interface หรือ ui จาก sliderInput เป็น numericInput เท่านั้นเองครับ

ui <- pageWithSidebar(
   headerPanel('Iris Predictor'),
   sidebarPanel(
   tags$label(h3('Input parameters')),
    numericInput("Sepal.Length", 
                 label = "Sepal Length", 
                 value = 5.1),
    numericInput("Sepal.Width", 
                 label = "Sepal Width", 
                 value = 3.6),
    numericInput("Petal.Length", 
                 label = "Petal Length", 
                 value = 1.4),
    numericInput("Petal.Width", 
                 label = "Petal Width", 
                 value = 0.2),
   actionButton("submitbutton", "Submit", 
                 class = "btn btn-primary")
  ), mainPanel(
    tags$label(h3('Status/Output')), # Status/Output Text Box
    verbatimTextOutput('contents'),
    tableOutput('tabledata') # Prediction results table
  ))

Web application เราก็จะออกมาเป็นการป้อนตัวเลขเข้าไปแทน slide bar แล้ว

รูปที่ 7 แดชบอร์ดทำนายพันธุ์ดอกไม้ของข้อมูล iris แบบกรอกตัวเลข

ทั้งนี้เพื่อน ๆ สามารถนำแดชบอร์ดที่เขียนไว้สั่งรันบนเว็บไซต์ Shinyapps.io ได้โดยไปที่ลิงค์ shinyapps.io ลงทะเบียนสร้างบัญชีของตัวเองก่อนหลังจากนั้นเพื่อนๆก็จะสามารถ public แดชบอร์ดขึ้นบนว็บไซต์ได้แล้ว โดยผมจะมาบอกวิธีการให้เพื่อนๆได้ลองทำตามได้ในบทความต่อไปครับ

เป็นไงกันบ้างครับทุกคน หลาย ๆ คนที่ยังไม่คุ้นชินกับ Shiny อาจจะมึนงงกันนิดหน่อยสำหรับคนที่ไม่เคยใช้คำสั่งหรือโค้ดในการสร้างแดชบอร์ดแบบนี้เพราะในปัจจุบันก็มีเครื่องมือสำเร็จรูปในการสร้างแดชบอร์ดมากมาย

แต่สำหรับ Shiny นั้นประโยชน์ที่สำคัญเลยคือเขียนบน Program R และทุกอย่างฟรี ประหยัดงบในการที่จะต้องซื้อโปรแกรมสำเร็จรูปได้มากเลยทีเดียว รวมถึงยังมีคลังแดชบอร์ดมากมายใน Shiny gallery ให้ได้ศึกษา และสามารถใช้แพคเกจต่าง ๆ ใน Program R เข้ามาร่วมในการทำแดชบอร์ดนี้ได้อีกด้วย ซึ่งใน Shiny ยังมี package เพิ่มเติมที่ช่วยให้ Shiny มีประสิทธิภาพมากขึ้นอีกมากมายเลย เช่น shinythemes ที่จะช่วยปรับธีม สี และฟอนต์ให้แดชบอร์ดเราดูสวยงามมากขึ้น shinydashboard ที่ถูกเขียนมาเพื่อวางโครงสร้างของแดชบอร์ดให้ผู้เขียนสามารถปรับแต่งได้ง่ายมากยิ่งขึ้น และแพคเกจอย่าง shinyjs ที่ถูกพัฒนามาช่วยปรับปรุงแอพได้เหมือนภาษา JavaScript เลย

หวังว่าบทความนี้จะเป็นไอเดียที่ดีในการที่ผู้อ่านจะนำไปต่อยอดหรือสร้างสุดยอด web application ในแบบของตัวเองต่อไปครับ

ขอขอบคุณแหล่งความรู้ดี ๆ จาก Youtube Dataprofessor : https://www.youtube.com/dataprofessor

Yuranan Jamjuree

Data Scientist Government Big Data Institute: GBDi

© Big Data Institute | Privacy Notice