Nhân tử pipe kí hiệu là %>% (tổ hợp tắt trong window là ctr+shift+M) là một trong những phương thức giúp cách triển khai code trở nên gọn gàng hơn. Nhân tử pipe là một thuộc tính trong package magrittr của Stefan Milton và được phát triển theo đề xuất của Tal Galili. Ý tưởng của nhân tử pipe là triển khai thuật toán từ trong ra ngoài, trong đó kết quả của phép toán trước làm input cho phép toán sau. Chẳng hạn như thông thường tính tổng bình phương tất cả các phần từ của một vector ta sẽ viết như sau:
#1. Tạo vector
vector <- c(seq(1,5,by = 1))
#2. TÍnh tổng bình phương
sum(vector^2)
## [1] 55
Tuy nhiên với nhân tử pipe thì ta chỉ cần viết trong 1 phép toán
#1. Trước tiên phải load package magrittr
library(magrittr)
c(seq(1,5,by = 1))^2 %>% sum()
## [1] 55
Trong phép toán trên sẽ không cần phải tạo thêm một biến trung gian là vector mà sử dụng luôn kết quả từ bước tính toán trước do đó giúp tiết kiệm bộ nhớ và thời gian lưu trữ biến lên global nên tốc độ tính toán sẽ nhanh hơn. Trước khi sử dụng pipe ta cần phải load package magrittr. Một số trường hợp có thể load package dplyr cũng được vì package này kế thừa nhân tử pipe của magrittr.
#1. Trước tiên phải load package magrittr
library(dplyr)
c(seq(1,5,by = 1))^2 %>% sum()
## [1] 55
Kiểm tra tốc độ tính toán
start.time <- Sys.time()
#Block code cần thực thi
vector <- c(seq(1,5,by = 1))
sum(vector^2)
## [1] 55
#Kết thúc block code
end.time <- Sys.time()
end.time - start.time
## Time difference of 0.01199698 secs
library(magrittr)
start.time <- Sys.time()
#Block code cần thực thi
c(seq(1,5,by = 1))^2 %>% sum()
## [1] 55
#Kết thúc block code
end.time <- Sys.time()
end.time - start.time
## Time difference of 0.007999897 secs
Tố́c độ được cải thiện khoảng 2-3 lần. Trong một số trường hợp khi kết quả trả về là dataframe ta sẽ không thể truy cập được các phần tử bên trong của nó. Chẳng hạn như:
#1. Tao dataframe
df <- data.frame(a=c(1,2,3,4),b=c(5,6,7,8))
#2. Tính tổng vector a
df %>% sum(a)
## Error in function_list[[k]](value): object 'a' not found
Lỗi xuất hiện là không tìm thấy vector a do nhân tử pipe coi toàn bộ output của phép toán trước là 1 khối. Muốn thoát khỏi lỗi này ta phải sử dụng nhân tử pipe rẽ nhánh %$% (đây là tên do mình đặt, không rõ tên thật là gì). Nhân tử pipe rẽ nhánh này được update trong các phiên bản mới của package magrittr cho phép hiện toàn bộ các phần tử của phép toán liền trước và các xử lý phía sau có thể truy cập mà không gặp lỗi, chức năng cũng gần như hàm attach(dataframe).
#1. Tao dataframe
df <- data.frame(a=c(1,2,3,4),b=c(5,6,7,8))
#2. Tính tổng vector a
df %$% sum(a)
## [1] 10
Hoặc ta có thể dùng hàm attach
#1. Tao dataframe
df <- data.frame(a=c(1,2,3,4),b=c(5,6,7,8))
#2. Attach dataframe để truy cập các biến mà không cần tên bảng
attach(df)
#2. Tính tổng vector a
sum(a)
## [1] 10
Trong thống kê và tổng hợp dữ liệu với R, nhân tử pipe thường được sử dụng kết hợp với các hàm summary, group_by sẽ giúp cho các tính toán trở nên đơn giản và ngắn gọn hơn. Chẳng hạn chúng ta muốn thống kê theo nhóm những chủng loại với kích cỡ trung bình trong bảng iris ta làm như sau
#1. Xóa các dữ liệu trong global
rm(list = ls())
#2. Load các package
library(magrittr)
library(dplyr)
#3. thống kê theo Species
iris %>% subset(Sepal.Length > 4) %>%
group_by(Species) %>%
summarise(Avg.Length = mean(Sepal.Length), Avg.Width = mean(Sepal.Width))
## # A tibble: 3 x 3
## Species Avg.Length Avg.Width
## <fctr> <dbl> <dbl>
## 1 setosa 5.006 3.428
## 2 versicolor 5.936 2.770
## 3 virginica 6.588 2.974
Để sử dụng được các hàm group_by và summarize thì phải import package dplyr.
Kết quả trả về là bảng tổng hợp các chỉ tiêu theo từng nhóm. Trong ví dụ trên ta sử dụng hàm subset để lọc theo điều kiện của dataframe là chỉ lấy những mẫu có sepal.length > 4.
Nhân tử pipe overwrite có tính chất như nhân tử pipe nhưng cho phép ghi đè lên dữ liệu gốc ban đầu. Vì thể kí hiệu của nó là sự kết hợp của nhân tử pipe thông thường %>% và phép gán <- thành kí hiệu %<>%
rm(list = ls())
library(magrittr)
library(dplyr)
#1. Lấy danh sách các chủng loại khác nhau
iris %>% distinct(Species)
## Species
## 1 setosa
## 2 versicolor
## 3 virginica
#2. Sử dụng nhân tử pipe overwite để ghi đè lên dữ liệu iris ban đầu
iris %<>% subset(Species %>% equals("setosa"))
#3. Lấy danh sách các chủng loại sau khi ghi đè lên iris, hiện tại iris sẽ chỉ còn setosa.
iris %>% distinct(Species)
## Species
## 1 setosa
Ngoài ra nhân tử pipe cũng thường sử dựng kết hợp với các hàm số. Bên dưới là một ví dụ:
library(dplyr)
set.seed(1) #tái hiện lại kết quả cho lần chạy sau
sampleWithReplace <- function(v,n=100) sample(v,size = n, replace = TRUE)
auction.data <-
data.frame(
Price = 1:100 %>% sampleWithReplace,
Quantity = 1:100 %>% sampleWithReplace,
Type =
0:1 %>%
sampleWithReplace %>%
factor(labels = c("Buy","Sell"))
)
head(auction.data)
## Price Quantity Type
## 1 27 66 Buy
## 2 38 36 Buy
## 3 58 28 Sell
## 4 91 100 Buy
## 5 21 64 Buy
## 6 90 22 Sell
Trong xử lý các bảng dữ liệu lớn chúng ta có thể chỉ quan tâm đến một phần nhỏ dữ liệu thay vì quan tâm đến toàn bộ bảng dữ liệu. Vì thể các hàm điều kiện để lọc bảng rất thường xuyên được sử dụng chẳng hạn như if,ifelse,filter,subset
Đây là cách xử lý dữ liệu cơ bản nhất dành cho những bạn mới bắt đầu làm quen với R. Chúng ta sẽ không phải thông qua một hàm nào để xử lý dữ liệu. Cú pháp như sau:
dataframe[điều kiện lọc, vị trí các cột]
Trong đó điều kiện lọc luôn để ở đầu, sau điều kiện lọc là danh sách các cột
rm(list = ls())
#1. Chỉ lấy những kiểu Species là "Setosa"
subIris <- iris[iris$Species=="setosa",]
#2. Kiểm tra các trường của subIris
head(subIris)
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1 5.1 3.5 1.4 0.2 setosa
## 2 4.9 3.0 1.4 0.2 setosa
## 3 4.7 3.2 1.3 0.2 setosa
## 4 4.6 3.1 1.5 0.2 setosa
## 5 5.0 3.6 1.4 0.2 setosa
## 6 5.4 3.9 1.7 0.4 setosa
#3. Kiểm tra species sau khi lọc theo điều kiện
subIris %>% distinct(Species)
## Species
## 1 setosa
#4. Nếu chỉ muốn lấy một số cột nhất định thì truyền vào vị trí cột sau điều kiện
head(iris[iris$Species=="setosa",1:3])
## Sepal.Length Sepal.Width Petal.Length
## 1 5.1 3.5 1.4
## 2 4.9 3.0 1.4
## 3 4.7 3.2 1.3
## 4 4.6 3.1 1.5
## 5 5.0 3.6 1.4
## 6 5.4 3.9 1.7
Trường hợp có từ 2 điều kiện trở lên thì sử dụng các hàm logic như and , or và các nhân tử logic & , | để truy xuất dữ liệu.
rm(list = ls())
#1. lấy những kiểu Species là "Setosa" có Sepal.Length > 5
subIris <- iris[and(iris$Species=="setosa", iris$Sepal.Length > 5),]
#2. Kiểm tra subIris
#cac loai cua subIris
distinct(subIris,Species)
## Species
## 1 setosa
#min cua Sepal.Length
min(subIris$Sepal.Length)
## [1] 5.1
Cách làm trên là lọc theo điều kiện của trường. Trong trường hợp ta muốn lấy theo điều kiện của dòng chẳng hạn từ dòng 10-20 của dataframe thì sử dụng công thức trích xuất phần tử của dataframe:
rm(list = all())
iris[10:20,]
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 10 4.9 3.1 1.5 0.1 setosa
## 11 5.4 3.7 1.5 0.2 setosa
## 12 4.8 3.4 1.6 0.2 setosa
## 13 4.8 3.0 1.4 0.1 setosa
## 14 4.3 3.0 1.1 0.1 setosa
## 15 5.8 4.0 1.2 0.2 setosa
## 16 5.7 4.4 1.5 0.4 setosa
## 17 5.4 3.9 1.3 0.4 setosa
## 18 5.1 3.5 1.4 0.3 setosa
## 19 5.7 3.8 1.7 0.3 setosa
## 20 5.1 3.8 1.5 0.3 setosa
Các xử lý dữ liệu này đơn giản với người bắt đầu nhưng nhược điểm là công thức dài và phải sử dụng lại tên bảng nhiều lần. Chúng ta đi sang cách thứ 2: sử dụng dụng hàm filter.
Hàm filter là một hàm cơ bản của package dplyr (phân biệt với hàm Filter của base). Được sử dụng để lọc bảng theo điều kiện. Cú pháp của filter:
filter(data.frame, điều kiện)
rm(list = ls())
#1. Trước tiên phải load dplyr
library(dplyr)
#2. lấy những kiểu Species là "Setosa" có Sepal.Length > 5
subIris <- filter(iris,Sepal.Length > 5)
#3. Kiểm tra điều kiện
#min cua Sepal.Length
min(subIris$Sepal.Length)
## [1] 5.1
Trong trường hợp có nhiều hơn 1 điều kiện thì chúng ta sử dụng hàm and, or hoặc các nhân tử logic &, | để xử lý mẫu:
rm(list = ls())
#1. Trước tiên phải load dplyr
library(dplyr)
#2. lấy những kiểu Species là "Setosa" có Sepal.Length > 5
subIris <- filter(iris,and(Species=="setosa", Sepal.Length > 5))
#hoặc cũng có thể viết
subIris <- filter(iris,Species=="setosa" & Sepal.Length > 5)
#3. Kiểm tra điều kiện
#cac loai species
distinct(subIris,Species)
## Species
## 1 setosa
#min cua Sepal.Length
min(subIris$Sepal.Length)
## [1] 5.1
Hàm subset có công thức và chức năng tương tự như filter tuy nhiên subset là một function trong base nên không cần phải load package. Cú pháp của subset:
subset(data.frame, điều kiện)
rm(list = ls())
#1. lấy những kiểu Species là "Setosa" có Sepal.Length > 5
subIris <- subset(iris,and(Species=="setosa", Sepal.Length > 5))
#2. Kiểm tra điều kiện
#Load dplyr để sử dụng hàm distinct
library(dplyr)
#cac loai species
distinct(subIris,Species)
## Species
## 1 setosa
#min cua Sepal.Length
min(subIris$Sepal.Length)
## [1] 5.1
SQL là ngôn ngữ truy vấn dữ liệu có cấu trúc mạnh và được sử dụng phổ biến trong quản lý bể dữ liệu của các doanh nghiệp. Đặc điểm của ngôn ngữ này là có tính hệ thống, các bảng được xác định trước định dạng trường cột và mối liên hệ giữa chúng. SQL phù hợp với các tập dữ liệu ổn định, không thay đổi cấu trúc thường xuyên nên trong tài chính, ngân hàng chúng được sử dụng phổ biến. Vì là ngôn ngữ đặc thù thiết kế chuyên cho truy vấn và quản trị dữ liệu nên câu lệnh của SQL khá linh hoạt trong biến đổi dữ liệu. Để kế thừa được các tính năng ưu việt đó trong R có package sqldf.
Nếu máy của bạn chưa có package này bạn cần cài như sau:
install.package(“sqldf”)
Cú pháp của hàm sqldf:
sqldf(‘Câu lệnh sql’)
Thông thường trong sql có một số câu lệnh cơ bản sau:
#1. load package sqldf
library(sqldf)
#2. Xử lý dữ liệu bằng câu lệnh sql
subMtcars <- sqldf('SELECT * FROM mtcars WHERE mpg > 15 AND disp > 150')
#3. Check điều kiện
#Min cua mpg
min(subMtcars$mpg)
## [1] 15.2
#Min cua disp
min(subMtcars$disp)
## [1] 160
Đây là cách thông dụng dành cho beginner. Nội dung của cách làm này là thông qua một phép gán:
trường <- công thức tạo trường
công thức tạo trường có thể là một hàm số, một trường dữ liệu khác,… nhưng kết quả trả về phải là dạng trường. Thông thường ta có thể sử dụng các hàm số sau đây để tạo trường.
Hàm mutate là một thuộc tính của package dplyr. mutate sẽ cho phép tạo ra các biến mới theo điều kiện. Cú pháp của mutate như sau:
mutate(data, công thức tạo trường)
rm(list = ls())
library(dplyr)
iris %<>% mutate(Type = ifelse(Sepal.Length+Sepal.Width > 8,"Height","Small"))
#Kiểm tra điều kiện
#Doi voi height
iris %>% filter(Sepal.Length+Sepal.Width > 8) %>% distinct(Type)
## Type
## 1 Height
#Doi voi small
iris %>% filter(Sepal.Length+Sepal.Width <= 8) %>% distinct(Type)
## Type
## 1 Small
Hàm sapply là một hàm trong base chuyên áp dụng các hàm số lên các dầu vào là vector để trả về đầu ra dạng vector hoặc list. Cú pháp của sapply:
sapply(vector, hàm số, simplify = TRUE)
Tham số simplify dùng để qui định kết quả trả về, nếu TRUE sẽ kết quả sẽ là dạng vector, nếu FALSE kết quả trả về sẽ là dạng list.
#1. Mặc định simplify sẽ là TRUE. Kết quả trả về là một vector
#Ket qua tra ve vector
sapply(1:5,function(x){x^2})
## [1] 1 4 9 16 25
#2. Thiết lập simplify = FALSE để kết quả trả về là một list
#Ket qua tra ve la 1 list
sapply(1:5,function(x){x^2},simplify = FALSE)
## [[1]]
## [1] 1
##
## [[2]]
## [1] 4
##
## [[3]]
## [1] 9
##
## [[4]]
## [1] 16
##
## [[5]]
## [1] 25
#3. Su dung lapply ket qua cung tuong tự
#Ket qua tra ve tu lapply
lapply(1:5,function(x){x^2})
## [[1]]
## [1] 1
##
## [[2]]
## [1] 4
##
## [[3]]
## [1] 9
##
## [[4]]
## [1] 16
##
## [[5]]
## [1] 25
Như vậy muốn sử dụng hàm sapply thì chúng ta phải xác định trước hàm số tạo dữ liệu và các input cho dữ liệu. Chẳng hạn ta muốn tạo Type theo điều kiện của Sepal.Length + Sepal.Width, kết quả trả về sẽ là “Big” hoặc “Small” nếu lớn hơn hoặc nhỏ hơn hoặc bằng 8. Sử dụng hàm mutate như sau:
rm(list = ls())
#1. Tạo hàm số:
fType <- function(x){
ifelse(x > 8,"Big","Small")
}
#2. Sử dụng hàm sapply vector
library(magrittr)
iris$Type <- iris %$% sapply(Sepal.Length+Sepal.Width,fType)
head(iris,10)
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species Type
## 1 5.1 3.5 1.4 0.2 setosa Big
## 2 4.9 3.0 1.4 0.2 setosa Small
## 3 4.7 3.2 1.3 0.2 setosa Small
## 4 4.6 3.1 1.5 0.2 setosa Small
## 5 5.0 3.6 1.4 0.2 setosa Big
## 6 5.4 3.9 1.7 0.4 setosa Big
## 7 4.6 3.4 1.4 0.3 setosa Small
## 8 5.0 3.4 1.5 0.2 setosa Big
## 9 4.4 2.9 1.4 0.2 setosa Small
## 10 4.9 3.1 1.5 0.1 setosa Small
Tương tự ta cũng có thể sử dụng hàm lapply
rm(list = ls())
#1. Tạo hàm số:
fType <- function(x){
ifelse(x > 8,"Height","Small")
}
#2. Sử dụng hàm sapply vector
library(magrittr)
iris$Type <- iris %$% unlist(lapply(Sepal.Length+Sepal.Width,fType))
head(iris,10)
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species Type
## 1 5.1 3.5 1.4 0.2 setosa Height
## 2 4.9 3.0 1.4 0.2 setosa Small
## 3 4.7 3.2 1.3 0.2 setosa Small
## 4 4.6 3.1 1.5 0.2 setosa Small
## 5 5.0 3.6 1.4 0.2 setosa Height
## 6 5.4 3.9 1.7 0.4 setosa Height
## 7 4.6 3.4 1.4 0.3 setosa Small
## 8 5.0 3.4 1.5 0.2 setosa Height
## 9 4.4 2.9 1.4 0.2 setosa Small
## 10 4.9 3.1 1.5 0.1 setosa Small
Trong biểu thức trên có sử dụng hàm unlist bên ngoài lapply với mục đích convert dữ liệu trả về dạng list sang vector.
Rõ ràng trong 2 cách sử dụng trên thì cách sử dụng hàm mutate có cú pháp ngắn gọn, dễ hiểu hơn mặc dù tốc độ xử lý không khác nhau đáng kể. Chính vì thế cho đến thời điểm hiện tại thì mutate vẫn được coi là phương pháp phổ biến nhất để tạo trường mới.
Qua bài học này chúng ta đã tổng hợp cho mình những kiến thức sau:
Trên đây chỉ là những tổng hợp cơ bản nhất, thực tế có thể còn những cách khác để xử lý dữ liệu và bài viết sẽ còn tiếp tục hoàn thiện ở những tái bản sau.