互動式圖表及 R 語言

The world is discussed in terms of feelings and ideologies rather than as an area of knowledge.

Hans Rosling

在如何探索資料中我們已經學習如何使用視覺化作為探索資料特徵與內涵的工具之一,在探索過程中運用分組、摘要與作圖函數快速地建立眾多圖形,搭配使用機器學習演算法所建立出的預測模型,這些過程可以讓資料科學團隊對資料暸若指掌。就像輕量級標記式語言所述,專案的價值會在將對資料的理解傳達給其他團隊成員時發揮得更淋漓盡致。這些受眾可能不具備資料科學的背景知識,也沒有時間深入研究資料,為了幫助他們快速理解專案,值得我們投入大量時間與精神讓專案情節能夠簡單易懂,多數的溝通中文字比表格稍差、表格又略不如靜態圖形、靜態圖形略遜於互動式圖表。

Hans Rosling、Gapminder 與 Factfulness

被 Bill Gates 大力推薦、譽為是他「人生中閱讀過最重要的作品之一,帶領讀者清晰認識世界的指南」,Factfulness 一書是由瑞典 Karolinska 醫學院的國際衛生學教授 Hans Rosling(1948–2017) 所著,Hans Rosling 創辦 Gapminder 基金會,他最著名的 TED Talk: The Best Stats You’ve Ever Seen 被資料科學愛好者奉為視覺化溝通的典範;利用 4 分鐘、使用 1 張互動式圖表、援引超過 12 萬列資料、清晰傳達全世界 200 多個國家、近 200 年的財富及健康演變趨勢。

https://youtu.be/jbkSRLYSojo

瀏覽最終成品

我們希望利用 Plotly、Shiny 與 R 語言複製出 Gapminder 視覺化,點選連結可以在往後閱讀前先瀏覽最終成品:

https://yaojenkuo.shinyapps.io/R_gapminder_replica/

https://youtu.be/pPP7Lhr9yck

關於 Plotly 與 Shiny

Plotly 是來自加拿大蒙特婁的新創公司,這個團隊為 R 語言,Python、JavaScript 開發並且維護極受歡迎的開源互動視覺化模組與套件,主要產品包含 Plotly.js、Plotly.R、Plotyly.py 與 Dash。Plotly.R 能有效幫助 R 語言使用者不需要額外去學習 JavaScript 就能夠建立出互動性、具備 D3.js 及 WebGL 特性的圖表。

Shiny 是一個 R 語言套件,由 RStudio 開發及維護,透過 Shiny 能夠直接從 R 建置互動式的網頁應用程式,進而在網頁上部署、嵌入 RMarkdown 文件或設計儀表板。

繪製 Plotly 氣泡圖

首先安裝兩個套件 gapminder 與 plotly 。

install.packages(c("gapminder", "plotly"))
library(gapminder)
library(plotly)
1
2
3

gapminder 套件會給我們一個關於預期壽命,人均 GDP 與國家人口數等變數的 Gapminder 資料摘錄版本,這個摘錄版本僅有 1704 個觀測值、6 個變數,涵括 1952 至 2007 年中每五年、142 個國家的快照。

# Library packages
# install.packages(c("gapminder", "plotly"))
library(gapminder)
library(plotly)

# About gapminder data
gapminder_dim <- dim(gapminder)
gapminder_years <- unique(gapminder$year)
gapminder_n_countries <- length(unique(gapminder$country))
sprintf("這個摘錄版本僅有 %d 個觀測值、%d 個變數,涵括 %d 至 %d 年中每五年、%d 個國家的快照。",
        gapminder_dim[1], gapminder_dim[2], min(gapminder_years), max(gapminder_years), gapminder_n_countries)
1
2
3
4
5
6
7
8
9
10
11
## [1] "這個摘錄版本僅有 1704 個觀測值、6 個變數,涵括 1952 至 2007 年中每五年、142 個國家的快照。"
1

至於 plotly 套件則能夠讓我們呼叫 plot_ly() 函數,藉由調整參數就能夠繪製具有基礎互動效果的視覺化,氣泡圖與 gapminder 資料的對應關係為:

  • X 軸變數:gdpPercap
  • Y 軸變數:lifeExp
  • 氣泡大小:pop
  • 氣泡顏色:continent

值得注意的技巧是在 X 軸應用 log scale 避免多數的氣泡擠在左邊,並利用圓面積公式調整氣泡大小。

# Library packages
# install.packages(c("gapminder", "plotly"))
library(gapminder)
library(plotly)

# Bubble chart
bubble_radius <- sqrt(gapminder$pop / pi)
gapminder %>% 
  plot_ly(x = ~gdpPercap, y = ~lifeExp,
        size = ~pop, type = "scatter", mode = "markers",
        color = ~continent, text = ~country, hoverinfo = "text",
        sizes = c(min(bubble_radius), max(bubble_radius))) %>%
  layout(xaxis = list(type = "log"))
1
2
3
4
5
6
7
8
9
10
11
12
13

plot_ly() 函數繪製出來的圖形所包含的基礎互動效果有:

  • Hover:滑鼠游標移至圖形上會提示資訊
  • Zoom In/Out:可以將圖形放大或縮小
  • Filter:可以選取部分資料觀察

Hover:滑鼠游標移至圖形上會提示資訊

Zoom In/Out:可以將圖形放大或縮小

Filter:可以選取部分資料觀察

接著安裝套件 shiny 。

install.packages("shiny")
library(shiny)
1
2

Shiny 套件可以讓我們編寫單一檔案的 app.R 或者多檔案的 ui.R 與 server.R 建立網頁應用程式;Shiny 與 Plotly 套件整合得很完善,能夠允許使用者呼叫 renderPlotly()plot_ly() 繪製具有基礎互動效果的圖形呈現在 Shiny 網頁應用程式之中。

# app.R
# Library packages
# install.packages(c("gapminder", "plotly", "shiny"))
library(gapminder)
library(plotly)
library(shiny)

# Globar variables
bubble_radius <- sqrt(gapminder$pop / pi)

# Define UI for application
ui <- fluidPage(
   # Application title
   titlePanel("R Gapminder Replica"),
   # Plotly rendering
   mainPanel(
        plotlyOutput("gapminder")
   )
)

# Define server logic
server <- function(input, output) {
   output$gapminder <- renderPlotly({
     gapminder %>% 
       plot_ly(x = ~gdpPercap, y = ~lifeExp,
               size = ~pop, type = "scatter", mode = "markers",
               color = ~continent, text = ~country, hoverinfo = "text",
               sizes = c(min(bubble_radius), max(bubble_radius))) %>%
       layout(xaxis = list(type = "log"))
   })
}

# Run the application 
shinyApp(ui = ui, server = server)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

在 Shiny 網頁應用程式中繪製 Plotly 圖形

加入時間軸滑桿篩選年份

目前的氣泡圖設置將 1952 至 2007 每隔五年共 11 個時間點的資料點快照全部都顯示出來,因此我們需要加入一個時間軸滑桿(Timeframe slider)篩選資料的顯示,在 Plotly.R 中只需要於 plot_ly() 函數中加入參數 frame = ~year 就可以完成。

# Library packages
# install.packages(c("gapminder", "plotly"))
library(gapminder)
library(plotly)

# Bubble chart
bubble_radius <- sqrt(gapminder$pop / pi)
range_gdpPercap <- log10(range(gapminder$gdpPercap) + c(-200, 20000))
range_lifeExp <- range(gapminder$lifeExp) + c(-30, 30)

gapminder %>% 
  plot_ly(x = ~gdpPercap, y = ~lifeExp,
        size = ~pop, type = "scatter", mode = "markers",
        color = ~continent, text = ~country, frame = ~year, hoverinfo = "text",
        sizes = c(min(bubble_radius), max(bubble_radius))) %>%
  layout(xaxis = list(type = "log",
                      range = range_gdpPercap),
         yaxis = list(range = range_lifeExp))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

加入參數 frame = ~year

如果是使用 Shiny 套件提供的時間軸滑桿,可以呼叫 sliderInput() 函數,記得加入參數 animate = TRUE 即可擁有跟 Ploty.R 相似的播放功能。值得注意的地方是,為避免每一次年份更動都重新渲染(re-rendering)氣泡圖,可以將時間軸滑桿所連動的資料篩選用 reactive() 函數包裝起來。

# app.R
# Library packages
# install.packages(c("gapminder", "plotly", "shiny"))
library(gapminder)
library(plotly)
library(shiny)
library(dplyr)

# Globar variables
bubble_radius <- sqrt(gapminder$pop / pi)
unique_year <- unique(gapminder$year)
range_gdpPercap <- log10(range(gapminder$gdpPercap) + c(-200, 20000))
range_lifeExp <- range(gapminder$lifeExp) + c(-30, 30)

# Define UI for application
ui <- fluidPage(
   # Application title
   titlePanel("R Gapminder Replica"),
   # Sidebar panel
   sidebarLayout(
     # Silder input
     sidebarPanel(
       sliderInput(
         "year",
         "Year:",
         min = min(gapminder$year),
         max = max(gapminder$year),
         value = min(gapminder$year),
         step = unique_year[2] - unique_year[1],
         sep = "",
         animate = TRUE
       )
     ),
     # Plotly rendering
     mainPanel(
       plotlyOutput("gapminder_bubble")
     )
   )
   
)

# Define server logic
server <- function(input, output) {
  # reactive filtering
  reactive_gapminder <- reactive(
    gapminder %>%
      filter(year == input$year)
  )
  output$gapminder_bubble <- renderPlotly({
    reactive_gapminder() %>% 
       plot_ly(x = ~gdpPercap, y = ~lifeExp,
               size = ~pop, type = "scatter", mode = "markers",
               color = ~continent, text = ~country, hoverinfo = "text",
               sizes = c(min(bubble_radius), max(bubble_radius))) %>%
       layout(xaxis = list(type = "log",
                           range = range_gdpPercap),
              yaxis = list(range = range_lifeExp))
   })
}

# Run the application 
shinyApp(ui = ui, server = server)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

使用 Shiny 套件提供的時間軸滑桿

加入複選框清單篩選洲別

目前的氣泡圖設置將 Asia、Europe、Africa、Americas 與 Oceania 五個洲的的資料點全部顯示出來,因此還會需要一個能篩選洲別資料的控制元件,常見對類別資料篩選的使用者介面元件有下拉式選單(Dropdown list)、 選項按鈕(Radio buttons)與複選框清單(Checkbox list);由於下拉式選單及選項按鈕應用的情境都是單選,故使用複選框清單較合乎篩選洲別的需求。

呼叫 checkboxGroupInput() 函數可以建立複選框清單,只要使用 Shiny 套件的使用者見面元件都必須考慮圖形重新渲染(re-rendering)的議題,因此這裡我們同樣將複選框清單所連動的資料篩選用 reactive() 函數包裝起來,由於 Plotly.R 建立時間軸滑感相對簡易、Shiny 套件有提供複選框清單元件,我們採取讓 Plotly.R 與 Shiny 套件分別負責時間軸滑桿及複選框清單。

另外一個值得注意的細節是,當洲別的複選框清單沒有被勾選,因為圖形上沒有任何資訊可以提示會導致錯誤訊息:

Error in : Column `hoverinfo` must be length 0, not 1
1

為了避免圖形出現錯誤,我們在 renderPlotly() 函數中加入 Shiny 套件的驗證函數 validate(),如此在沒有選擇任何一個洲別資料的時候會出現提示訊息: Check at least one continent!

# app.R
# Library packages
# install.packages(c("gapminder", "plotly", "shiny"))
library(gapminder)
library(plotly)
library(shiny)
library(dplyr)

# Globar variables
bubble_radius <- sqrt(gapminder$pop / pi)
unique_continents <- unique(gapminder$continent)
range_gdpPercap <- log10(range(gapminder$gdpPercap) + c(-200, 20000))
range_lifeExp <- range(gapminder$lifeExp) + c(-30, 30)

# Define UI for application
ui <- fluidPage(
   # Application title
   titlePanel("R Gapminder Replica"),
   # Sidebar panel
   sidebarLayout(
     # CheckboxGroup input
     sidebarPanel(
       checkboxGroupInput(
         "continents",
         "Continents:",
         choices = unique_continents,
         selected = unique_continents
       )
     ),
     # Plotly rendering
     mainPanel(
       plotlyOutput("gapminder_bubble")
     )
   )
   
)

# Define server logic
server <- function(input, output) {
  # reactive filtering
  reactive_gapminder <- reactive(
    gapminder %>%
      filter(continent %in% input$continents)
  )
  output$gapminder_bubble <- renderPlotly({
    validate(
      need(input$continents, 'Check at least one continent!')
    )
    reactive_gapminder() %>% 
       plot_ly(x = ~gdpPercap, y = ~lifeExp,
               size = ~pop, type = "scatter", mode = "markers",
               color = ~continent, text = ~country, frame = ~year, hoverinfo = "text",
               sizes = c(min(bubble_radius), max(bubble_radius))) %>%
       layout(xaxis = list(type = "log",
                           range = range_gdpPercap),
              yaxis = list(range = range_lifeExp))
   })
}

# Run the application 
shinyApp(ui = ui, server = server)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

加入複選框清單

在沒有選擇任何一個洲別資料的時候會出現提示訊息

部署 Shiny 網頁應用程式

將 Shiny 套件的網頁應用程式部署並能透過網址分享最簡單的方式為透過 shinyapps.ioshinyapps.io 是由 RStudio 團隊開發並維運的雲端服務平台,可以讓使用者快速地透過四個步驟分享 Shiny 網頁應用程式:

  1. 安裝並載入 rsconnect 套件
  2. 申請一個 shinyapps.io 帳號
  3. 設定 rsconnect
  4. 一鍵部署

安裝與載入 rconnect 套件與其他套件的操作相同:

install.packages('rsconnect')
library(rsconnect)
1
2

前往 shinyapps.io 申請一個帳號:

前往 shinyapps.io 申請一個帳號

申請後取得帳號、密碼與憑證

申請後取得帳號、密碼與憑證

利用 rsconnect 的 setAccountInfo() 函數設定帳號、密碼與憑證:

rsconnect::setAccountInfo(name="<ACCOUNT>", token="<TOKEN>", secret="<SECRET>")
1

最後使用 RStudio 提供使用者「一鍵部署」到 shinyapps.io 的功能:

於 RStudio 點選發佈應用程式

點選發佈

大功告成,我們成功將複製的 Gapminder 視覺化 Shiny 應用程式部署到 shinyapps.io,並透過一個網址與他人分享:

https://yaojenkuo.shinyapps.io/R_gapminder_replica/

小結

在這個小節中我們從 Gapminder 創辦人、 Factfulness 一書作者 Hans Rosling 的視覺化典範切入主題、先瀏覽最終互動視覺化的成品、接著簡介 Plotly 與 Shiny 這兩個套件、繪製 Plotly 氣泡圖、加入時間軸滑桿篩選年份、加入複選框清單篩選洲別與最後是如何部署 Shiny 網頁應用程式。

延伸閱讀