DATA ANALYTICS
Hands-on with R programming language
January 2022
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-Seq và ChIP-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 tidyverse và ggviz , đâ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 readr và readxl để đọ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à d và p bắt là viết tắt của hai từ : dataframe và pliers (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 tidyverse và ggvis:
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
<- function(petal_length, petal_width){
which_species
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)
<- read_excel("C:/Users/Admin/OneDrive/Desktop/Project-EDA/Data/datasets.xlsx", sheet = 2)
mydata %>%
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
<- loadWorkbook("C:/Users/Admin/OneDrive/Desktop/Project-EDA/Data/datasets.xlsx") # thống kê những sheets có trong file excel
wb_obj <- sheets(wb_obj) # tên của các sheets
sheet_names 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:
Mỗi quan sát / mẫu phải được tìm thấy trong một hàng riêng.
Mỗi biến / thuộc tính mô tả mẫu phải có cột riêng.
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:
- 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.
- 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:
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.
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 :
Tạo ngẫu nhiên 100 số theo phân phối chuẩn, trung bình = 20, sd=5 ;
Chia đều vector này thành 1 ma trậncó 2 cột;
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,
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à :
Tạo ngẫu nhiên 100 số theo phân phối chuẩn, trung bình = 10, sd=1,
Nhánh thứ 1 : vẽ một đồ thị time series (chuỗi thời gian) cho dãy số ở bước 1
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:
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)
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
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)
= abs(rnorm(100))
x = x
y 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
%<>% .^2 %>%
x sqrt()
== y x
## [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 đỏ.
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.
<- read.csv("C:/Users/Admin/OneDrive/Desktop/Project-EDA/Data/gapminder_wide.csv")
gapminder_wide %>%
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_wide %>%
gapminder_long gather(key = year,
value = value,
::starts_with('pop'),
dplyr::starts_with('lifeExp'),
dplyr::starts_with('gdpPercap')) %>%
dplyrseparate(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",
== "Vietnam") -> vietnam
country 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
<- gapminder_long %>%
plotdata 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
<- gapminder_long %>%
gap_wide_new # 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.
<- gapminder %>%
vietnam filter(continent == "Asia",
== "Vietnam",
country %in% c(1997, 2002, 2007))
year %>%
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",
== 2007) %>%
year 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 Á.
<- gapminder %>%
mydata 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.
Department of Computer Sciences and Engineering, Faculty of Information Technology, Thai Nguyen University of Information Technology and Communication, tqquy@ictu.edu.vn↩︎