Trực quan hóa (p2)

Thinh Ong

Mục tiêu

  • Hiểu cách sử dụng các lệnh geom_*() của ggplot

  • Sử dụng plotly để tạo biểu đồ tương tác

  • Học cách vẽ bản đồ bằng leaflet

library(tidyverse)
library(sf)
library(plotly)
library(leaflet)

ggplot

ggplot

Như đã học ở phần trước, quá trình vẽ ggplot được thực hiện tương tự như quá trình vẽ đồ thị thủ công, bao gồm việc vẽ từng lớp đồ thị và các lớp sẽ được nối bằng dấu +.

Quá trình vẽ như sau

  • Bắt đầu với lớp nền bằng lệnh ggplot() - nơi người dùng thường quy định dữ liệu được vẽ và các biến nằm trên trục x và y

  • Thêm các lớp geom - mỗi lớp geom sẽ thêm 1 loại biểu đồ (VD: biểu đồ cột, biểu đồ đường, biểu đồ phân tán, histogram). Các hàm này đều bắt đầu bằng geom_*

  • Thêm các yếu tố thiết kế vào đồ thị, chẳng hạn như nhãn trục, tiêu đề, phông chữ, kích thước, phối màu, chú giải hoặc xoay trục

ggplot

VD: Vẽ số ca theo thời gian từ dữ liệu covid

# ------ Tính số ca theo ngày phát triệu chứng ------ 
linelist <- read_rds("../data/simulated_covid.rds") %>% mutate_if(is.Date, ymd) 
case_data <- linelist %>% 
  group_by(date_onset) %>% # nhóm theo ngày phát triệu chứng
  summarize(
    no_cases = n() # đếm số ca theo nhóm
  ) 

# ------- Vẽ biểu đồ bằng ggplot() ------ 
ggplot(
  data = case_data, # sử dụng case_data để plot
  # quy định biến sử dụng làm trục x và y
  # lấy ngày phát triệu chứng làm trục x, số ca làm trục y
  aes(x = date_onset, y = no_cases)
) +                   
  geom_col( # vẽ biểu đồ cột
    # thêm các tuỳ chọn khác, ở vd này quy định màu là "cornflowerblue"
    color = "cornflowerblue"    
  )+                       
  labs(
    # thêm tên bảng và tên các trục
    title = "Số ca Covid theo ngày",
    x = "Ngày",
    y = "Số ca"
  )+                                 
  theme_minimal() # chỉnh thêm màu sắc, font size

ggplot

VD: Vẽ số ca theo thời gian từ dữ liệu covid

Chọn màu cho ggplot

Ngoài các màu cơ bản, ta có thể quy định màu trong R dưới dạng hex color

VD

ggplot(data = my_data)+                   
  geom_point(                             
    aes(x = col1, y = col2),    
    color = "#03d3fc")

Để có được hex color, ta có thể google “hex color”, chọn màu phù hợp và copy HEX

Một số geom_ thông dụng

Lệnh geom_ Công dụng Các tuỳ chỉnh
geom_histogram() Tạo biểu đồ cột với x là biến liên tục động được chia thành các khoảng (bin) còn y là số quan sát trong mỗi khoảng

Dữ liệu cần cung cấp: Giá trị cho cột x (y là số quan sát, sẽ tự động được tính)
Các tham số khác:

  • bin (số nhóm để chia trục x) hoặc binwidth (độ rộng cho mỗi nhóm của trục x)
geom_col() Tạo biểu đồ cột từ x và y Dữ liệu cần cung cấp: Giá trị cho cột xy
geom_bar() Tương tự như geom_col() nhưng y sẽ tự động được tính theo tham số stat

Dữ liệu cần cung cấp: Giá trị cho cột x (y sẽ tự động được tính theo tham số stat)
Các tham số khác:

  • stat cách tính y (mặc định stat = "count" nghĩa là lấy số quan sát làm giá trị y)
geom_boxplot() Tạo boxplot từ x và y (nếu 1 trục là biến liên trục, trục còn lại phải thuộc biến phân loại) Dữ liệu cần cung cấp: Giá trị cho cột xy
geom_point() Tạo biểu đồ phân tán từ x và y Dữ liệu cần cung cấp: Giá trị cho cột xy
geom_line() Tạo biểu đồ đường từ x và y Dữ liệu cần cung cấp: Giá trị cho cột xy
geom_smooth() Tạo biểu đồ đường hồi quy cùng khoảng tin cậy (confidence interval) từ x và y

Dữ liệu cần cung cấp: Giá trị cho cột xyCác tham số khác:

  • method quy định mô hình hồi quy ("auto", "lm", "glm", "gam", "loess")

Histograms - geom_histogram()

ggplot(
  data = case_data, aes(x = date_onset) ) +                   
  geom_histogram(
    fill = "cornflowerblue"
  ) +                       
  labs(
    # thêm tên bảng và tên các trục
    title = "Số ca Covid theo ngày",
    x = "Ngày",
    y = "Số ca"
  )

Biểu đồ cột - geom_bar() hoặc geom_col()

ggplot(
    data = case_data, 
    aes(x = date_onset, y = no_cases)
  ) +                   
  geom_col(
    color = "cornflowerblue"    
  )+                       
  labs(
    title = "Số ca Covid theo ngày",
    x = "Ngày",
    y = "Số ca"
  )

Box plots - geom_boxplot()

# --- Tính số ca mỗi ngày trong từng đợt dịch ---- 
linelist %>% 
  group_by(outbreak, date_onset) %>% 
  summarize(
    no_cases = n()
  ) %>% 
# --- Vẽ boxplot so sánh số ca mỗi ngày trong 2 đợt dịch ---- 
  ggplot(
    aes(x = outbreak, y = no_cases) ) +                   
    geom_boxplot() +                       
    labs(
      x = "Đợt dịch",
      y = "Số ca mỗi ngày"
    )

Biểu đồ phân tán- geom_point()

ggplot(
  data = case_data, aes(x = date_onset, y = no_cases) ) +                   
  geom_point(
    color = "cornflowerblue"
  ) +                       
  labs(
    # thêm tên bảng và tên các trục
    title = "Số ca Covid theo ngày",
    x = "Ngày",
    y = "Số ca"
  )

Biểu đồ đường - geom_line()

ggplot(
  data = case_data, aes(x = date_onset, y = no_cases) ) +                   
  geom_line(
    color = "cornflowerblue"
  ) +                       
  labs(
    # thêm tên bảng và tên các trục
    title = "Số ca Covid theo ngày",
    x = "Ngày",
    y = "Số ca"
  )

Đường xu hướng - geom_smooth()

ggplot(
  data = case_data, aes(x = date_onset, y = no_cases) ) +                   
  geom_smooth(
    color = "cornflowerblue"
  ) +                       
  labs(
    # thêm tên bảng và tên các trục
    title = "Số ca Covid theo ngày",
    x = "Ngày",
    y = "Số ca"
  )

geom_area()

ggplot(
  data = case_data, aes(x = date_onset, y = no_cases) ) +                   
  geom_area(
    fill = "cornflowerblue", color = "black"
  ) +                       
  labs(
    # thêm tên bảng và tên các trục
    title = "Số ca Covid theo ngày",
    x = "Ngày",
    y = "Số ca"
  )

Stat

Tham số stat của hàm geom

  • "identity" lấy dữ liệu thô

  • "count" đếm số quan sát (số hàng) trong từng phân nhóm

  • "sum" tính tổng của các hàng trong từng phân nhóm

VD: vẽ biểu đồ số ca trực tiếp từ data linelist thay vì case_data bằng các quy định stat = "count"

ggplot(
    data = linelist,
    aes(x = date_onset) # không cần quy định y ở đây
  ) +                   
  geom_bar(
    # ggplot sẽ tự tính y bằng các đếm số hàng nhóm theo mỗi giá trị của x (đếm số ca theo date_onset)
    stat = "count",
    color = "cornflowerblue"
  ) +                       
  labs(title = "Số ca Covid theo ngày",
    x = "Ngày", y = "Số ca"
  ) 

Stat

VD: vẽ biểu đồ số ca trực tiếp từ data linelist thay vì case_data bằng các quy định stat = "count"

Điều chỉnh các đặc điểm của biểu đồ

Một số đặc điểm quan trọng cho các lớp geom_

Tên đặc điểm Công dụng
fill quy định fill color cho đồ thị
color quy định border color cho đồ thị
size quy định kích cỡ của đường/điểm cho đồ thị
alpha quy định độ trong suốt của màu (từ 0-1, càng gần 0 màu càng trong suốt)
linetype quy định loại đường (thẳng, đứt, …) cho các đồ thị đường
shape quy định loại điểm cho các đồ thị có điểm

Ta có thể quy định giá trị cho các đặc điểm của biểu đồ như một tham số của lệnh geom_ (VD: geom_bar(color = "cornflowerblue") trong các ví dụ trên)

Tuy nhiên, nếu muốn thay đổi các đặc điểm dựa theo biến từ dữ liệu, ta cần quy định mối liên hệ giữa biến và đặc điểm trong lệnh aes()

Điều chỉnh các đặc điểm của biểu đồ

Lệnh aes() được sử dụng để quy định mối quan hệ giữa các biến và các đặc điểm của biểu đồ

VD: quy định màu của cột theo đợt dịch

linelist %>% 
  mutate(
    # ----- Đổi label cho từng đợt dịch ------
    outbreak = factor(outbreak, 
                      levels = c("1st outbreak", "2nd outbreak"), 
                      labels = c("Đợt dịch 1", "Đợt dịch 2"))
  ) %>% 
  ggplot(
    aes(x = date_onset)
  ) +                   
  geom_bar(
    aes(fill = outbreak) # quy định mỗi đợt dịch có màu khác nhau
  ) +                       
  labs(title = "Số ca Covid theo ngày",
    x = "Ngày", y = "Số ca", 
    # optional: đổi tên ô chú thích
    fill = "Đợt dịch"
  ) 

Điều chỉnh các đặc điểm của biểu đồ

Lệnh aes() được sử dụng để quy định mối quan hệ giữa các biến và các đặc điểm của biểu đồ

VD: quy định màu của cột theo đợt dịch

Quy định màu

Để tự quy định màu cho biểu đồ, ta có thể sử dụng các lệnh scale_color_*() khi muốn quy định màu viền, hoặc scale_fill_*() quy định màu fill

Function Công dụng Tham số

scale_color_manual()/

scale_fill_manual()

Quy định màu cho từng giá trị biến phân loại

breaks các giá trị trong biến phân loại

values màu tương ứng

labels chú thích cho màu

scale_color_continuous()/

scale_fill_continuous()

Quy định màu cho biến liên tục

low màu cho giá trị thấp nhất

high màu cho giá trị cao nhất

scale_color_binned()/

scale_fill_binned()

Tạo thang màu bằng cách chia giá trị biến liên tục thành các nhóm (bins).

low màu cho giá trị thấp nhất

high màu cho giá trị cao nhất

n.breaks số bin để chia

Quy định màu

VD: quy định màu và label cho từng đợt dịch bằng lệnh scale_fill_manual()

linelist %>% 
  ggplot(aes(x = date_onset)) +                   
  geom_bar(aes(fill = outbreak) ) +   
  # ------ Quy định màu cho từng đợt dịch ------
  scale_fill_manual(
    breaks = c("1st outbreak", "2nd outbreak"),
    values = c("#91deed", "#67b6e0"),
    labels = c("Đợt dịch 1", "Đợt dịch 2")
  ) +
  labs(title = "Số ca Covid theo ngày", x = "Ngày", y = "Số ca", fill = "Đợt dịch") 

Quy định màu

VD: quy định màu và label cho từng đợt dịch bằng lệnh scale_fill_manual()

Facet

Facets, hay “chia nhỏ biểu đồ”, được sử dụng để chia một biểu đồ thành nhiều phần nhỏ, với mỗi phần (“facet”) đại diện cho một nhóm của dữ liệu.

Trong ggplot, có 2 loại facet chính

  • facet_wrap() hiện thị các biểu đồ khác nhau cho từng nhóm của một biến số. (VD: thể hiện các đường cong dịch bệnh khác nhau cho từng khu vực).

  • facet_grid() áp dụng khi muốn đưa một biến thứ hai vào sắp xếp các biểu đồ con. Ở đây mỗi ô thể hiện sự giao nhau của các giá trị giữa hai cột.

Facet

facet_wrap facet_grid
biểu đồ số ca bệnh sốt rét chia theo tỉnh (tên tỉnh ở phía trên mỗi biểu đồ) biểu đồ số ca bệnh sốt rét chia theo tỉnh nhóm tuổi

Facet

VD: dùng facet_grid để vẽ biểu đồ theo giới tính và đợt dịch

linelist %>% 
  mutate(
    # optional: đổi label cho biến đợt dịch và giới tính
    outbreak = factor(outbreak, 
                      levels = c("1st outbreak", "2nd outbreak"), 
                      labels = c("Đợt dịch 1", "Đợt dịch 2")),
    sex = factor(sex,
                 levels = c("f", "m"),
                 labels = c("nữ", "nam"))
  ) %>% 
  ggplot(
    aes(x = date_onset)
  ) +                   
  geom_bar(
    color = "cornflowerblue"
  ) +                       
  labs(title = "Số ca Covid theo ngày",
    x = "Ngày", y = "Số ca"
  ) +
  facet_grid(
    cols = vars(outbreak),
    rows = vars(sex)
    )

Facet

VD: dùng facet_grid để vẽ biểu đồ theo giới tính và đợt dịch

Lưu biểu đồ

Để lưu biểu đồ, ta sử dụng lệnh ggsave() với các tham số sau

  • path đường dẫn đến nơi lưu bản đồ

  • filename tên file được lưu

  • plot biểu đồ được lưu (tự động lưu biểu đồ được vẽ gần nhất)

  • width, height kích cỡ của ảnh được lưu

  • dpi độ phân giải của ảnh được lưu

VD: lưu biểu đồ

ggsave(
  path = file.path(getwd(), "images"), # lưu tại folder images
  filename = "latest_plot.png", # lưu với tên latest_plot.png
  width = 3000, height = 2100, # chỉnh size ảnh
  units = "px", # quy định kích cỡ size theo pixel
  create.dir = TRUE, # tạo folder nếu folder không tồn tại
  dpi = 400)

Plotly

Plotly

Package plotly cung cấp lệnh ggplotly giúp người dùng nhanh chóng biến biểu đồ của ggplot thành biểu đồ tương tác (interactive plot).

VD: dùng ggplotly để biến biểu đồ số ca thành dạng tương tác

# Vẽ biểu đồ bằng ggplot 
case_plot <- linelist %>% 
  mutate(
    # ----- Đổi label cho từng đợt dịch ------
    outbreak = factor(outbreak, 
                      levels = c("1st outbreak", "2nd outbreak"), 
                      labels = c("Đợt dịch 1", "Đợt dịch 2"))
  ) %>% 
  group_by(outbreak, date_onset) %>% 
  summarize(no_cases = n()) %>% 
  ggplot(aes(x = date_onset, y = no_cases)) +                   
  geom_col( aes(fill = outbreak) ) + 
  scale_x_date() +
  labs(title = "Số ca Covid theo ngày", x = "Ngày", y = "Số ca", fill = "Đợt dịch") 

# Dùng lệnh ggplotly để biến thành biểu đồ tương tác
ggplotly(case_plot)

Important

Khi knit file .Rmd sang các định dạng file khác, tính tương tác của đồ thị chỉ còn khi người dùng knit thành file .html

Plotly

Điều chỉnh giá trị cho tooltip

Để thay đổi giá trị hiển trị trên tooltip, ta có thể tạo một đặc điểm qua lệnh aes() và quy định giá trị cho tham số tooltip khi sử dụng plotly

example_plot <- linelist %>% 
  mutate(
    # ----- Đổi label cho từng đợt dịch ------
    outbreak = factor(outbreak, 
                      levels = c("1st outbreak", "2nd outbreak"), 
                      labels = c("Đợt dịch 1", "Đợt dịch 2"))
  ) %>% 
  group_by(outbreak, date_onset) %>% 
  summarize(no_cases = n()) %>% 
  ggplot(
    aes(x = date_onset, y = no_cases,
        # ------- Tạo đặc điểm mới cho tooltip, đặt tên là text ------
        text = paste0(
          "Ngày: ", date_onset,
          "\nSố ca: ",no_cases,
          "\nĐợt dịch: ", outbreak
        ))
    ) +                   
  geom_col( aes(fill = outbreak) ) + 
  scale_x_date() +
  labs(title = "Số ca Covid theo ngày", x = "Ngày", y = "Số ca", fill = "Đợt dịch") 
  
ggplotly(
  example_plot,
  # quy định lấy đặc điểm text làm tooltip
  tooltip = "text"
)

Điều chỉnh giá trị cho tooltip

Để thay đổi giá trị hiển trị trên tooltip, ta có thể tạo một đặc điểm qua lệnh aes() và quy định giá trị cho tham số tooltip khi sử dụng plotly

Vẽ bản đồ

Dữ liệu bản đồ

Trước tiên, ta cần dữ liệu để vẽ biểu đồ trong R. Trong khoá học này ta sẽ sử dụng dữ liệu được cấp bởi GADM.

Để download dữ liệu biểu đồ Việt Nam, thực hiện các bước sau:

Lưu ý

  • Các nguồn bản đồ mở thường sẽ không có các phần lãnh thổ tranh chấp (VD: Hoàng Sa, Trường Sa)

  • Các thay đổi về bản đồ hành chính có thể không được cập nhật kịp thời (VD: các quận được sáp nhập thành TP. Thủ Đức)

Dữ liệu bản đồ

Dữ liệu bản đồ tải về sẽ có 3 cấp bậc

  • Cấp tỉnh/ thành phố

  • Cấp quận/ huyện

  • Cấp phường/ xã

Để đọc dữ liệu bản đồ, dùng lệnh st_read của package sf

Để vẽ dữ liệu đọc được, dùng lệnh geom_sf của ggplot

# Đọc dữ liệu bản đồ
map_path <- "../data"
vn_tinh <- st_read(dsn = file.path(map_path, "gadm41_VNM.gpkg"), layer = "ADM_ADM_1")
vn_qh <- st_read(dsn = file.path(map_path, "gadm41_VNM.gpkg"), layer = "ADM_ADM_2")
vn_px <- st_read(dsn = file.path(map_path, "gadm41_VNM.gpkg"), layer = "ADM_ADM_3")

Dữ liệu bản đồ

# Vẽ dữ liệu đọc được
ggplot(vn_tinh) + geom_sf()

Dữ liệu bản đồ

# Vẽ dữ liệu đọc được
ggplot(vn_qh) + geom_sf()

Dữ liệu bản đồ

# Vẽ dữ liệu đọc được
ggplot(vn_px) + geom_sf()

Điều chỉnh dữ liệu bản đồ

Khi thử nhìn dữ liệu cho bản đồ, ta có thể thấy dữ liệu đọc được cũng là một bảng dữ liệu và nhiều lệnh điều chỉnh bảng dữ liệu cũng có thể áp dụng với bảng này

Một số cột đáng lưu ý bao gồm

  • Các cột có format NAME_`cấp bậc hành chính` chứa tên tiếng Việt

  • Các cột có format VARNAME_`cấp bậc hành chính` chứa tên tiếng Anh

  • Cột geom sẽ chứa dữ liệu để vẽ nên bản đồ

head(vn_qh)

Điều chỉnh dữ liệu bản đồ

Để lựa chọn các khu vực được vẽ trên bản đồ, ta có thể sử dụng lệnh filter() tương tự như các bảng dữ liệu khác

VD: chỉ vẽ bản đồ các quận của phố Hồ Chí Minh

hcm_map <- vn_qh %>% filter( NAME_1 == "Hồ Chí Minh" ) 
hcm_map %>% ggplot() + geom_sf()

Điều chỉnh dữ liệu bản đồ

Để nối một số khu vực được vẽ trên bản đồ, ta có thể sử dụng lệnh st_join() của gói sf

VD: gộp quận 2, quận 9 và quận Thủ Đức thành Thành phố Thủ Đức

# quy định các quận được gộp
thuduc_subset <- (hcm_map$VARNAME_2 == "District 2" | hcm_map$VARNAME_2 == "District 9" | 
             hcm_map$VARNAME_2 == "Thu Duc" )

# gộp các quận 2, 9, Thủ Đức và lưu giá trị vào cột geom của Thủ Đức trong bảng
hcm_map[hcm_map$VARNAME_2 == "Thu Duc", "geom"] <- st_union( subset(hcm_map, thuduc_subset)$geom )

# xoá dữ liệu cho Q2 và Q9 sau khi gộp
hcm_map <- hcm_map %>% filter(!VARNAME_2 == "District 2" & !VARNAME_2 == "District 9")

# vẽ biểu đồ sau khi gộp
hcm_map %>% ggplot() + geom_sf()

Điều chỉnh dữ liệu bản đồ

Để nối một số khu vực được vẽ trên bản đồ, ta có thể sử dụng lệnh st_join() của gói sf

VD: gộp quận 2, quận 9 và quận Thủ Đức thành Thành phố Thủ Đức

Điều chỉnh dữ liệu bản đồ

Để thêm giá trị từ một bảng khác vào bảng dữ liệu biểu đồ, ta có thể sử dụng left_join() tương tự các bài học trước

VD: vẽ bản đồ cùng số ca

# ------- Tính tổng số ca ------- 
case_by_district <- linelist %>% 
  group_by(district) %>% 
  summarize(
    no_cases = n()
  )

# -------- Nối bảng số ca và bảng dữ liệu bản đồ ----- 
hcm_map <- hcm_map %>% 
  mutate(
    # Trước khi join, phải điều chỉnh cột dùng để join để các giá trị tương ứng giống nhau
    VARNAME_2 = str_replace(VARNAME_2, "District", "Quan")
  ) %>% 
  left_join(
    case_by_district,
    by = join_by(VARNAME_2 == district)
  )

# --------- Vẽ biểu đồ cùng dữ liệu số ca bằng ggplot ------
hcm_map %>%
  ggplot() +
    geom_sf(
      # quy định màu theo số ca
      aes(fill = no_cases)
    ) +
  labs(fill = "Số ca")

Điều chỉnh dữ liệu bản đồ

Để thêm giá trị từ một bảng khác vào bảng dữ liệu biểu đồ, ta có thể sử dụng left_join() tương tự các bài học trước

VD: vẽ bản đồ cùng số ca

Leaflet

Gói leaflet được dùng để vẽ bản đồ tương tác.
Chúng ta sử dụng leaflet thay vì ggplot + plotly vì bản đồ vẽ bởi leaflet thường đẹp hơn

Quá trình vẽ biểu đồ bằng leaflet cũng tương tự như ggplot, bao gồm các bước sau:

  • Gọi leaflet() và cung cấp dữ liệu cần vẽ

  • Thêm lớp biểu đồ qua lệnh addPolygon()

  • Thêm ô chú thích bằng lệnh addLegend() nếu cần thiết

Leaflet

Tên lệnh Các tham số thông dụng
addPolygon()

color - màu cho đường viền

fillColor - màu cho các khu vực

fillOpacity - độ trong suốt của màu (từ 0-1, càng gần 0 màu càng trong suốt)

label - giá trị được hiển thị (tương tự tooltip của plotly)

addLegend()

pal - palette màu

values - cung cấp giá trị cần chú thích

title - tiêu đề cho ô chú thích

opacity - độ trong suốt của màu (cần giống fillOpacity trong addPolygon() để hiển thị màu chính xác)

position - vị trí của ô chú thích

Leaflet

# ----- quy định bảng màu ----
pal <- colorBin(
  # bảng màu cho giá trị từ nhỏ đến lớn 
  palette = c("#cadefa","#73aeff", "#134c9c"), 
  domain = hcm_map$no_cases, # cung cấp khoảng giá trị 
  na.color = "grey" # quy định màu cho giá trị trống
  )

# ----- vẽ biểu đồ ------
leaflet(hcm_map) %>% 
  # vẽ biểu đồ
  addPolygons(
    color="black", # quy định màu viền
    # quy định fillColor dựa theo số ca, và dùng màu từ bảng màu pal
    fillColor = ~pal(no_cases),  
    weight = 1, # độ dày của viền
    # chỉnh tooltip
    label = ~paste0(NAME_2, ". \nSố ca: ", no_cases), 
    fillOpacity=0.8
  ) %>% 
  # thêm ô chú thích
  addLegend(pal = pal, values = ~no_cases, title = "Số ca", position = "bottomright", opacity=1)

Leaflet

Leaflet

Các lệnh tạo bảng màu trong leaflet

Function Công dụng
colorNumeric() tạo palette màu liên tục từ biến liên tục
colorBin() tạo palette màu phân loại từ biến liên tục
colorFactor() tạo palette màu phân loại từ biến phân loại