Kỹ thuật trực quan hóa dữ liệu có mật độ cao

Biên soạn

ThS. Nguyễn Tấn Đức | www.tuhocr.com

Cập nhật

2024 June 06

Register Source Web

1 Tình huống thường gặp

Bạn có dataset gồm hai biến có mật độ tương quan với nhau rất cao (các điểm dữ liệu rất gần nhau và dày đặc) vì vậy khi plot theo kiểu thông thường ta không phân biệt được là có bao nhiêu điểm dữ liệu vì các điểm này trùng lắp lên nhau. Vì vậy để đồ thị thể hiện được có bao nhiêu điểm dữ liệu trong cùng một vị trí tọa độ thì ta sẽ áp dụng dạng biểu đồ sunflower với các cánh hoa đại diện cho 1 điểm dữ liệu hoặc biểu đồ hexbin (bản chất là heatmap).

Dataset minh họa là Galton nằm trong package HistData về thông số chiều cao giữa cha mẹ parent và con cái child, ta thấy dữ liệu của hai biến chiều cao này rất dày đặc với nhau thông qua lệnh table().

library(HistData)
data(Galton)
dim(Galton)
[1] 928   2
summary(Galton)
     parent          child      
 Min.   :64.00   Min.   :61.70  
 1st Qu.:67.50   1st Qu.:66.20  
 Median :68.50   Median :68.20  
 Mean   :68.31   Mean   :68.09  
 3rd Qu.:69.50   3rd Qu.:70.20  
 Max.   :73.00   Max.   :73.70  
table(Galton$parent, Galton$child)
      
       61.7 62.2 63.2 64.2 65.2 66.2 67.2 68.2 69.2 70.2 71.2 72.2 73.2 73.7
  64      1    0    2    4    1    2    2    1    1    0    0    0    0    0
  64.5    1    1    4    4    1    5    5    0    2    0    0    0    0    0
  65.5    1    0    9    5    7   11   11    7    7    5    2    1    0    0
  66.5    0    3    3    5    2   17   17   14   13    4    0    0    0    0
  67.5    0    3    5   14   15   36   38   28   38   19   11    4    0    0
  68.5    1    0    7   11   16   25   31   34   48   21   18    4    3    0
  69.5    0    0    1   16    4   17   27   20   33   25   20   11    4    5
  70.5    1    0    1    0    1    1    3   12   18   14    7    4    3    3
  71.5    0    0    0    0    1    3    4    3    5   10    4    9    2    2
  72.5    0    0    0    0    0    0    0    1    2    1    2    7    2    4
  73      0    0    0    0    0    0    0    0    0    0    0    1    3    0
head(Galton, n = 30)
   parent child
1    70.5  61.7
2    68.5  61.7
3    65.5  61.7
4    64.5  61.7
5    64.0  61.7
6    67.5  62.2
7    67.5  62.2
8    67.5  62.2
9    66.5  62.2
10   66.5  62.2
11   66.5  62.2
12   64.5  62.2
13   70.5  63.2
14   69.5  63.2
15   68.5  63.2
16   68.5  63.2
17   68.5  63.2
18   68.5  63.2
19   68.5  63.2
20   68.5  63.2
21   68.5  63.2
22   67.5  63.2
23   67.5  63.2
24   67.5  63.2
25   67.5  63.2
26   67.5  63.2
27   66.5  63.2
28   66.5  63.2
29   66.5  63.2
30   65.5  63.2

2 Các dạng đồ thị thường áp dụng

2.1 Đồ thị scatter plot

Khi plot theo kiểu thông thường ta thấy các điểm data point rất ít, bởi vì hầu như các điểm này có giá trị rất sát nhau nên đều trùng lắp lên nhau \(\Rightarrow\) dẫn đến cảm giác là bộ dataset này có rất ít điểm dữ liệu.

plot(x = Galton$parent,
     y = Galton$child,
     main = paste0("Đồ thị scatter plot với rất nhiều điểm có cùng tọa độ (n = ",
                   dim(Galton)[1], ")"),
     xlab = "Biến X",
     ylab = "Biến Y")

Khi cho xlimylim xuất phát từ gốc tọa độ để bao quát dữ liệu, ta thấy các điểm dữ liệu tập trung ở khu vực trong khoảng 64 đến 73 ở trục hoành và trong khoảng 61.7 đến 73.7.

plot(x = Galton$parent,
     y = Galton$child,
     main = paste0("Đồ thị scatter plot với xlim và ylim tính từ gốc tọa độ (n = ",
                   dim(Galton)[1], ")"),
     xlab = "Biến X",
     ylab = "Biến Y",
     xlim = c(0, 100),
     ylim = c(0, 100))

abline(v = c(min(Galton$parent), max(Galton$parent)),
       lty = 2,
       col = "red")
abline(h = c(min(Galton$child), max(Galton$child)),
       lty = 2,
       col = "red")

2.2 Đồ thị sunflower

Trong tình huống này ta dùng đồ thị sunflower, vốn bản chất là một cải tiến của đồ thị scatter plot khi thể hiện thêm các đường gạch nhỏ (đại diện cho 1 datapoint) ngay ở vị trí các data point trùng nhau, khi đó ta nhìn vào các điểm nào có nhiều gạch (tương tự như cánh hoa hướng dương) thì xác định được khu vực đó mật độ điểm dữ liệu rất cao.

sunflowerplot(x = Galton$parent,
              y = Galton$child,
              main = paste0("Đồ thị sunflower với mỗi đoạn thẳng là 1 điểm dữ liệu (n = ",
                   dim(Galton)[1], ")"),
              xlab = "Biến X",
              ylab = "Biến Y",
              col = "blue",
              seg.col = "darkgreen")

Nếu bạn muốn vẽ một đường clustering bao xung quanh cụm dữ liệu có mật độ cao thì sử dụng function dataEllipse. Có nhiều loại đường ellipse, bạn tham khảo trong trang help package car nhé.

dataEllipse() superimposes the normal-probability contours over a scatterplot of the data.

library(car)

sunflowerplot(x = Galton$parent,
              y = Galton$child,
              main = paste0("Đồ thị sunflower bao gồm đường clustering (n = ",
                   dim(Galton)[1], ")"),
              xlab = "Biến X",
              ylab = "Biến Y",
              col = "blue",
              xlim = c(60, 75),
              ylim = c(60, 75),
              seg.col = "darkgreen")


car::dataEllipse(x = Galton$parent,
                 y = Galton$child,
                 plot.points = FALSE,
                 col = "red",
                 lty = 2)

2.3 Đồ thị hexbin

Đây là đồ thị biểu diễn điểm dữ liệu ở dạng hình lục giác, với thang màu thay đổi tùy theo mật độ điểm dữ liệu.

2.3.1 Hexbin vẽ theo kiểu base R

library(hexbin)
library(RColorBrewer)

## chuyển dataset về dạng bins

bins_data <- hexbin::hexbin(x = c(Galton$parent),
                            y = c(Galton$child),
                            xbnds = c(60, 75),
                            ybnds = c(60, 75),
                            xbins = 20,
                            shape = 1)

## gọi lệnh này qua S4 method
# hexbin::plot(x = bins_data)

## nên gọi trực tiếp lệnh này
hexbin::gplot.hexbin(x = bins_data,
                     main = paste0("Đồ thị hexbin (n = ",    
                                   dim(Galton)[1], ")"),
                     xlab = "Biến X",
                     ylab = "Biến Y",
                     border = "blue",
                     legend = 1,
                     colramp = colorRampPalette(c("lightgreen", "yellow", "red")))

Để thay đổi title của legend thì ta hack một chút ở code gốc

source("gplot_hexbin.R")
library(grid)

gplot_hexbin(x = bins_data,
             main = paste0("Đồ thị hexbin (n = ",    
                           dim(Galton)[1], ")"),
             xlab = "Biến X",
             ylab = "Biến Y",
             border = "blue",
             legend = 1,
             title_legend = "Counts",
             colramp = colorRampPalette(c("lightgreen", "yellow", "red")))

Hexagon Bin Smoothing

https://cran.r-project.org/web/packages/hexbin/vignettes/hexagon_binning.pdf

source("gplot_hexbin.R")
library(grid)

smooth_data <- smooth.hexbin(bins_data)

gplot_hexbin(x = smooth_data,
             main = "Hexagon Bin Smoothing",
             xlab = "Biến X",
             ylab = "Biến Y",
             # border = "blue",
             legend = 1,
             title_legend = "Counts",
             colramp = colorRampPalette(BTY(n = 50))
             # colramp = colorRampPalette(terrain.colors(n = 50))
             )

2.3.2 Hexbin vẽ theo kiểu lattice

# tuy nhiên hexbin xây dựng trên nền grid graphics nên ta vẽ trực tiếp
# nền grid graphics sẽ thuận tiện tinh chỉnh đồ thị
library(lattice)
hexbin::hexbinplot(x = child ~ parent,
                   data = Galton,
                   xlim = c(60, 75),
                   ylim = c(60, 75),
                   main = paste0("Đồ thị hexbin trên nền package lattice (n = ",
                                   dim(Galton)[1], ")"),
                     xlab = "Biến X",
                     ylab = "Biến Y",
                   colramp = colorRampPalette(rev(brewer.pal(20,"Spectral")))
                   )

2.3.3 Hexbin vẽ theo kiểu ggplot2

library(ggplot2)

ggplot(data = Galton,
       mapping = aes(x = parent, y = child)) + geom_hex() +
  labs(title = paste0("Đồ thị hexbin trên nền package ggplot2 (n = ",
                                   dim(Galton)[1], ")")) +
  scale_x_continuous(name = "Biến X",
                     limits = c(60, 75)) + 
  scale_x_continuous(name = "Biến Y",
                     limits = c(60, 75)) + 
  theme_classic()

3 Tài liệu tham khảo

  1. https://subscription.packtpub.com/book/data/9781783989508/7/ch07lvl1sec69/constructing-a-sunflower-plot

  2. https://search.r-project.org/CRAN/refmans/car/html/Ellipses.html

  3. https://stackoverflow.com/questions/43120327/how-to-call-a-function-that-contains-a-comma-in-r

  4. https://www.biostars.org/p/291845/

  5. https://www.geeksforgeeks.org/hexbin-plot-using-hexbin-packages-in-r/

  6. https://datavizproject.com/data-type/hexagonal-binning

  7. https://stat.ethz.ch/pipermail/r-help/2014-November/423554.html