Hands-on R programming

Tổng quan về phân tích dữ liệu

Tổng quan

Sự ra đời của các công nghệ thông lượng cao, như RNA-SeqChIP-Seq, đã dẫn đến một lượng lớn dữ liệu trong khoa học đời sống. Do đó, các kỹ năng tính toán và thống kê đang trở thành một phần quan trọng trong chương trình giảng dạy của các nhà khoa học đời sống.

Chúng ta đang bước vào kỷ nguyên số với nhu cầu lưu trữ và khai thác các nguồn dữ liệu (Big Data) ngày một lớn. Trở thành một nhà phân tích dữ liệu hoặc đảm nhiệm các vị trí liên quan đến lĩnh vực phân tích dữ liệu là công việc có ý nghĩa quan trọng với bất kì tổ chức, doanh nghiệp nào. Cùng với IoT (Internet of Things), AI (Artificial Intelligence – Trí tuệ nhân tạo), Blockchain (Chuỗi khối), Big Data (Dữ liệu lớn) là một trong bốn nền tảng quan trọng của cuộc cách mạng công nghệ 4.0. Big Data được hiểu là những dữ liệu khổng lồ, là nguồn tài sản thông tin có dung lượng lớn và đa dạng, có vận tốc cao, đòi hỏi các hình thức xử lý thông tin có hiệu quả về chi phí, để nâng cao việc đưa ra quyết định và tối ưu hóa quy trình. Nói cách khác, Big Data là một tệp dữ liệu khổng lồ không thể phân tích được bằng các công cụ và phần mềm thông thường.

Phân tích dữ liệu là gì?

Phân tích dữ liệu là một quá trình kiểm tra, làm sạch, chuyển đổi và mô hình hóa dữ liệu với mục tiêu khám phá thông tin hữu ích, thông báo kết luận và hỗ trợ ra quyết định. Phân tích dữ liệu có nhiều khía cạnh và cách tiếp cận, bao gồm các kỹ thuật đa dạng dưới nhiều tên khác nhau và được sử dụng trong các lĩnh vực kinh doanh, khoa học và khoa học xã hội khác nhau.

Phân tích dữ liệu (Data Analytics) là một chuyên ngành trong Công nghệ thông tin. Công việc tập trung vào việc thu thập, khai thác, quản lý và xử lý bộ dữ liệu – các Big Data, từ đó đưa ra các nhận định, dự đoán xu hướng hoạt động của tương lai. Phân tích dữ liệu có thể bao gồm phân tích dữ liệu thăm dò, phân tích dữ liệu xác nhận, phân tích dữ liệu định lượng và phân tích dữ liệu định tính (tập trung vào các dữ liệu như video, hình ảnh và văn bản).


Đây là một công việc có ý nghĩa và có tầm quan trọng lớn đối với bất cứ tổ chức hoặc doanh nghiệp nào. Đặc biệt là các lĩnh vực như ngân hàng đầu tư, bảo hiểm, du lịch, quốc phòng, hàng không vũ trụ và y học – nơi các phần mềm đóng vai trò quan trọng.

Phân tích dữ liệu trong tương lai

Hệ thống phân tích dữ liệu tự động đang được đưa vào sử dụng trong nhiều công ty. Tuy nhiên, nó vẫn chưa thể đáp ứng hoàn toàn nhu cầu của người sử dụng. Theo các nghiên cứu, 80% lượng công việc không thể tự động hóa; 20% còn lại có thể thực hiện bằng máy nhưng hiệu quả chưa cao. Hơn nữa, máy học tự động chỉ có thể giải quyết được những vấn đề đơn giản. Các vấn đề phức tạp hơn cần đến tư duy của con người mới có thể giải quyết được. Do đó, ngành Phân tích dữ liệu sẽ không biến mất ngay cả khi công nghệ phát triển.

Theo Diễn đàn Kinh tế thế giới (WEF), nhu cầu tuyển dụng nhân sự ngành Phân tích dữ liệu đã tăng mạnh trong năm 2020, gấp 6 lần so với 5 năm trước. Trong 5 năm tới, tỉ lệ này sẽ tiếp tục tăng cao hơn nữa do lượng dữ liệu con người tạo ra ngày càng nhiều. Nhờ đó mà cơ hội việc làm và phát triển sự nghiệp của những người theo đuổi ngành Phân tích dữ liệu cũng vô cùng rộng mở. 

Theo Glassdoor (một website về việc làm của Mỹ), mức lương trung bình của 1 nhà phân tích dữ liệu rơi vào khoảng 84.000 USD/ năm. Tại Việt Nam, con số này cũng lên tới trên 470 triệu/ năm theo thống kê của TopDev. Mức thu nhập này cao hơn mức thu nhập trung bình, điều này khiến cho nghề phân tích dữ liệu trở thành một ngành nghề sinh lời cao và cực hấp dẫn, được bầu chọn là ngành nghề “quyến rũ” nhất thế kỷ. Jeanne Harris – Giám đốc điều hành cấp cao tại Accenture Institute for High Performance (AIHP) cũng đã từng nhấn mạnh tầm quan trọng của các chuyên gia phân tích khi khẳng định “dữ liệu sẽ trở nên vô dụng nếu thiếu người có kỹ năng để phân tích nó”.

Sinh viên ICTU sẵn sàng cho những trải nghiệm mới

Các công cụ liên quan

Tài liệu và công cụ

Trong module này sử dụng một số tài liệu và khái niệm từ cuốn sách R dành cho Khoa học Dữ liệu của Garret Grolemund và Hadley Wickham. Đây là một cuốn sách tuyệt vời dành cho những ai muốn khám phá và bắt đầu với hành trình về khoa học dữ liệu.

R for Data Science by Garret Grolemund and Hadley Wickham

Cài đặt R và RStudio

Để có thể thực hành module này, yêu cầu người dùng cài đặt R và RStudio trên máy tính, bài viết cụ thể hướng dẫn, các bạn có thể tham khảo theo đường link sau: Cài đặt R và RStudio.

Thư viện xử lý dữ liệu

Trong nội dung module này, chúng ta sẽ thực hành với thư viện tidyverseggviz , đây là hệ sinh thái cho phép thao tác và xử lý dữ liệu, trực quan dữ liệu ở mức cơ bản.

Tidyverse là hệ sinh thái lớn chứa nhiều package nhỏ bên trong và các mảnh nhỏ này kết nối với nhau một cách tiện ích để vận hành một quy trình phân tích dữ liệu hoàn chỉnh, từ khâu nhập dữ liệu vào R (readr,readxl,haven), định hình (tibble), hoán chuyển, chọn lọc và dọn dẹp (tidyr, dplyr,purr) đến bước trình bày và thăm dò dữ liệu bằng đồ họa (ggplot2). Package tidyverse được tạo ra vào năm 2016 bởi Hadley Wickham, một nhân vật quan trọng đã góp phần cho sự thành công của R, cũng là tác giả của những package nổi tiếng như reshape, dplyr, tidyr và ggplot2.

Tidyverse - Ecosystem

Mô hình phân tích dữ liệu

Một số khái niệm cơ bản

Trong hầu hết các dự án phân tích dữ liệu, chúng ta sẽ phải thực hiện những việc sau:

Mô hình phân tích dữ liệu tổng quát

Các bước cơ bản trong một quy trình này bao gồm:

  • Import - Nhập dữ liệu

  • Tidy - Làm sạch dữ liệu

  • Transform - Chuyển đổi hình dạng dữ liệu, thông thường dữ liệu sẽ tồn tại ở hai dạng là long_format và wide_format

  • Visualize - Trực quan hóa dữ liệu khám phá

  • Model - Xây dựng các mô hình liên quan để phân tích và dự đoán

  • Communicate - Kết nối dữ liệu và kết quả phân tích tới người đọc và sử dụng thông qua các nền tảng tương tác.

Nhập dữ liệu

Bước đầu tiên trong khoa học dữ liệu, đó là nhập dữ liệu của vào phần mềm xử lý, ví dụ ở đây là R để thao tác với các hàm xử lý dữ liệu. Chúng ta sẽ lấy dữ liệu được lưu trữ trong một tệp, cơ sở dữ liệu hoặc trang web và tải nó vào thành một đối tượng để xử lý. Những bộ dữ liệu này có thể là đầu ra của một dự án nghiên cứu mã gen, kết quả của một cuộc khảo sát, các phép đo được thực hiện trong phòng thí nghiệm, v.v. Trong R chúng ta sử dụng hai thư viện readrreadxl để đọc các định dạng file vào để xử lý.

Nhập dữ liệu với các thư viện trong tidyverse

Làm sạch, gọn gàng dữ liệu

Việc tiếp theo sau khi nhập xong dữ liệu đó chính là việc làm sạch và gọn gàng dữ liệu (clean and tidy up data). Công việc này cho phép chúng ta có một bộ dữ liệu sạch và gọn gàng, giúp dễ dàng hơn cho việc trực quan và đưa vào mô hình học máy. Thông thường, với một dữ liệu được gọi là gọn gàng (tidy), cần đảm bảo hai yếu tố sau:

  • Mỗi một hàng của dữ liệu có thể được ví như một quan sát nhất định

  • Mỗi một cột của dữ liệu là một đặc tính hoặc biến số miêu tả tính chất của quan sát

Tidy up data

Tidy data

Trong module này, chúng ta sử dụng thư viện tidyr để làm gọn gàng dữ liệu.

Chuyển đổi dữ liệu

Khi dữ liệu của chúng ta đã gọn gàng, thông thường cần phải chuyển đổi dạng hình dáng của dữ liệu. Các phép biến đổi phổ biến bao gồm chuẩn hóa (normalization), tạo các biến mới từ các biến hiện có (ví dụ: chuyển đổi đơn vị từ inch sang cm) hoặc tính toán thống kê tóm tắt (ví dụ: tính mean, median, max, min, mode….).

Transform data

Chúng ta sẽ sử dụng thư viện dplyr để thao tác với dữ liệu, chuyển hóa hình dạng của dữ liệu. Thư viện này bao gồm hai tiền tố là dp bắt là viết tắt của hai từ : dataframepliers (kìm, cặp, nén), ngụ ý cho phép biến đổi và thao tác với dữ liệu.

Trực quan và mô hình hóa

Visualization and Model

Khi dữ liệu chúng ta đã sẵn sàng, việc trực quan hóa dữ liệu là cần thiết để bước đầu khám phá ra các nội dung kiến thức và tri thức từ những con số, chữ cái. Đôi khi trực quan dữ liệu mang tới những tri thức mà chúng ta có thể không tưởng tượng ra được.

Trong module này, chúng ta sử dụng thư viện ggviz để tạo ra các biểu đồ và biểu đồ động tương tác (interactive plots).

Mô hình hóa dữ liệu bản chất là đưa ra các công thức toán học hoặc các mô hình toán học nhằm bổ sung rõ hơn về mặt phân tích con số cho trực quan. Các mô hình và phương trình toán học được đưa ra áp dụng cho các quan sát và biến số trong tập dữ liệu. Điều quan trọng cần nhấn mạnh là trực quan và mô hình hóa thường sẽ dẫn đến các câu hỏi và giả thuyết mới sẽ đưa chúng ta quay trở lại bước chuyển đổi dữ liệu. Với các biến và số liệu thống kê mới, có thể hình dung lại và tinh chỉnh mô hình đề xuất trước đó.

Kết nối

Communicate

Khi các nội dung đã được phân tích và có kết quả phù hợp với bài toán đề ra, chúng ta có thể kết nối hoặc đưa ra các nội dung này tới người đọc hoặc nghiên cứu, thông qua các phương tiện như bài báo khoa học, báo cáo trình bày, thesis…..

Trong nội dung module này, tác giả sẽ trình bày về cách tạo một báo cáo khoa học chuẩn dạng Markdown kết hợp LaTex, cách đưa phương pháp nghiên cứu khả lặp (reproducible research) vào trong một bài báo cáo; Cách lấy chỉ số trích dẫn dạng BibTex và xuất chuẩn định dạng các tạp chí quốc tế uy tín.

Scientific Paper

Ví dụ về phân tích dữ liệu cơ bản

Tập dữ liệu IRIS

Bộ dữ liệu về hoa iris được thu thập bởi Edgar Anderson, một nhà thực vật học (botanist) người Mỹ, vào những năm 1920. Dữ liệu này được nhà thống kê Ronald Fisher sử dụng để chứng minh các phương pháp phân loại thống kê (Statistical methods of Classifications).

Bộ dữ liệu ban đầu chứa 150 mẫu (samples) từ ba loài hoa iris. Bạn sẽ làm việc với một phiên bản rút gọn (reduced version) chỉ có 100 mẫu từ hai loài: Iris setosa và Iris virginica.

Đối với mỗi mẫu, Anderson ghi lại chiều dài và chiều rộng của đài hoa (Speal), cũng như chiều dài và chiều rộng của cánh hoa (Petal):

Iris Flower

Câu hỏi đặt ra: Với một bông hoa diên vĩ (iris), liệu có thể phân loại nó thành setosa hay virginica dựa trên những phép đo (measurements) này không?

Trước khi bắt đầu phân tích, chúng ta cần tải các gói tidyverseggvis:

library(tidyverse)
library(ggvis)
library(kableExtra)
# Load dữ liệu
data("iris")
# Xem 6 quan sát đầu tiên
iris %>% 
  head() %>% kable()
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
5.1 3.5 1.4 0.2 setosa
4.9 3.0 1.4 0.2 setosa
4.7 3.2 1.3 0.2 setosa
4.6 3.1 1.5 0.2 setosa
5.0 3.6 1.4 0.2 setosa
5.4 3.9 1.7 0.4 setosa
# Xem 6 quan sát cuối cùng
iris %>% 
  tail() %>% kable()
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
145 6.7 3.3 5.7 2.5 virginica
146 6.7 3.0 5.2 2.3 virginica
147 6.3 2.5 5.0 1.9 virginica
148 6.5 3.0 5.2 2.0 virginica
149 6.2 3.4 5.4 2.3 virginica
150 5.9 3.0 5.1 1.8 virginica
# Xem thống kê tóm tắt các biến
iris %>% 
  summary()
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
##        Species  
##  setosa    :50  
##  versicolor:50  
##  virginica :50  
##                 
##                 
## 

Chúng ta sẽ biến đổi dữ liệu và tạo tập con nhờ thư viện dplyr và hàm filter()

library(dplyr)
# Lọc lấy dữ liệu chỉ bao gồm các quan sát cho 2 loài hoa setosa và virginica
iris %>% 
  filter(Species == "setosa" | Species == "virginica") -> iris_subset

Bây giờ chúng ta hãy vẽ biểu đồ cho từng đặc điểm của hoa để xem liệu có một biểu đồ nào có thể được sử dụng để phân loại hai loài hoa diên vĩ này hay không?

library(ggplot2)
library(plyr)
# Trực quan cho chiều dài Đài hoa
iris_subset %>% 
  group_by(Species) %>% 
  ggvis(x = ~ Sepal.Length, fill = ~ Species) %>% 
  layer_histograms(width = 0.15) 
# Trực quan cho chiều rộng Đài hoa
iris_subset %>% 
  group_by(Species) %>% 
  ggvis(x = ~ Sepal.Width, fill = ~ Species) %>% 
  layer_histograms(width = 0.15) 
# Trực quan cho chiều dài cánh hoa
iris_subset %>% 
  group_by(Species) %>% 
  ggvis(x = ~ Petal.Length, fill = ~ Species) %>% 
  layer_histograms(width = 0.15) 
# Trực quan cho chiều rộng Đài hoa
iris_subset %>% 
  group_by(Species) %>% 
  ggvis(x = ~ Petal.Width, fill = ~ Species) %>% 
  layer_histograms(width = 0.15) 

Nhìn vào 4 biểu đồ phân bố histogram, chúng ta hoàn toàn dễ dàng nhận thấy sự khác biệt về các thuộc tính với hai loài hoa này. Ví dụ: Với loài hoa setosa, chiều dài và chiều rộng cánh hoa có xu hướng tập trung ở các bông hoa có kích thước nhỏ hơn 3.0 và 1.0 (các cột tần số xuất hiện) màu xanh nước biển.

Mô hình hóa:

Nhờ các đồ thị trên, chúng ta biết rằng chúng ta có thể sử dụng chiều dài và chiều rộng của cánh hoa để phân biệt giữa một bông hoa thuộc loài Iris setosa và loài Iris virginica. Với giả định rằng chúng tôi chỉ xử lý hoa từ hai loài này, hãy sử dụng các quan sát của chúng tôi để đề xuất một mô hình đơn giản để phân loại hoa iris.

# the model is implemented with a function that takes 2 parameters
which_species <- function(petal_length, petal_width){

  if (petal_length <= 3 && petal_width <= 1){
    print("Iris setosa")
    }
  else{
    print("Iris virginica")
  }

}
# Test
which_species(petal_length=2, petal_width=0.5)
## [1] "Iris setosa"
which_species(petal_length=10, petal_width=0.5)
## [1] "Iris virginica"

Import dữ liệu

Bản chất của việc import dữ liệu là quá trình đọc một dạng dữ liệu nào đó vào RStudio và chuyển dữ liệu này thành dạng dataframe có thể đọc và phân tích được. Trong RStudio cung cấp rất nhiều thư viện cho phép import dữ liệu một cách trực tiếp và dễ dàng.

Để có thể thực hiện phân tích dữ liệu, trước tiên chúng ta cần phải có dữ liệu, mà dữ liệu có thể có từ nhiều nguồn khác nhau và được lưu trữ dưới các định dạng file khác nhau như .txt, .csv, .xlsx, hay database…. Excel là dạng file dữ liệu phổ biến nhất mà chúng ta hay gặp khi phân tích dữ liệu. Để import file Excel vào R, chúng ta có thể sử dụng package readxl.

Giả sử chúng ta có file excel là datasets.xlsx với 4 sheets là bộ dữ liệu hoa IRIS, mtcars, quakes, và bộ dữ liệu chickwts.

library(readxl)
library(dplyr)
library(DT)
mydata <- read_excel("C:/Users/Admin/OneDrive/Desktop/Project-EDA/Data/datasets.xlsx", sheet = 2)
mydata %>% 
  datatable() 

Như vậy, để import dữ liệu của tất cả các sheet trong file Excel vào R, chúng ta có thể import từng sheet trong file Excel đó vào R bằng việc sử dụng câu lệnh ở trên. Tuy nhiên, việc làm “thủ công” đó chỉ thích hợp với trường hợp file Excel chỉ gồm 1 vài sheet, còn đối với những trường hợp file Excel bao gồm cả hàng chục hoặc hàng trăm sheet thì chúng ta cần một giải pháp khác để xử lý. Chúng ta cần cài thêm package openxlsx

library(openxlsx) # package cần dùng
wb_obj <- loadWorkbook("C:/Users/Admin/OneDrive/Desktop/Project-EDA/Data/datasets.xlsx") # thống kê những sheets có trong file excel
sheet_names <- sheets(wb_obj) # tên của các sheets
for (i in 1:length(sheet_names)) {
  
  # Đối với lần lượt từng sheet trong file excel, câu lệnh sẽ import thành 1 data frame trong R với tên tương ứng theo từng sheet
  assign(sheet_names[i], readWorkbook(wb_obj, sheet = i))
  
}

Như vậy, chúng ta có tất cả 4 dataframe lấy từ 1 fiel Excel ban đầu

Import dữ liệu từ SPSS, STATA, SAS

Hiện nay, ngày càng nhiều các bạn dịch chuyển từ việc sử dụng STATA, SPSS hay SAS sang R. Tuy nhiên, khi chuyển sang công cụ mới, ta thường vẫn muốn tiếp tục sử dụng dữ liệu trên các phần mềm trước đây từng sử dụng. Quý sẽ hướng dẫn các bạn cách import dữ liệu từ SAS, SPSS hoặc STATA vào R.

Với ba loại định dạng dữ liệu trên, ta có thể sử dụng package haven được tích hợp sẵn trong tidyverse.

Với SAS

library(haven)

read_sas(“mtcars.sas7bdat”)

write_sas(mtcars, “mtcars.sas7bdat”)

Với SPSS

read_sav(“mtcars.sav”)

write_sav(mtcars, “mtcars.sav”)

Với STATA

read_dta(“mtcars.dta”)

write_dta(mtcars, “mtcars.dta”)

Chuyển gọn dữ liệu

Dữ liệu gọn là gì?

Người ta thường nói rằng 80% phân tích dữ liệu được dành cho việc làm sạch và chuẩn bị dữ liệu. Và đây không chỉ là bước đầu tiên mà còn phải lặp lại nhiều lần trong quá trình phân tích khi các vấn đề mới xuất hiện hoặc dữ liệu mới được thu thập. Tiêu chuẩn dữ liệu gọn gàng đã được thiết kế để tạo điều kiện thuận lợi cho việc khám phá và phân tích dữ liệu ban đầu, đồng thời đơn giản hóa việc phát triển các công cụ phân tích dữ liệu hoạt động tốt cùng nhau.

Happy families are all alike; every unhappy family is unhappy in its own way —- Leo Tolstoy

Giống như các gia đình, các tập dữ liệu gọn gàng đều giống nhau nhưng mọi tập dữ liệu lộn xộn đều lộn xộn theo cách riêng của nó. Tập dữ liệu ngăn nắp cung cấp một cách thức chuẩn hóa để liên kết cấu trúc của tập dữ liệu (bố cục vật lý của nó) với ngữ nghĩa của nó (ý nghĩa của nó).

Có ba quy tắc đơn giản và có liên quan với nhau giúp tập dữ liệu gọn gàng hơn:

  1. Mỗi quan sát / mẫu phải được tìm thấy trong một hàng riêng.

  2. Mỗi biến / thuộc tính mô tả mẫu phải có cột riêng.

  3. Mỗi giá trị phải có ô riêng để lưu dữ liệu

Tại sao dữ liệu cần phải làm gọn gàng (tidy)?

Có hai nội dung chính mà chúng ta cần phải đề cập tới điều này:

  1. Nếu bạn có cấu trúc dữ liệu nhất quán (consistent structure), thì việc tìm hiểu các công cụ hoạt động với cấu trúc đó sẽ dễ dàng hơn vì chúng có sự đồng nhất cơ bản (underlying uniformity). Các hàm xử lý dữ liệu trong tidyverse hoạt động hoàn hảo với các bộ dữ liệu gọn.
  2. Các nguyên tắc của dữ liệu ngăn nắp dường như rất rõ ràng.

Tuy nhiên, trong thực tế, dữ liệu mà chúng ta nhận được rất hiếm khi ở dạng tidy. Có hai lý do chính:

  1. Hầu hết mọi người không quen thuộc với các nguyên tắc của dữ liệu ngăn nắp.

  2. Dữ liệu thường được tổ chức lại theo một dạng khác để tạo điều kiện cho một số việc sử dụng khác hơn là dùng để khám phá, phân tích

Điều này có nghĩa là đối với hầu hết các phân tích thực tế, chúng ta sẽ cần sắp xếp dữ liệu của chính mình.

Ở các nội dung tiếp theo trong phần này, chúng ta sẽ làm quen với các hàm trong tidyverse để chuyển hóa dữ liệu, đưa dữ liệu sang các dạng khác nhau thích hợp để phân tích.

Giới thiệu toán tử Pipe (%>%)

Package dplyr là thành phần rất quan trọng trong hệ sinh thái tidyverse, vì nó cung cấp những function hoán chuyển và thao tác trên dữ liệu sau khi nó đã được tải vào R. Package này được phát hành lần đầu tiên vào ngày 17 tháng 1 năm 2014,và nhanh chóng tạo ra phong trào thay thế baseR bằng dplyr trong cộng đồng những người sử dụng R để phân tích dữ liệu.

pipe là 1 khái niệm thường được dùng để mô tả việc sử dụng đầu ra của 1 hàm xử lý này để sử dụng như đầu vào của 1 hàm xử lý khác. Toán tử pipe không chỉ thay đổi về cách thức viết R code, làm cho R code trong sáng và được cấu trúc tốt hơn mà còn thay đổi cách tư duy của người dùng R. Toán tử Forward Pipe %>%

Đây là dạng pipe thông dụng nhất. Công dụng của %>% là chuyển toàn bộ kết quả của hàm đi trước (bên trái) thành dữ liệu đầu vào của hàm đi sau (bên phải) trong một chuỗi quy trình. Kết quả sau cùng là của hàm cuối cùng (bên phải) trong chuỗi. Như vậy toán tử %>% có tính chất 1 chiều và định hướng từ trái sang phải. Có thể hình dung về một dòng chảy của data qua một đường ống (pipe).

library(dplyr)
# Forward pipe
rnorm(100,20,5)%>%
  matrix(ncol=2)%>%
  data.frame(x=.[,1],y=.[,2])%>%
  plot(col="red")

Là một chuỗi quy trình :

  1. Tạo ngẫu nhiên 100 số theo phân phối chuẩn, trung bình = 20, sd=5 ;

  2. Chia đều vector này thành 1 ma trậncó 2 cột;

  3. Chuyển ma trận này thành 1 dataframe, và gán giá trị 2 cột 1, 2 của ma trận cho 2 biến x,y,

  4. Cuối cùng, vẽ biểu đồ phân tán giữa X1,X2,x,y bằng hàm plot().

Cách trình bày này rõ ràng và dễ hiểu, tránh việc tạo object trung gian và lồng ghép các hàm nếu như ta không dùng pipe. Toán tử T pipe %T>% Toán tử T pipe %T>% có thể được hình dung như 1 ống nước hình chữ T, khiến cho dữ liệu đầu vào của 1 hàm A đi trước sẽ được truyền cho 2 nhánh tương ứng với quy trình B1 (là 1 hàm) và quy trình B2. Một ứng dụng phổ biến nhất của T pipe là để vẽ 2 đồ thị khác nhau cho cùng 1 gói dữ liệu. Ví dụ:

library(dplyr)
# T pipe
rnorm(100,10,1)%T>%
  ts.plot(col="red")%>%
  density()%>%
  plot(col="blue","densityplot")

Quy trình trên có nội dung là :

  1. Tạo ngẫu nhiên 100 số theo phân phối chuẩn, trung bình = 10, sd=1,

  2. Nhánh thứ 1 : vẽ một đồ thị time series (chuỗi thời gian) cho dãy số ở bước 1

  3. Nhánh thứ 2 : vẽ một biểu đồ mật độ xác suất cho dãy số ở bước 1.

Như vậy chúng ta sẽ có 2 đồ thị ở đầu ra 2 nhánh của ống T

Toán tử Assigning pipe %$%

Toán tử %$% cho phép trích xuất đích danh một đối tượng trong kết quả của hàm đi trước để sử dụng như dữ liệu đầu vào cho hàm đi sau. Như vậy nó có tính định hướng 1 chiều nhưng chuyên biệt. Tuy nhiên để sử dụng toán tử này, chúng ta cần cài thêm thư viện magrittr

library(dplyr)
library(magrittr)
# Assigning pipe
data.frame(x=rnorm(100,10,5),
           y=rnorm(100,20,5)) %$% ts.plot(x,col="green")

data.frame(x=rnorm(100,10,5),
           y=rnorm(100,20,5)) %$% ts.plot(y,col="indianred3")

Toán tử Backward pipe %<>%

Công dụng của backward pipe trái chiều với forward pipe, tức là kết quả cuối cùng sau mọi quy trình bên phải trong chuỗi sẽ được truyền ngược về đối tượng đầu tiên nằm ngoài cùng bên trái của chuỗi.

Để minh họa, xét thí dụ sau đây:

  1. x là 1 vector chứa 100 giá trị ngẫu nhiên có phân phối chuẩn và gán cho y là một vector có giá trị giống hệt x (tạo bản sao dễ so sánh)

  2. Sử dụng backward pipe để thực hiện lần lượt 2 phép toán : bình phương x, sau đó khai căn bậc 2. Dễ thấy kết quả sau cùng chính là giá trị ban đầu

  3. Có thể kiểm tra bằng cách so sánh x, y, chúng hoàn toàn như nhau

library(dplyr)
library(magrittr)
x = abs(rnorm(100))
y = x
cbind(x,y)%>%
  head()
##              x         y
## [1,] 0.7661013 0.7661013
## [2,] 0.8386013 0.8386013
## [3,] 0.4082599 0.4082599
## [4,] 0.7021833 0.7021833
## [5,] 1.2069957 1.2069957
## [6,] 0.7729443 0.7729443
x %<>% .^2 %>% 
  sqrt()
x == y
##   [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
##  [16] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
##  [31] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
##  [46] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
##  [61] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
##  [76] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
##  [91] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE

Tóm lại: Trong quá trình phân tích dữ liệu sử dụng dplyr, ngoài khả năng nhận biết và thao tác với dữ liệu (data manipulation) thì việc lựa chọn toán tử pipe nào phù hợp là điều hết sức cần thiết, việc này giúp cho chúng ta tiết kiệm thời gian khi xử lý dữ liệu, giúp code xử lý trong sáng hơn và dễ fix lỗi hơn khi có lỗi xảy ra.

Vẻ đẹp của tòa thờ thánh Basil trong trung tâm quảng trường đỏ.

Vẻ đẹp của tòa thờ thánh Basil trong trung tâm quảng trường đỏ.

Hoán chuyển dữ liệu

Khi đứng trước một tập dữ liệu, bước đầu tiên luôn là tìm ra những gì các quan sát được và các đặc điểm mô tả chúng trên dữ liệu là gì. Bước thứ hai là tìm cách để xử lý để hoán chuyển dữ liệu (transform)

  • Quan sát trong tập dữ liệu được hiển thị ở nhiều dòng (long_format)

  • Một thuộc tính nào đó của tập dữ liệu được hiển thị ở nhiều cột (wide_format)

Trong trường hợp này, dữ liệu cần được chuyển về đúng dạng để phân tích, tức là chuyển đổi linh hoạt giữa long_format và wide_format

Tiếp theo chúng ta sẽ cùng thao tác với dữ liệu gapminder, đây là tập dữ liệu hiển thị thông tin về các quốc gia trên thế giới theo các tiêu chí như GDP bình quân đầu người hàng năm, tuổi thọ trung bình của các quốc gia, dân số các quốc gia. Dữ liệu được ghi nhận từ năm 1957 tới năm 2007 cho tất cả các quốc gia trên thế giới.

Dữ liệu ban đầu quan sát ở dạng wide_format

Sử dụng hàm Spread() và Gather()

Để chuyển dữ liệu thành từ dạng hàng thành cột, chúng ta sử dụng hàm gather().

Dữ liệu gapminder ở dạng wide_format() có thể truy cập lấy từ link sau: Download

Chúng ta cần đặt tên cho hai biến mới trong cặp key-value, một cho khóa, một cho giá trị. Sử dụng hàm seperate() để phân chia nhỏ hơn các giá trị trong cột biến year.

gapminder_wide <- read.csv("C:/Users/Admin/OneDrive/Desktop/Project-EDA/Data/gapminder_wide.csv")
gapminder_wide %>% 
  head(n=5)
##   continent      country gdpPercap_1952 gdpPercap_1957 gdpPercap_1962
## 1    Africa      Algeria      2449.0082      3013.9760      2550.8169
## 2    Africa       Angola      3520.6103      3827.9405      4269.2767
## 3    Africa        Benin      1062.7522       959.6011       949.4991
## 4    Africa     Botswana       851.2411       918.2325       983.6540
## 5    Africa Burkina Faso       543.2552       617.1835       722.5120
##   gdpPercap_1967 gdpPercap_1972 gdpPercap_1977 gdpPercap_1982 gdpPercap_1987
## 1      3246.9918       4182.664       4910.417      5745.1602      5681.3585
## 2      5522.7764       5473.288       3008.647      2756.9537      2430.2083
## 3      1035.8314       1085.797       1029.161      1277.8976      1225.8560
## 4      1214.7093       2263.611       3214.858      4551.1421      6205.8839
## 5       794.8266        854.736        743.387       807.1986       912.0631
##   gdpPercap_1992 gdpPercap_1997 gdpPercap_2002 gdpPercap_2007 lifeExp_1952
## 1      5023.2166       4797.295       5288.040       6223.367       43.077
## 2      2627.8457       2277.141       2773.287       4797.231       30.015
## 3      1191.2077       1232.975       1372.878       1441.285       38.223
## 4      7954.1116       8647.142      11003.605      12569.852       47.622
## 5       931.7528        946.295       1037.645       1217.033       31.975
##   lifeExp_1957 lifeExp_1962 lifeExp_1967 lifeExp_1972 lifeExp_1977 lifeExp_1982
## 1       45.685       48.303       51.407       54.518       58.014       61.368
## 2       31.999       34.000       35.985       37.928       39.483       39.942
## 3       40.358       42.618       44.885       47.014       49.190       50.904
## 4       49.618       51.520       53.298       56.024       59.319       61.484
## 5       34.906       37.814       40.697       43.591       46.137       48.122
##   lifeExp_1987 lifeExp_1992 lifeExp_1997 lifeExp_2002 lifeExp_2007 pop_1952
## 1       65.799       67.744       69.152       70.994       72.301  9279525
## 2       39.906       40.647       40.963       41.003       42.731  4232095
## 3       52.337       53.919       54.777       54.406       56.728  1738315
## 4       63.622       62.745       52.556       46.634       50.728   442308
## 5       49.557       50.260       50.324       50.650       52.295  4469979
##   pop_1957 pop_1962 pop_1967 pop_1972 pop_1977 pop_1982 pop_1987 pop_1992
## 1 10270856 11000948 12760499 14760787 17152804 20033753 23254956 26298373
## 2  4561361  4826015  5247469  5894858  6162675  7016384  7874230  8735988
## 3  1925173  2151895  2427334  2761407  3168267  3641603  4243788  4981671
## 4   474639   512764   553541   619351   781472   970347  1151184  1342614
## 5  4713416  4919632  5127935  5433886  5889574  6634596  7586551  8878303
##   pop_1997 pop_2002 pop_2007
## 1 29072015 31287142 33333216
## 2  9875024 10866106 12420476
## 3  6066080  7026113  8078314
## 4  1536536  1630347  1639131
## 5 10352843 12251209 14326203
gapminder_wide %>% 
  tail(n=5)
##     continent        country gdpPercap_1952 gdpPercap_1957 gdpPercap_1962
## 138    Europe    Switzerland      14734.233      17909.490       20431.09
## 139    Europe         Turkey       1969.101       2218.754        2322.87
## 140    Europe United Kingdom       9979.508      11283.178       12477.18
## 141   Oceania      Australia      10039.596      10949.650       12217.23
## 142   Oceania    New Zealand      10556.576      12247.395       13175.68
##     gdpPercap_1967 gdpPercap_1972 gdpPercap_1977 gdpPercap_1982 gdpPercap_1987
## 138      22966.144      27195.113      26982.291      28397.715      30281.705
## 139       2826.356       3450.696       4269.122       4241.356       5089.044
## 140      14142.851      15895.116      17428.748      18232.425      21664.788
## 141      14526.125      16788.629      18334.198      19477.009      21888.889
## 142      14463.919      16046.037      16233.718      17632.410      19007.191
##     gdpPercap_1992 gdpPercap_1997 gdpPercap_2002 gdpPercap_2007 lifeExp_1952
## 138      31871.530       32135.32      34480.958      37506.419       69.620
## 139       5678.348        6601.43       6508.086       8458.276       43.585
## 140      22705.093       26074.53      29478.999      33203.261       69.180
## 141      23424.767       26997.94      30687.755      34435.367       69.120
## 142      18363.325       21050.41      23189.801      25185.009       69.390
##     lifeExp_1957 lifeExp_1962 lifeExp_1967 lifeExp_1972 lifeExp_1977
## 138       70.560       71.320       72.770       73.780       75.390
## 139       48.079       52.098       54.336       57.005       59.507
## 140       70.420       70.760       71.360       72.010       72.760
## 141       70.330       70.930       71.100       71.930       73.490
## 142       70.260       71.240       71.520       71.890       72.220
##     lifeExp_1982 lifeExp_1987 lifeExp_1992 lifeExp_1997 lifeExp_2002
## 138       76.210       77.410       78.030       79.370       80.620
## 139       61.036       63.108       66.146       68.835       70.845
## 140       74.040       75.007       76.420       77.218       78.471
## 141       74.740       76.320       77.560       78.830       80.370
## 142       73.840       74.320       76.330       77.550       79.110
##     lifeExp_2007 pop_1952 pop_1957 pop_1962 pop_1967 pop_1972 pop_1977 pop_1982
## 138       81.701  4815000  5126000  5666000  6063000  6401400  6316424  6468126
## 139       71.777 22235677 25670939 29788695 33411317 37492953 42404033 47328791
## 140       79.425 50430000 51430000 53292000 54959000 56079000 56179000 56339704
## 141       81.235  8691212  9712569 10794968 11872264 13177000 14074100 15184200
## 142       80.204  1994794  2229407  2488550  2728150  2929100  3164900  3210650
##     pop_1987 pop_1992 pop_1997 pop_2002 pop_2007
## 138  6649942  6995447  7193761  7361757  7554661
## 139 52881328 58179144 63047647 67308928 71158647
## 140 56981620 57866349 58808266 59912431 60776238
## 141 16257249 17481977 18565243 19546792 20434176
## 142  3317166  3437674  3676187  3908037  4115771
gapminder_long <- gapminder_wide %>% 
  gather(key   = year,
         value = value,
         dplyr::starts_with('pop'),
         dplyr::starts_with('lifeExp'),
         dplyr::starts_with('gdpPercap')) %>%
  separate(year,
           into = c('obs_type','year'),
           sep = "_",
           convert = TRUE) #this ensures that the year column is an integer rather than a character
gapminder_long %>% 
  head(n=5)
##   continent      country obs_type year   value
## 1    Africa      Algeria      pop 1952 9279525
## 2    Africa       Angola      pop 1952 4232095
## 3    Africa        Benin      pop 1952 1738315
## 4    Africa     Botswana      pop 1952  442308
## 5    Africa Burkina Faso      pop 1952 4469979

Trong nội dung trên, hàm separate() được sử dụng để tách biến year thành các thành phần khác nhau. Câu hỏi đặt ra là sau khi chuyển dữ liệu về long_format, có ích lợi gì? Hãy cùng xem xét phân tích trực quan sử dụng thư viện ggplot2 về tuổi thọ trung bình của dữ liệu dành cho Việt Nam thông qua tập dữ liệu gapminder_long đã được chuyển đổi từ gapminder_wide:

library(ggplot2)
gapminder_long %>% 
  filter(obs_type == "lifeExp", 
         country == "Vietnam") -> vietnam
ggplot(vietnam, aes(x = year, y = value)) +
  geom_line(size = 1.5, color = "lightgrey") +
  geom_point(size = 3, color = "steelblue") +
  labs(y = "Life Expectancy (years)",
  x = "Year",
  title = "Life expectancy changes over time of Vietnam",
  subtitle = "Vietnam (1952-2007)",
  caption = "Source: http://www.gapminder.org/data/")

Nhìn vào đồ thị, chúng ta dễ dàng nhận thấy sự thay đổi khác biệt về tuổi thọ trung bình của người Việt Nam có sự thay đổi từ năm 1952 tới năm 2007.

Một ví dụ khác về phân tích trực quan với dữ liệu dạng long_format là sử dụng biểu đồ Cleverland, giả sử chúng ta muốn trực quan dữ liệu gapminder_long so sánh về tuổi thọ trung bình của tất cả các quốc gia ở Châu Âu (Europe) vào năm 2007

plotdata <- gapminder_long %>% 
  filter(continent == "Europe" & year == 2007 & obs_type == "lifeExp")
plotdata %>% 
  head()
##   continent                country obs_type year  value
## 1    Europe                Albania  lifeExp 2007 76.423
## 2    Europe                Austria  lifeExp 2007 79.829
## 3    Europe                Belgium  lifeExp 2007 79.441
## 4    Europe Bosnia and Herzegovina  lifeExp 2007 74.852
## 5    Europe               Bulgaria  lifeExp 2007 73.005
## 6    Europe                Croatia  lifeExp 2007 75.748
ggplot(plotdata, aes(x = value, y=reorder(country, value))) +
  geom_point(color="blue", size = 2) +
  geom_segment(aes(x = 40, xend = value, 
                   y = reorder(country, value), 
                   yend = reorder(country, value)), 
                   color = "lightgrey") +
  labs (x = "Life Expectancy (years)",
        y = "",
        title = "Life Expectancy by Country",
        subtitle = "GapMinder data for Europe - 2007") +
  theme_classic() +
  scale_fill_brewer(palette = "Blues") 

Bây giờ, chúng ta cùng đến với hàm spread() . Về bản chất, hàm này cho phép làm quá trình ngược lại với gather() . Tức là cho phép hoán chuyển dữ liệu từ dạng long format sang wide format

gap_wide_new <- gapminder_long %>% 
  # first unite obs_type and year into a new column called var_names. Separate by _
  unite(col = var_names, obs_type, year, sep = "_") %>% 
  # then spread var_names out by key-value pair.
  spread(key = var_names, value = value)
gap_wide_new %>% 
  head()
##   continent      country gdpPercap_1952 gdpPercap_1957 gdpPercap_1962
## 1    Africa      Algeria      2449.0082      3013.9760      2550.8169
## 2    Africa       Angola      3520.6103      3827.9405      4269.2767
## 3    Africa        Benin      1062.7522       959.6011       949.4991
## 4    Africa     Botswana       851.2411       918.2325       983.6540
## 5    Africa Burkina Faso       543.2552       617.1835       722.5120
## 6    Africa      Burundi       339.2965       379.5646       355.2032
##   gdpPercap_1967 gdpPercap_1972 gdpPercap_1977 gdpPercap_1982 gdpPercap_1987
## 1      3246.9918      4182.6638      4910.4168      5745.1602      5681.3585
## 2      5522.7764      5473.2880      3008.6474      2756.9537      2430.2083
## 3      1035.8314      1085.7969      1029.1613      1277.8976      1225.8560
## 4      1214.7093      2263.6111      3214.8578      4551.1421      6205.8839
## 5       794.8266       854.7360       743.3870       807.1986       912.0631
## 6       412.9775       464.0995       556.1033       559.6032       621.8188
##   gdpPercap_1992 gdpPercap_1997 gdpPercap_2002 gdpPercap_2007 lifeExp_1952
## 1      5023.2166      4797.2951      5288.0404      6223.3675       43.077
## 2      2627.8457      2277.1409      2773.2873      4797.2313       30.015
## 3      1191.2077      1232.9753      1372.8779      1441.2849       38.223
## 4      7954.1116      8647.1423     11003.6051     12569.8518       47.622
## 5       931.7528       946.2950      1037.6452      1217.0330       31.975
## 6       631.6999       463.1151       446.4035       430.0707       39.031
##   lifeExp_1957 lifeExp_1962 lifeExp_1967 lifeExp_1972 lifeExp_1977 lifeExp_1982
## 1       45.685       48.303       51.407       54.518       58.014       61.368
## 2       31.999       34.000       35.985       37.928       39.483       39.942
## 3       40.358       42.618       44.885       47.014       49.190       50.904
## 4       49.618       51.520       53.298       56.024       59.319       61.484
## 5       34.906       37.814       40.697       43.591       46.137       48.122
## 6       40.533       42.045       43.548       44.057       45.910       47.471
##   lifeExp_1987 lifeExp_1992 lifeExp_1997 lifeExp_2002 lifeExp_2007 pop_1952
## 1       65.799       67.744       69.152       70.994       72.301  9279525
## 2       39.906       40.647       40.963       41.003       42.731  4232095
## 3       52.337       53.919       54.777       54.406       56.728  1738315
## 4       63.622       62.745       52.556       46.634       50.728   442308
## 5       49.557       50.260       50.324       50.650       52.295  4469979
## 6       48.211       44.736       45.326       47.360       49.580  2445618
##   pop_1957 pop_1962 pop_1967 pop_1972 pop_1977 pop_1982 pop_1987 pop_1992
## 1 10270856 11000948 12760499 14760787 17152804 20033753 23254956 26298373
## 2  4561361  4826015  5247469  5894858  6162675  7016384  7874230  8735988
## 3  1925173  2151895  2427334  2761407  3168267  3641603  4243788  4981671
## 4   474639   512764   553541   619351   781472   970347  1151184  1342614
## 5  4713416  4919632  5127935  5433886  5889574  6634596  7586551  8878303
## 6  2667518  2961915  3330989  3529983  3834415  4580410  5126023  5809236
##   pop_1997 pop_2002 pop_2007
## 1 29072015 31287142 33333216
## 2  9875024 10866106 12420476
## 3  6066080  7026113  8078314
## 4  1536536  1630347  1639131
## 5 10352843 12251209 14326203
## 6  6121610  7021078  8390505

Điện Kremlin tại thành phố Kazan, LB NGA, di sản UNESCO thế giới

Thao tác cùng dplyr

Trong nội dung này, chúng ta sẽ làm việc chính với các hàm thao tác dữ liệu cùng thư viện dplyr. Khi nói đến phân tích dữ liệu trong R, thư viện dplyr cung cấp những khả năng phân tích tuyệt vời. Đó là một công cụ toàn diện - cung cấp cho bạn khả năng chuyên sâu sâu rộng (drill-down abilities) khi giữ mã R khá trong sáng và dễ hiểu. Để giúp thực hành với dplyr, chúng ta cùng sử dụng tập dữ liệu gapminder có trong thư viện gapminder, miêu tả về GDP, tuổi thọ trung bình, dân số của các quốc gia trên thế giới.

Lọc dữ liệu filter

Chức năng này cho phép lọc dữ liệu theo dòng, theo các quan sát. Ví dụ: Chúng ta muốn tìm hiểu dữ liệu của Việt Nam vào các năm 1997, 2002 và 2007. Khi đó sử dụng filter như sau:

library(dplyr)
library(gapminder)
data("gapminder")
gapminder %>% 
  head()
## # A tibble: 6 x 6
##   country     continent  year lifeExp      pop gdpPercap
##   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
## 1 Afghanistan Asia       1952    28.8  8425333      779.
## 2 Afghanistan Asia       1957    30.3  9240934      821.
## 3 Afghanistan Asia       1962    32.0 10267083      853.
## 4 Afghanistan Asia       1967    34.0 11537966      836.
## 5 Afghanistan Asia       1972    36.1 13079460      740.
## 6 Afghanistan Asia       1977    38.4 14880372      786.
vietnam <- gapminder %>% 
  filter(continent == "Asia",
         country == "Vietnam",
         year %in% c(1997, 2002, 2007))
vietnam %>% 
  head()
## # A tibble: 3 x 6
##   country continent  year lifeExp      pop gdpPercap
##   <fct>   <fct>     <int>   <dbl>    <int>     <dbl>
## 1 Vietnam Asia       1997    70.7 76048996     1386.
## 2 Vietnam Asia       2002    73.0 80908147     1764.
## 3 Vietnam Asia       2007    74.2 85262356     2442.

Nếu nhiều giá trị phù hợp với tiêu chí tìm kiếm của bài toán, hãy sử dụng toán tử % in% và đưa ra một véc tơ số hoặc chuỗi ký tự phù hợp.

Tóm tắt dữ liệu với summarize

Thống kê tóm tắt là một bước quan trọng trong bất kỳ phân tích dữ liệu khám phá nào. Chúng cho phép tìm giá trị mô tả tốt nhất một mẫu dữ liệu hoặc danh sách các giá trị đại diện tốt nhất cho từng tập con của mẫu. Ví dụ: Chúng ta muốn thống kê xem vào năm 2007, tuổi thọ trung bình của người dân Châu Âu là bao nhiêu?

gapminder %>% 
  filter(continent == "Europe",
         year == 2007) %>% 
  summarise(mean_euro_lifeExp = mean(lifeExp))
##   mean_euro_lifeExp
## 1           77.6486

Kết quả trả về là 77.6 tuổi. Tuy nhiên một câu hỏi có thể đặt ra là, nếu chúng ta có nhiều nhóm các quốc gia hoặc nhiều nhóm các lục địa, làm thế nào để so sánh một cách nhanh nhất tuổi thọ trung bình giữa các quốc gia hoặc lục địa? Nếu chỉ sử dụng filter và summarize thông thường thì sẽ rất lâu. Ở đây, hàm summarize chỉ thực sự phát huy sức mạnh trong trường hợp nó được kết hợp với hàm group_by(). Việc thực hiện thống kê số liệu theo nhóm luôn là nội dung nổi bật của việc tính toán thống kê.

Hàm group_by thống kê theo nhóm

Câu hỏi thống kê đặt ra trong trường hợp này là: Hãy thống kê tuổi thọ trung bình của các châu lục vào năm 2007 và so sánh.

gapminder %>% 
  filter(year == 2007) %>% 
  group_by(continent) %>% 
  summarise(avg_lifeExp = mean(lifeExp))
##   avg_lifeExp
## 1    67.00742

Kết quả thống kê theo nhóm cho thấy, tuổi thọ của Châu Đại Dương (Úc và NewZealands và các đảo khác) là lớn nhất với 80.7, theo sau là châu Âu, Mỹ, Châu Á và cuối cùng là Châu Phi thấp nhất với xấp xỉ 55 tuổi.

Thử thiết lập một câu hỏi khác như sau: Hãy sắp xếp theo tổng số dân số các châu lục từ cao xuống thấp vào năm 2007? Lưu ý: Sử dụng thêm hàm arrange() để sắp xếp dữ liệu đầu ra

gapminder %>% 
  filter(year == 2007) %>% 
  group_by(continent) %>% 
  summarise(totalPopulation = sum(pop)) %>% 
  arrange(desc(totalPopulation))
##   totalPopulation
## 1      6251013179

Thêm biến với mutate

Hàm mutate được sử dụng trong phân tích khi chúng ta cần thêm một biến số mới nào đó với các nội dung được phân tích hoặc tính toán bổ sung. Ý nghĩa của mutate được miêu tả như sau: newColumn = your_calculation. Trong đó biến số mới (cột mới) là một biểu thức tính toán từ các biến đã có trước đó. Ví dụ: Chúng ta muốn thêm một cột biến mới trong tập dữ liệu gapminder với ý nghĩa: Tính tổng GDP của một quốc gia, việc này có thể thực hiện được bằng cách lấy tích của GDP đầu người và số dân số của quốc gia đó:

gapminder %>%
  filter(year == 2007) %>%
  mutate(totalGdp = pop * gdpPercap) %>% 
  head()
## # A tibble: 6 x 7
##   country     continent  year lifeExp      pop gdpPercap      totalGdp
##   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>         <dbl>
## 1 Afghanistan Asia       2007    43.8 31889923      975.  31079291949.
## 2 Albania     Europe     2007    76.4  3600523     5937.  21376411360.
## 3 Algeria     Africa     2007    72.3 33333216     6223. 207444851958.
## 4 Angola      Africa     2007    42.7 12420476     4797.  59583895818.
## 5 Argentina   Americas   2007    75.3 40301927    12779. 515033625357.
## 6 Australia   Oceania    2007    81.2 20434176    34435. 703658358894.

Hàm select()

Nếu như hàm filter() cho phép lọc dữ liệu theo hàng thì hàm select() cho phép lọc biến số (lấy cột dữ liệu). Ví dụ trong tập dữ liệu gapminder, chúng ta chỉ quan tâm tới tuổi thọ dân số và số lượng dân số của các quốc gia:

gapminder %>% 
  select(country, continent, pop, lifeExp) -> mydata
mydata %>% 
  head()
## # A tibble: 6 x 4
##   country     continent      pop lifeExp
##   <fct>       <fct>        <int>   <dbl>
## 1 Afghanistan Asia       8425333    28.8
## 2 Afghanistan Asia       9240934    30.3
## 3 Afghanistan Asia      10267083    32.0
## 4 Afghanistan Asia      11537966    34.0
## 5 Afghanistan Asia      13079460    36.1
## 6 Afghanistan Asia      14880372    38.4

Một số dạng câu hỏi liên quan tham khảo

Câu hỏi 1: Hãy thêm một cột biến mới vào trong tập dữ liệu gapminder, cột này gọi là tuổi thọ theo tháng (chuyển từ year -> month). Hãy tìm xem quốc gia nào có tuổi thọ theo tháng thấp nhất và cao nhất ở châu Á vào năm 2002.

gapminder %>% 
  mutate(life_months = 12 * lifeExp) %>% 
  filter(year == 2002 & continent == "Asia") %>% 
  arrange(desc(life_months)) -> asia_month
asia_month %>% 
  head()
## # A tibble: 6 x 7
##   country          continent  year lifeExp       pop gdpPercap life_months
##   <fct>            <fct>     <int>   <dbl>     <int>     <dbl>       <dbl>
## 1 Japan            Asia       2002    82   127065841    28605.        984 
## 2 Hong Kong, China Asia       2002    81.5   6762476    30209.        978.
## 3 Israel           Asia       2002    79.7   6029529    21906.        956.
## 4 Singapore        Asia       2002    78.8   4197776    36023.        945.
## 5 Korea, Rep.      Asia       2002    77.0  47969150    19234.        925.
## 6 Taiwan           Asia       2002    77.0  22454239    23235.        924.
asia_month %>% 
  tail()
## # A tibble: 6 x 7
##   country     continent  year lifeExp      pop gdpPercap life_months
##   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>       <dbl>
## 1 Nepal       Asia       2002    61.3 25873917     1057.        736.
## 2 Yemen, Rep. Asia       2002    60.3 18701257     2235.        724.
## 3 Myanmar     Asia       2002    59.9 45598081      611         719.
## 4 Iraq        Asia       2002    57.0 24001816     4391.        685.
## 5 Cambodia    Asia       2002    56.8 12926707      896.        681.
## 6 Afghanistan Asia       2002    42.1 25268405      727.        506.

Nhìn vào dữ liệu, tuổi thọ theo tháng cao nhất là Nhật Bản và thấp nhất vào năm 2002 là Afghanistan.

Vẻ đẹp của thành phố Nzhiny Novgrod, liên bang Nga.

Trực quan hóa dữ liệu

Việc trực quan dữ liệu thực sự khá là quan trọng, nó không chỉ giúp bước đầu cho người phân tích dữ liệu có thể hiểu được những bí mật chưa được khám phá hoặc bước nào đó giúp người tìm hiểu có thể có những hiểu biết sơ lược về dữ liệu mà mình cần phân tích.

Dữ liệu quả thật rất khô khan, nếu chỉ nhìn nó từ góc cạnh là những con số, chữ cái thì không khác gì một ông nghiện rượu Vodka người Nga cả, thay vào đó, chúng ta hãy biến người nghiện rượu này thành một cô gái Nga xinh đẹp bên hàng bạch dương vàng óng ả thông qua công cụ trực quan.

Trong module này, dữ liệu gapmin sẽ được phân tích trực quan theo một số phương pháp tiếp cận dễ nhất, để thấy được nét đẹp của việc trực quan dữ liệu.

Cô gái Nga bên hàng bạch dương tỏa nắng vàng

Hãy làm quen với Edward Tufte, vì ông là một guru - bậc thầy về biểu đồ. Ông là giáo sư thống kê học của Đại học Yale, giáo sư chính trị học và giáo sư về khoa học máy tính của đại học Yale, ông là người có ảnh hưởng rất lớn tới lĩnh vực trực quan dữ liệu. Ông là người đã dám thế chấp căn nhà của mình để vay tiền ngân hàng nghiên cứu và cho ra công trình data visualization mà sau này ông không bao giờ phải hối hận. Tờ báo New York Times gọi ông là Leonardo Da Vinci of Data

Edward Tufte đặt ra 4 triết lý và nguyên tắc chính trong trình bày dữ liệu bằng biểu đồ. Có thể tóm gọn như sau “Graphical excellence is that which gives to the viewer the greatest number of ideas in the shortest time with the least ink in the smallest space”. Như vậy, khi trình bày dữ liệu bằng biểu đồ phải để ý tới thông tin của 4 khía cạnh: Lượng thông tin, thời gian, lượng mực in và không gian.

Từ đó có thể hiểu 4 nguyên tắc chính khi trình bày dữ liệu chính là: Phản ảnh dữ liệu một cách đầy đủ, tối ưu hóa dữ liệu trên mực in, tối ưu hóa mật độ dữ liệu và trình bày dữ liệu chứ không phải trang trí biểu đồ.

Edward Tufte

Chúng ta cùng làm quen với thư viện trực quan cơ bản trong R là ggplot2.

ggplot2 do tác giả Hadley Wickham phát triển và phổ biến là một thư viện rất có ích cho việc biên soạn biểu đồ với chất lượng cao. Cú pháp của ggplot2 được mô phỏng theo việc chồng lấp các lớp (layers); mỗi lớp sẽ có một chức năng riêng. Tuy nhiên có 3 layers chính, đó là:

  • Lớp xác định biến số cần trực quan dữ liệu

  • Lớp hình thức thể hiện

  • Lớp trang trí, gán nhãn cho biểu đồ..

Trong nội dung này, chúng tôi đề cập đến việc trực quan bộ dữ liệu gapminder với hai biến số cần thiết là GDP đầu người và tuổi thọ trung bình, việc trực quan được thực hiện cho các quốc gia tại các châu lục trên thế giới, ở đây sử dụng biểu đồ phân tán scatter plot để phân tích mối tương quan giữa 2 biến số này.

Hình thức thể hiện:

Hình thức thể hiện của biến x và y thể hiện qua argument có tên là geom(), trong đó có một số loại phổ biến sau:

  • geom_point(): Biểu đồ tương quan

  • geom_box(): Biểu đồ hộp

  • geom_histogram(): Biểu đồ phân bố

  • geom_line(): Biểu đồ dây

  • geom_text(): Biểu đồ chữ viết

  • geom_smooth(): Chọn đường biểu diễn làm mịn dữ liệu

  • geom_jitter(): Tạo sàng rung, hiển thị dữ liệu, fix lỗi overplotting

  • geom_hline(): Đường biểu diễn nằm ngang

  • geom_vline(): Đường biểu diễn thẳng đứng

# Định nghĩa biến số đưa vào và lớp hình thức thể hiện
ggplot(data = gapminder, mapping = aes(x = gdpPercap, y = lifeExp, size = pop, color = continent)) +
  geom_point()

Sự mất cân đối về tỷ lệ dữ liệu ở đây tạo ra sự trực quan mất đối xứng, trong khi trục Ox thể hiện GDP theo con số hàng nghìn thì trục Oy thể hiện theo con số hàng chục, để chỉnh sửa lại kích thước, sử dụng scale_x_log() có nghĩa là hoán chuyển đơn vị của trục Ox về đơn vị log10, nhưng không mất đi tỷ lệ phân chia với trục Oy. Đây cũng là một kỹ thuật trực quan thường thấy khi phân tích các biến số có độ lệch tương đối lớn với nhau.

# Chuyển về đơn vị log10
ggplot(data = gapminder, mapping = aes(x = gdpPercap, y = lifeExp)) +
  geom_point(aes(color = continent)) +
  scale_x_log10() +
  # Thêm đường hồi quy làm mịn dữ liệu
  geom_smooth(method = "loess") + 
  labs(x =" Log GDP per Capita", y = "Life Expectancy") +
  ggtitle("Association between GDP Per Capita and Life Expectancy") +   theme(plot.title = element_text(lineheight = 0.8, face = "bold", hjust = 0.5))
## `geom_smooth()` using formula 'y ~ x'

Ngoài ra, để mang tính “chuyên nghiệp” hơn một chút. Chúng ta có thể sử dụng gói thư viện ggthemes. Trong gói này chứa các giao diện biểu thị đồ họa của một số tạp chí uy tín. Điển hình là tạp chí The Economist.

library(ggthemes)
ggplot(data = gapminder, mapping = aes(x = gdpPercap, y = lifeExp)) +
  geom_point(aes(color = continent)) +
  scale_x_log10() +
  # Thêm đường hồi quy làm mịn dữ liệu
  geom_smooth(method = "loess") + 
  labs(x =" Log GDP per Capita", y = "Life Expectancy") +
  ggtitle("Association between GDP Per Capita and Life Expectancy") +   theme(plot.title = element_text(lineheight = 0.8, face = "bold", hjust = 0.5)) +
  theme_economist()
## `geom_smooth()` using formula 'y ~ x'

Biểu đồ thanh so sánh

Nội dung tiếp theo, chúng ta cùng sử dụng biểu đồ thanh và kỹ thuật xoay ngược trục để trực quan dữ liệu về tuổi thọ trung bình và GDP trung bình năm 2007 cho các quốc gia tại Châu Á để so sánh.

# Lọc dữ liệu cho châu Á.
mydata <- gapminder %>% 
  filter(continent == "Asia" & year == 2007)
# Trực quan bằng biểu đồ bar plot
ggplot(data = mydata, mapping = aes(x = country, y = lifeExp, fill = country)) +
  geom_bar(stat = "identity", width = 0.9) +
  # Đảo trục
  coord_flip()

Dĩ nhiên biểu đồ này có rất nhiều thông tin thừa và dữ liệu chưa được sắp xếp lại nên nhìn khá lộn xộn. Chúng ta cùng tinh chỉnh (fine tune) biểu đồ này sao cho hợp lý

ggplot(data = mydata, mapping = aes(x = reorder(country, lifeExp), y = lifeExp, fill = country)) + 
  geom_bar(stat = "identity", width = 0.9) + 
  coord_flip()+ 
  theme(legend.position = "none") +
  # Loại bỏ legend dư thừa. 
  # Gán cho đối tượng tên là graph1
  labs(x="", y="Life Expectancy", title = "Life Expectancy",
       subtitle = "Collecting in 2007",
       caption = "source = www.gapminder-org")  -> graph1
graph1

Chúng ta làm tương tự với thu nhập bình quân đầu người GDP và gán cho một đối tượng đồ họa là graph2

ggplot(data = mydata, mapping = aes(x = reorder(country, gdpPercap), y = gdpPercap, fill = country)) + 
  geom_bar(stat = "identity", width = 0.9) + 
  coord_flip()+ 
  theme(legend.position = "none") +
  # Loại bỏ legend dư thừa. 
  # Gán cho đối tượng tên là graph1
  labs(x="", y="GDP per Cap", title = "GDP Per Cap",
       subtitle = "Collecting in 2007",
       caption = "source = www.gapminder-org")  -> graph2
graph2

Chúng ta cùng khớp hai đồ thị này trên cùng một panel để tiện so sánh, sử dụng gói gridExtra

library(gridExtra)
grid.arrange(graph1, graph2, nrow = 1)

Việc so sánh số liệu giữa các quốc gia châu Á và thứ tự theo tuổi thọ trung bình và GDP đầu người trở nên dễ dàng hơn bao giờ hết.


  1. Department of Computer Sciences and Engineering, Faculty of Information Technology, Thai Nguyen University of Information Technology and Communication, ↩︎