Biên soạn: Duc Nguyen | Chuyên đào
tạo kỹ năng xử lý dữ liệu sử dụng phần mềm R | Website:
www.tuhocr.com
Bạn xây dựng function filter theo chỉ tiêu ở các file
.csv như sau. Ý
tưởng ban đầu là function này (click here)
Download dataset example 2007.rar
gồm 10 file .csv, đồng nhất cấu trúc
mercury-nitrite-id.
Download dataset example 2008.rar
gồm 8 file .csv, không đồng nhất cấu trúc: khi file 2, 6, 8
có thêm cột COD.
## FUNCTION_MIN_CHI_TIEU
"%min_chi_tieu%" <- function(a, b) {
# import dataset
files_list <- list.files(b, full.names = TRUE)
tmp_working <- vector(mode = "list", length = length(files_list))
for (i in seq_along(files_list)) {
tmp_working[[i]] <- read.csv(files_list[i])
tmp_working[[i]]$Date <- as.Date(tmp_working[[i]]$Date, format = "%m/%d/%Y")
}
# find min value
ket_qua_min <- data.frame()
for (min_i in seq_along(tmp_working)) {
gia_tri_min <- min(get(chi_tieu, tmp_working[[min_i]]), na.rm = TRUE)
### trích xuất ra vị trí index
vi_tri <- which(get(chi_tieu, tmp_working[[min_i]]) == gia_tri_min)
ket_qua_min <- rbind(ket_qua_min, tmp_working[[min_i]][vi_tri, ])
}
print(paste("Kết quả lọc theo giá trị MIN của", chi_tieu, "là:"))
return(ket_qua_min)
}
## FUNCTION_MAX_CHI_TIEU
"%max_chi_tieu%" <- function(a, b) {
# import dataset
files_list <- list.files(b, full.names = TRUE)
tmp_working <- vector(mode = "list", length = length(files_list))
for (i in seq_along(files_list)) {
tmp_working[[i]] <- read.csv(files_list[i])
tmp_working[[i]]$Date <- as.Date(tmp_working[[i]]$Date, format = "%m/%d/%Y")
}
# find max value
ket_qua_max <- data.frame()
for (max_i in seq_along(tmp_working)) {
gia_tri_max <- max(get(chi_tieu, tmp_working[[max_i]]), na.rm = TRUE)
### trích xuất ra vị trí index
vi_tri <- which(get(chi_tieu, tmp_working[[max_i]]) == gia_tri_max)
ket_qua_max <- rbind(ket_qua_max, tmp_working[[max_i]][vi_tri, ])
}
print(paste("Kết quả lọc theo giá trị MAX của", chi_tieu, "là:"))
return(ket_qua_max)
}
## FUNCTION RANGE CHỈ TIÊU
"%range_chi_tieu%" <- function(a, b) {
# import dataset
files_list <- list.files(b, full.names = TRUE)
tmp_working <- vector(mode = "list", length = length(files_list))
for (i in seq_along(files_list)) {
tmp_working[[i]] <- read.csv(files_list[i])
tmp_working[[i]]$Date <- as.Date(tmp_working[[i]]$Date, format = "%m/%d/%Y")
}
# tìm trong khoảng
ket_qua_range <- data.frame()
for (range_i in seq_along(tmp_working)) {
ket_qua_tim_trong_khoang <- which(get(chi_tieu, tmp_working[[range_i]]) >= a[1] &
get(chi_tieu, tmp_working[[range_i]]) <= a[2])
ket_qua_range <- rbind(ket_qua_range, tmp_working[[range_i]][ket_qua_tim_trong_khoang, ])
}
print(paste("Kết quả lọc trong khoảng, từ", a[1], "đến", a[2], "của", chi_tieu, "là:"))
return(ket_qua_range)
}
## FUNCTION GHÉP CHUNG
"%chi_tieu%" <- function(a, b) {
if (length(a) == 1) {
switch(a,
"min" = a %min_chi_tieu% b,
"max" = a %max_chi_tieu% b
)
} else {
a %range_chi_tieu% b
}
}
## FUNCTION FINAL
filter_theo_chi_tieu <- function(chi_tieu = "mercury", a = "min", b = "2007") {
chi_tieu <<- chi_tieu ### sử dụng super assign
a %chi_tieu% b
}
Test OK với các
file trong folder 2007 vì nó cùng cấu trúc.
filter_theo_chi_tieu(chi_tieu = "mercury", a = "max", b = "2007")
## [1] "Kết quả lọc theo giá trị MAX của mercury là:"
## Date mercury nitrite ID
## 987 2005-09-13 19.10 0.149 1
## 2437 2007-09-03 27.90 1.320 2
## 1717 2005-09-13 22.50 0.133 3
## 17171 2005-09-13 23.60 0.347 4
## 9871 2005-09-13 20.40 0.887 5
## 1004 2004-09-30 12.80 0.241 6
## 1352 2005-09-13 19.80 0.432 7
## 13521 2005-09-13 20.20 0.496 8
## 256 2005-09-13 16.20 0.452 9
## 497 2003-05-12 2.27 0.367 10
Test không OK ở các
file trong folder 2008. Lệnh rbind báo lỗi
ghép không khớp số cột ở các data frame.
filter_theo_chi_tieu(chi_tieu = "mercury", a = "max", b = "2008")
## Error in rbind(deparse.level, ...): numbers of columns of arguments do not match
Function ban đầu hoạt động tốt với các file .csv trong
folder 2007 có cùng số cột như sau nên khi ghép lại bằng
lệnh rbind thì ổn.
water_id1_2007 <- read.csv("2007/water001.csv")
head(water_id1_2007)
## Date mercury nitrite ID
## 1 1/1/2003 NA NA 1
## 2 1/2/2003 NA NA 1
## 3 1/3/2003 NA NA 1
## 4 1/4/2003 NA NA 1
## 5 1/5/2003 NA NA 1
## 6 1/6/2003 NA NA 1
water_id2_2007 <- read.csv("2007/water002.csv")
head(water_id2_2007)
## Date mercury nitrite ID
## 1 1/1/2001 NA NA 2
## 2 1/2/2001 NA NA 2
## 3 1/3/2001 NA NA 2
## 4 1/4/2001 NA NA 2
## 5 1/5/2001 NA NA 2
## 6 1/6/2001 NA NA 2
Tuy nhiên nếu có 1 vài file, ở đây ví dụ là
file water002.csv trong folder 2008 thì có
chèn thêm 1 cột mới, là cột COD, dẫn đến cấu trúc file khác
với các file .csv còn lại. Do đó, để function
rbind() nhận biết được ghép các data frame khác cột khi
thực hiện lệnh for loop thì cần dùng lệnh
bind_rows() trong package dplyr thay thế, vì
lệnh này có khả năng ghép theo hàng, các data frame có số cột không đồng
nhất.
water_id1_2008 <- read.csv("2008/water001.csv")
head(water_id1_2008)
## Date mercury nitrite ID
## 1 1/1/2003 NA NA 1
## 2 1/2/2003 NA NA 1
## 3 1/3/2003 NA NA 1
## 4 1/4/2003 NA NA 1
## 5 1/5/2003 NA NA 1
## 6 1/6/2003 NA NA 1
water_id2_2008 <- read.csv("2008/water002.csv")
head(water_id2_2008)
## Xuất hiện cột mới ở đây
## ↓
## Date mercury COD nitrite ID
## 1 1/1/2001 NA 0.699 NA 2
## 2 1/2/2001 NA NA NA 2
## 3 1/3/2001 NA NA NA 2
## 4 1/4/2001 NA NA NA 2
## 5 1/5/2001 NA NA NA 2
## 6 1/6/2001 NA NA NA 2
Function ban đầu có tên là
filter_theo_chi_tieu().
Function cải tiến lần 1, có tên là
filter_theo_chi_tieu_enhanced() tăng khả
năng filter các dataset khác số lượng cột. Những chỗ highlight là được
cải tiến so với function ban đầu.
library(dplyr) # Load package này để dùng lệnh bind_rows() thay lệnh rbind()
## FUNCTION_MIN_CHI_TIEU
"%min_chi_tieu%" <- function(a, b) {
# import dataset
files_list <- list.files(b, full.names = TRUE)
tmp_working <- vector(mode = "list", length = length(files_list))
for (i in seq_along(files_list)) {
tmp_working[[i]] <- read.csv(files_list[i])
tmp_working[[i]]$Date <- as.Date(tmp_working[[i]]$Date, format = "%m/%d/%Y")
}
# find min value
ket_qua_min <- data.frame()
for (min_i in seq_along(tmp_working)) {
if (chi_tieu %in% names(tmp_working[[min_i]])) { # Đưa lệnh `if` để check tên cột, nếu không trùng thì `next`
gia_tri_min <- min(get(chi_tieu, tmp_working[[min_i]]), na.rm = TRUE)
### trích xuất ra vị trí index
vi_tri <- which(get(chi_tieu, tmp_working[[min_i]]) == gia_tri_min)
ket_qua_min <- bind_rows(ket_qua_min, tmp_working[[min_i]][vi_tri, ])
}
next
}
#### THÊM DÒNG THÔNG BÁO
print(paste("Kết quả lọc theo giá trị MIN của", chi_tieu, "ở folder", b, "là:"))
#### TRẢ KẾT QUẢ
return(ket_qua_min)
}
## FUNCTION_MAX_CHI_TIEU
"%max_chi_tieu%" <- function(a, b) {
# import dataset
files_list <- list.files(b, full.names = TRUE)
tmp_working <- vector(mode = "list", length = length(files_list))
for (i in seq_along(files_list)) {
tmp_working[[i]] <- read.csv(files_list[i])
tmp_working[[i]]$Date <- as.Date(tmp_working[[i]]$Date, format = "%m/%d/%Y")
}
# find max value
ket_qua_max <- data.frame()
for (max_i in seq_along(tmp_working)) {
if (chi_tieu %in% names(tmp_working[[max_i]])) {
gia_tri_max <- max(get(chi_tieu, tmp_working[[max_i]]), na.rm = TRUE)
### trích xuất ra vị trí index
vi_tri <- which(get(chi_tieu, tmp_working[[max_i]]) == gia_tri_max)
ket_qua_max <- bind_rows(ket_qua_max, tmp_working[[max_i]][vi_tri, ])
}
next
}
print(paste("Kết quả lọc theo giá trị MAX của", chi_tieu, "ở folder", b, "là:"))
return(ket_qua_max)
}
## FUNCTION RANGE CHỈ TIÊU
"%range_chi_tieu%" <- function(a, b) {
# import dataset
files_list <- list.files(b, full.names = TRUE)
tmp_working <- vector(mode = "list", length = length(files_list))
for (i in seq_along(files_list)) {
tmp_working[[i]] <- read.csv(files_list[i])
tmp_working[[i]]$Date <- as.Date(tmp_working[[i]]$Date, format = "%m/%d/%Y")
}
# tìm trong khoảng
ket_qua_range <- data.frame()
for (range_i in seq_along(tmp_working)) {
if (chi_tieu %in% names(tmp_working[[range_i]])) {
ket_qua_tim_trong_khoang <- which(get(chi_tieu, tmp_working[[range_i]]) >= a[1] &
get(chi_tieu, tmp_working[[range_i]]) <= a[2])
ket_qua_range <- bind_rows(ket_qua_range, tmp_working[[range_i]][ket_qua_tim_trong_khoang, ])
}
next
}
print(paste("Kết quả lọc trong khoảng, từ", a[1], "đến", a[2], "của", chi_tieu, "ở folder", b, "là:"))
return(ket_qua_range)
}
## FUNCTION GHÉP CHUNG
"%chi_tieu%" <- function(a, b) {
if (length(a) == 1) {
switch(a,
"min" = a %min_chi_tieu% b,
"max" = a %max_chi_tieu% b
)
} else {
a %range_chi_tieu% b
}
}
## FUNCTION FINAL
filter_theo_chi_tieu_enhanced <- function(chi_tieu = "mercury", a = "min", b = "2007") {
chi_tieu <<- chi_tieu ### sử dụng super assign
a %chi_tieu% b
}
Trường hợp 1: Khi filter theo chỉ tiêu
nitrite đều có trong các file .csv thì những
file nào có cột COD sẽ thể hiện NA (missing
value) thay thế ở dataset kết quả xuất ra.
filter_theo_chi_tieu_enhanced("nitrite", c(0.89, 0.9), "2007")
## [1] "Kết quả lọc trong khoảng, từ 0.89 đến 0.9 của nitrite ở folder 2007 là:"
## Date mercury nitrite ID
## 1 2005-02-15 3.61 0.895 2
## 2 2005-04-22 3.54 0.896 2
## 3 2010-02-25 2.40 0.894 2
## 4 2002-05-08 7.12 0.898 4
## 5 2003-05-21 5.16 0.898 4
## 6 2005-10-19 6.48 0.892 4
## 7 2007-12-08 6.71 0.891 4
## 8 2007-04-24 4.66 0.896 5
## 9 2008-04-18 3.07 0.890 5
## 10 2008-11-02 4.44 0.893 5
## 11 2003-12-05 3.94 0.896 6
## 12 2006-01-05 2.08 0.895 6
## 13 2002-02-13 2.64 0.896 7
## 14 2002-03-15 4.59 0.899 7
## 15 2005-01-10 3.78 0.896 7
## 16 2003-04-09 4.43 0.900 8
filter_theo_chi_tieu_enhanced("nitrite", c(0.89, 0.9), "2008")
## [1] "Kết quả lọc trong khoảng, từ 0.89 đến 0.9 của nitrite ở folder 2008 là:"
## Date mercury nitrite ID COD
## 1 2005-02-15 3.61 0.895 2 2.23
## 2 2005-04-22 3.54 0.896 2 NA
## 3 2002-05-08 7.12 0.898 4 NA
## 4 2003-05-21 5.16 0.898 4 NA
## 5 2005-10-19 6.48 0.892 4 NA
## 6 2007-12-08 6.71 0.891 4 NA
## 7 2007-04-24 4.66 0.896 5 NA
## 8 2008-04-18 3.07 0.890 5 NA
## 9 2008-11-02 4.44 0.893 5 NA
## 10 2003-12-05 3.94 0.896 6 NA
## 11 2006-01-05 2.08 0.895 6 NA
## 12 2002-02-13 2.64 0.896 7 NA
## 13 2002-03-15 4.59 0.899 7 NA
## 14 2005-01-10 3.78 0.896 7 NA
## 15 2003-04-09 4.43 0.900 8 1.51
Trường hợp 2: Khi filter theo chỉ tiêu
COD chỉ có ở một số file .csv trong folder
2008, còn folder 2007 không có. Kết quả trả về
là các file chỉ chứa cột COD rất gọn.
filter_theo_chi_tieu_enhanced("COD", c(1.7, 1.9), "2007")
## [1] "Kết quả lọc trong khoảng, từ 1.7 đến 1.9 của COD ở folder 2007 là:"
## data frame with 0 columns and 0 rows
filter_theo_chi_tieu_enhanced("COD", c(1.7, 1.9), "2008")
## [1] "Kết quả lọc trong khoảng, từ 1.7 đến 1.9 của COD ở folder 2008 là:"
## Date mercury COD nitrite ID
## 1 2001-02-18 2.05 1.79 3.570 2
## 2 2001-09-16 8.55 1.79 0.803 2
## 3 2002-02-13 1.90 1.79 1.510 2
## 4 2002-07-13 4.00 1.79 0.563 2
## 5 2003-03-21 NA 1.79 NA 2
## 6 2003-11-17 4.35 1.79 0.987 2
## 7 2004-07-15 NA 1.79 NA 2
## 8 2004-12-04 NA 1.73 NA 2
## 9 2004-12-13 NA 1.82 NA 2
## 10 2005-01-08 NA 1.79 NA 2
## 11 2005-06-08 NA 1.79 NA 2
## 12 2005-11-06 4.69 1.79 0.474 2
## 13 2006-04-06 NA 1.79 NA 2
## 14 2002-07-04 NA 1.79 NA 6
## 15 2003-03-02 NA 1.79 NA 6
## 16 2003-07-22 NA 1.73 NA 6
## 17 2003-07-31 NA 1.82 NA 6
## 18 2003-08-26 NA 1.79 NA 6
## 19 2004-01-24 NA 1.79 NA 6
## 20 2004-06-23 NA 1.79 NA 6
## 21 2004-11-21 NA 1.79 NA 6
## 22 2004-02-23 NA 1.70 NA 8
## 23 2004-04-05 NA 1.73 NA 8
## 24 2005-06-11 NA 1.72 NA 8
filter_theo_chi_tieu_enhanced() hoạt
động OK. Tuy nhiên kết quả xuất ra thì vị trí các cột không theo thứ tự
như mong muốn. Có thể sẽ gây nhầm lẫn. Vì vậy chúng ta cải tiến hàm này
để làm sao kết quả xuất ra giữ cột Date và ID
ở đầu và cuối, còn ở giữa thì các chỉ tiêu sẽ được xếp theo
alphabet.
Function cải tiến lần 2, có tên là
filter_theo_chi_tieu_super() giúp xử lý
dataset kết quả thu được “user-friendly” hơn. Cách tiếp cận là ta sẽ
lồng ghép/nested
filter_theo_chi_tieu_enhanced() vào trong
function sau cùng.
filter_theo_chi_tieu_super <- function(chi_tieu = "mercury", a = "min", b = "2007") {
# Logic là: lấy kết quả từ function enhanced để sắp xếp thứ tự cột
data_1 <- filter_theo_chi_tieu_enhanced(chi_tieu, a, b)
if (length(data_1) != 0) {
kq_sort <- sort(names(data_1)[!(names(data_1) %in% c("Date", "ID"))])
ok <- c("Date", kq_sort, "ID")
data_2 <- data_1[ok]
return(data_2)
}
return("Không có kết quả tìm kiếm") # Nếu không có kết quả thì in thông báo rõ ràng
}
filter_theo_chi_tieu_super(, ,) # test với default value (folder "2007", min của mercury)
## [1] "Kết quả lọc theo giá trị MIN của mercury ở folder 2007 là:"
## Date mercury nitrite ID
## 1 2005-12-12 0.6130 0.3260 1
## 2 2006-09-29 0.0000 0.0000 2
## 3 2005-12-12 0.7130 0.2940 3
## 4 2009-07-12 0.0283 0.0199 4
## 5 2008-11-08 0.5160 0.4210 5
## 6 2003-11-29 0.5210 0.2240 6
## 7 2008-11-26 0.6450 1.2500 7
## 8 2003-01-15 1.0900 2.8500 8
## 9 2009-04-01 0.4130 0.3910 9
## 10 2004-04-27 0.1170 0.0636 10
filter_theo_chi_tieu_super(, , "2008") # tương tự, nhưng là folder "2008
## [1] "Kết quả lọc theo giá trị MIN của mercury ở folder 2008 là:"
## Date COD mercury nitrite ID
## 1 2005-12-12 NA 0.6130 0.3260 1
## 2 2006-09-29 0.772 0.0000 0.0000 2
## 3 2005-12-12 NA 0.7130 0.2940 3
## 4 2009-07-12 NA 0.0283 0.0199 4
## 5 2008-11-08 NA 0.5160 0.4210 5
## 6 2003-11-29 NA 0.5210 0.2240 6
## 7 2008-11-26 NA 0.6450 1.2500 7
## 8 2003-01-15 NA 1.0900 2.8500 8
filter_theo_chi_tieu_super("COD", "max", "2007")
## [1] "Kết quả lọc theo giá trị MAX của COD ở folder 2007 là:"
## [1] "Không có kết quả tìm kiếm"
filter_theo_chi_tieu_super("COD", "max", "2008")
## [1] "Kết quả lọc theo giá trị MAX của COD ở folder 2008 là:"
## Date COD mercury nitrite ID
## 1 2004-10-26 11.2 NA NA 2
## 2 2005-04-21 11.2 NA NA 2
## 3 2005-09-19 11.2 9.69 0.607 2
## 4 2006-02-17 11.2 NA NA 2
## 5 2006-07-18 11.2 NA NA 2
## 6 2006-12-18 11.2 NA NA 2
## 7 2007-05-05 11.2 NA NA 2
## 8 2007-09-20 11.2 NA NA 2
## 9 2008-02-05 11.2 NA NA 2
## 10 2008-06-22 11.2 NA NA 2
## 11 2008-11-07 11.2 NA NA 2
## 12 2009-03-25 11.2 NA NA 2
## 13 2009-08-10 11.2 NA NA 2
## 14 2003-06-13 11.2 NA NA 6
## 15 2003-12-07 11.2 NA NA 6
## 16 2004-05-06 11.2 NA NA 6
## 17 2004-10-04 11.2 NA NA 6
## 18 2005-03-04 11.2 NA NA 6
## 19 2005-08-04 11.2 NA NA 6
## 20 2005-12-20 11.2 NA NA 6
## 21 2006-05-07 11.2 NA NA 6
## 22 2006-09-22 11.2 NA NA 6
## 23 2005-10-26 11.8 NA NA 8
Như vậy sau 2 cải tiến thì filter_theo_chi_tieu_super()
đã hoàn chỉnh. Khi có nhu cầu bổ sung thêm các chức năng khác thì chúng
ta cũng thêm dòng code tương tự như vậy. Việc xây dựng function trong R
không khó, chỉ là chúng ta cần có tư duy hệ thống và bình tĩnh thử và
sai một chút để hiểu quy luật trong R.
Nhiều trường hợp chạy function bị lỗi vì bị thiếu dấu ()
hay {} đóng mở ngoặc, vì vậy cần nắm chắc logic viết
function để đảm bảo bạn viết code đến đâu là khi test hàm sẽ đúng đến
đó.
Trên đây là ví dụ minh họa cho bài giảng Cách xây dựng hàm số trong khóa học “HDSD R để xử lý dữ liệu | Chuyên đề Coding in R”, sau 20 giờ học, bạn sẽ có nền tảng vững chắc về R căn bản và cách xây dựng function cho riêng mình!
Nội dung khóa học:
www.tuhocr.com
Hành trình ngàn dặm bắt đầu từ bước chân đầu tiên.
ĐĂNG KÝ NGAY:
https://www.tuhocr.com/register