Trong thời đại hiện nay, phân tích dữ liệu đóng vai trò vô cùng quan
trọng và mang lại nhiều lợi ích trong nhiều lĩnh vực khác nhau. Các công
việc như là xử lý dữ liệu cũng như phân tích văn bản ngày càng phổ biến
và đòi hỏi sự tỉ mỉ hơn. Trong lĩnh vực xử lý và phân tích dữ liệu,
người dùng thường đối mặt với nhiều thách thức và khó khăn trong việc
làm việc với dữ liệu lớn . Các công việc như nhập dữ liệu, xử lý dữ liệu
thường đòi hỏi nhiều công đoạn phức tạp và mất thời gian. Hiện nay, hầu
hết các lĩnh vực từ công ty đến các doanh nghiệp lớn nhỏ đều sử dụng
phân tích dữ liệu. Đối với các công ty và doanh nghiệp hoạt động trong
lĩnh vực kinh doanh, phân tích dữ liệu giúp họ hiểu hơn về khách hàng
của mình. Thông thường, hướng phân tích dữ liệu thuận tiện và nhanh gọn
mà mọi người tới là các ứng dụng phân tích dữ liệu trong thực tiễn như
Excel, BI Tools, FineReport, R và Python,…Đặc biệt là trong các ngành
liên quan tới tài chính, ngành mà tập hợp hầu hết những kiểu dữ liệu có
thể phân tích thông qua ngôn ngữ lập trình R hiện nay. Để giải quyết
những thách thức này, thì có rất nhiều công cụ hoàn toàn giúp ta có thể
giải quyết được một cách nhanh gọn và tiết kiệm thời gian hiệu quả.
Trong bài viết này, chúng em sẽ giới thiệu về Purrr, một
công cụ giúp chúng ta có thể dễ dàng phân tích và xử lý dữ liệu.
Gói Purrr được phát triển bởi Hadley Wickham, một nhà
phân tích dữ liệu và nhà phát triển phần mềm nổi tiếng trong cộng đồng
R. Hadley Wickham cũng là tác giả của nhiều gói R khác như “dplyr”,
“ggplot2” và “tidyr”, được sử dụng rộng rãi trong ngành phân tích dữ
liệu.
Việc tạo ra gói Purrr nhằm mục đích đơn giản hóa và tăng cường khả năng xử lý dữ liệu trong R, đặc biệt là khi làm việc với các phép toán lặp lại trên các cấu trúc dữ liệu.
Package Purrr trong ngôn ngữ lập trình R là một gói mở
rộng mạnh mẽ của ngôn ngữ, được sử dụng để làm việc với các cấu trúc dữ
liệu dạng vector, danh sách và các loại dữ liệu khác trong R. Gói này
giúp cải thiện tính linh hoạt và hiệu suất khi thực hiện các phép toán
lặp lại trên các phần tử của các cấu trúc dữ liệu đó.
Package purrr là 1 package rất mạnh, thiết kế rất thông
minh, làm cho việc viết code trở lên đơn giản, dễ hiểu. Package Purrr
này cung cấp một loạt các hàm như map(), map2(), pmap(), walk(),
reduce(),… để thực hiện các phép toán lặp lại trên các phần tử của các
vector, danh sách hoặc bất kỳ cấu trúc dữ liệu nào khác. Một hàm được sử
dụng phổ biến trong package Purrr này là hàm map(), sự ra
đời của nó đã thay đổi hoàn toàn cách thức viết vòng lặp trong R. Vốn
trước kia viết vòng lặp dựa trên 2 nhóm công cụ chính là các vòng lặp cổ
điển (for, while, …) hoặc những hàm apply (sapply, lapply…), nhưng bây
giờ hàm map() cho phép bạn thay thế nhiều vòng lặp for bằng mã ngắn gọn
hơn và dễ đọc hơn.
1.Hàm map()
map(.x, ~function(.x,…))
Mỗi bước trên lộ trình của vòng lặp tương ứng với một điều kiện (.x), được dùng như 1 tham số/dữ liệu trong hàm f . Tùy bản chất của dữ liệu đầu vào (thường là 1 list), (.x) có thể là vector, matrix, dataframe, hay 1 con số … , và đóng vai trò dữ liệu đầu vào, tùy chỉnh, tham số để cá biệt hóa nội dung hàm f.
Có nhiều hàm map trong package Purrr, mỗi hàm có công
dụng khác nhau và xuất kết quả khác nhau. Ví dụ : hàm map() mặc định
xuất kết quả là 1 danh sách, hàm map_df() trả về một khung dữ liệu bằng
cách liên kết hàng các phần tử riêng lẻ, hàm map_dbl() hay map_int()
xuất kết quả là 1 vector số thực hay số nguyên …
Các họ hàm map_*().
Các hàm họ map_*() sử dụng cho vòng lặp (loop). Được thiết kế rất tiện và loại bỏ những thao tác thừa đối với việc sử dụng vòng lặp for, và đầu ra xác định rõ ràng hơn so với vòng lặp while. Cụ thể như sau:
Đối với vòng lặp for ta phải xác định trước số phần tử của object (nếu không muốn giảm hiệu năng của hàm). Còn đối với hàm map thì không cần làm công việc như vậy mà không bị ảnh hướng tới hiệu năng.
Thiết kế phù hợp để thực hiện theo chu trình (pipe %>%)
Các hàm họ map_*():
map(): tạo thành một danh sách.
map_lgl(): tạo ra một vectơ logic.
map_int(): tạo thành một vectơ số nguyên.
map_dbl(): tạo thành một vectơ kép
map_chr(): tạo thành một vectơ ký tự.
2.Hàm pmap(): là một hàm trong gói “purrr” của ngôn ngữ lập trình R. Hàm này được sử dụng để áp dụng một hàm lên các phần tử từ nhiều danh sách hoặc vector cùng một lúc. Nó giúp thực hiện các phép toán song song trên các cấu trúc dữ liệu đó.
pmap(.l, .f, ...)
Hàm pmap() rất hữu ích khi bạn muốn áp dụng một hàm lên các phần tử từ nhiều danh sách hoặc vector cùng một lúc và thu thập kết quả thành một danh sách.
3.Hàm walk(): là một hàm trong gói “purrr” của ngôn ngữ lập trình R. Hàm walk() rất hữu ích khi bạn muốn thực hiện một hành động trên các phần tử của một cấu trúc dữ liệu mà không cần thu thập kết quả trả về từ mỗi phần tử đó. Thay vào đó, nó cho phép bạn thực hiện một hành động “đi qua” các phần tử một cách tuần tự.
walk(.x, .f, ...)
4.Hàm reduce(): là một hàm trong gói “purrr” của ngôn ngữ lập trình R. Hàm này được sử dụng để áp dụng một phép tổ hợp lên các phần tử của một vector hoặc danh sách để thu được một kết quả duy nhất.
reduce(.x, .f, ...)
5.Hàm flatten(): trong gói “purrr” của R được sử dụng để trích xuất các phần tử từ một danh sách lồng nhau và biến đổi chúng thành một danh sách đơn giản hơn, không còn cấu trúc lồng nhau.
flatten(.x, .simplify = TRUE)
6.Hàm pluck(): trong gói “purrr” của R được sử dụng để trích xuất giá trị từ một danh sách dựa trên các chỉ số hoặc tên.
pluck(.x, ..., .default = NULL)
7.Hàm transpose(): trong gói “purrr” của R được sử dụng để chuyển đổi dữ liệu dạng danh sách lồng nhau thành một danh sách mới với các phần tử được chuyển đổi thành cột và các danh sách con thành các hàng.
transpose(.x, ...)
Để sử dụng package Purrr trên R, ta thực hiệ các bước sau:
Bước 1: Cài đặt package Purrr Chúng ta có thể cài đặt package Purrr bàng cách chạy câu lệnh trên R:
install.packages("Purrr")
Bước 2: Gọi package Purrr bằng câu lệnh trên R
library(purrr)## Warning: package 'purrr' was built under R version 4.3.1
Bước 3: Sau khi gọi package Purrr ta có thể sử dụng các hàm được nói ở trên của package này để xử lí dữ liệu
Mục tiêu chính của package Purrr trong R là cung cấp một
cách tiếp cận mạnh mẽ và linh hoạt để làm việc với dữ liệu dạng danh
sách và vector trong ngôn ngữ lập trình R. Các lợi ích của gói
Purrr bao gồm:
Lập trình dạng hàm: gói Purrr cung cấp
một loạt các hàm như map(), map2(), pmap(),… giúp bạn áp dụng một hàm
lên các phần tử trong danh sách hoặc vector một cách dễ dàng. Điều này
cho phép bạn viết code ngắn gọn, sáng tạo và dễ đọc hơn.
Tự động hóa các thao tác lặp lại: Thay vì viết các
vòng lặp thủ công để thực hiện các thao tác trên từng phần tử, gói
Purrr cho phép bạn áp dụng các hàm một cách tự động trên
toàn bộ danh sách hoặc vector một cách hiệu quả. Điều này giúp giảm đáng
kể lượng mã lặp lại và tăng hiệu suất của code.
Xử lý dữ liệu dạng danh sách: Khi làm việc với dữ
liệu phức tạp, thường có nhiều danh sách lồng nhau hoặc dữ liệu có cấu
trúc không đồng nhất. Gói Purrr cung cấp các hàm như
flatten(), pluck(), transpose() để trích xuất, biến đổi và tổ chức lại
dữ liệu dạng danh sách một cách dễ dàng.
Điều khiển quá trình tính toán: gói
Purrr cung cấp các hàm như walk(), invoke_map() cho phép
bạn điều khiển quá trình tính toán và thực thi các tác vụ phức tạp như
tính toán song song.
Tích hợp với các gói dữ liệu phổ biến:
purrr tương thích tốt với nhiều gói dữ liệu phổ biến như
“dplyr”, “tidyr” và “ggplot2”. Bạn có thể kết hợp các hàm từ các gói này
để xử lý dữ liệu một cách linh hoạt và hiệu quả.
Khi tiếp xúc với ngôn ngữ R, chúng em thấy có rất nhiều gói bổ ích hỗ
trợ trong quá trình phân tích dữ liệu. Tuy nhiên chúng em chọn gói
Purrr trong R vì nó là một công cụ mạnh mẽ và linh hoạt để
làm việc với dữ liệu dạng danh sách và vector, giúp chúng em viết code
ngắn gọn, giảm sự lặp lại mã, tăng tính linh hoạt và dễ đọc. Nó cũng
tích hợp tốt với các gói dữ liệu phổ biến và cung cấp các hàm hữu ích
cho xử lý dữ liệu phức tạp và quá trình tính toán phức tạp, điều này
giúp chúng em tìm hiểu thêm được nhiều gói tương thích với gói Purrr có
thể kết hợp các hàm từ các gói này để xử lý dữ liệu một cách linh hoạt
và hiệu quả.
Lặp lại là một cách hiệu quả để máy tính thực hiện công việc cho bạn. Nó cũng có thể là một lĩnh vực mã hóa dễ mắc nhiều lỗi chính tả và lỗi đơn giản. Gói purrr giúp đơn giản hóa việc lặp lại để bạn có thể tập trung vào bước tiếp theo, thay vì tìm lỗi chính tả.
Hãy tưởng tượng rằng bạn cần đọc hàng trăm tệp có cấu trúc tương tự và thực hiện một hành động trên chúng. Bạn không muốn viết hàng trăm dòng mã lặp đi lặp lại để đọc trong tất cả các tệp hoặc để thực hiện hành động. Thay vào đó, bạn muốn lặp lại chúng. Lặp lại là quá trình thực hiện cùng một quy trình cho nhiều đầu vào. Khả năng lặp lại là điều quan trọng để làm cho mã của bạn hiệu quả và mạnh mẽ khi làm việc với danh sách. Các nhà dịch tễ học thường phải phân tích lặp lại trên các phân nhóm như quốc gia, quận hoặc nhóm tuổi. Đây chỉ là một vài trong số rất nhiều tình huống yêu cầu việc lặp lại. Mã hóa các thao tác lặp lại của bạn bằng cách sử dụng các phương pháp bên dưới sẽ giúp bạn thực hiện các tác vụ lặp đi lặp lại như vậy nhanh hơn, giảm khả năng xảy ra lỗi và giảm độ dài code.
Chương này sẽ giới thiệu hai cách tiếp cận đối với các thao tác lặp lại: sử dụng các vòng lặp for và package purrr.
vòng lặp for lặp lại code trên một loạt đầu vào, nhưng ít phổ biến hơn trong R so với các ngôn ngữ lập trình khác. Tuy nhiên, chúng tôi giới thiệu chúng ở đây như một công cụ học tập và tham khảo Package purrr là phương pháp tiếp cận tidyverse đối với các thao tác lặp lại - nó hoạt động bằng cách “maps” (áp dụng) một hàm trên nhiều đầu vào (giá trị, cột, datasets, v.v.) Trong chương này, chúng tôi sẽ lấy một số ví dụ như:
Nhập dữ liệu từ: https://drive.google.com/open?id=1OXW-hpweD6BfwhP2-PfEDNoHmXvptlHd&usp=drive_copy
Tệp chứa các ca bệnh và ca tử vong do dịch Ebola được xác nhận từ tháng 8 năm 2014 đến tháng 3 năm 2016 trên toàn thế giới. Gồm có 4 biến với 2485 quan sát:
Đất nước
Ngày khảo sát
Ca bệnh
Ca tử vong
library(readr)
linelist <- read_csv("D:\\\\ebola_2014_2016_clean.csv")## Rows: 2485 Columns: 4
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): Country
## dbl (2): Cumulative no. of confirmed, probable and suspected cases, Cumulat...
## date (1): Date
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
linelist## # A tibble: 2,485 × 4
## Country Date Cumulative no. of confirmed,…¹ Cumulative no. of co…²
## <chr> <date> <dbl> <dbl>
## 1 Guinea 2014-08-29 648 430
## 2 Nigeria 2014-08-29 19 7
## 3 Sierra Leone 2014-08-29 1026 422
## 4 Liberia 2014-08-29 1378 694
## 5 Sierra Leone 2014-09-05 1261 491
## 6 Nigeria 2014-09-05 22 8
## 7 Liberia 2014-09-05 1871 1089
## 8 Guinea 2014-09-05 812 517
## 9 Senegal 2014-09-05 1 0
## 10 Senegal 2014-09-08 3 0
## # ℹ 2,475 more rows
## # ℹ abbreviated names:
## # ¹`Cumulative no. of confirmed, probable and suspected cases`,
## # ²`Cumulative no. of confirmed, probable and suspected deaths`
names(linelist) <- c('Country','Date','Cases','Deaths')Vòng lặp for không được nhấn mạnh trong R, nhưng phổ biến trong các ngôn ngữ lập trình khác. Khi mới bắt đầu, chúng có thể hữu ích để học và thực hành vì chúng dễ “khám phá”, “gỡ lỗi” hơn và nắm bắt chính xác những gì đang xảy ra cho mỗi lần lặp, đặc biệt là khi bạn chưa cảm thấy thoải mái khi viết các hàm của riêng mình.
Cấu phần cốt lõi Một vòng lặp for có ba phần cốt lõi:
Dưới đây là một ví dụ đơn giản về vòng lặp for.
for (num in c(1,2,3,4,5)) {
print(num + 2)
} ## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7
sau khi thự hiện câu lệnh trên R ta có được kết quả chạy từ 1 đến 5 và cộng thêm 2 của từng phần tử
Chuỗi
Đây là phần “for” của vòng lặp for - các thao tác sẽ chạy “cho (for)” từng phần tử trong chuỗi. Chuỗi có thể là một loạt các giá trị (ví dụ: tên của khu vực pháp lý, bệnh, tên cột, phần tử danh sách, v.v.) hoặc nó có thể là một chuỗi các số liên tiếp (ví dụ: 1,2,3,4,5). Mỗi cách tiếp cận được mô tả dưới đây có các tiện ích riêng của chúng.
Cấu trúc cơ bản của biểu thức chuỗi là item in vector.
Bạn có thể viết bất kỳ ký tự hoặc từ nào thay cho “item” (ví dụ: “i”, “num”, “hosp”, “district”, v.v.). Giá trị của “item” này thay đổi theo từng lần lặp lại của vòng lặp, tiếp tục qua từng giá trị trong vector. Vector có thể là các giá trị ký tự, tên cột hoặc có thể là một chuỗi số - đây là những giá trị sẽ thay đổi theo mỗi lần lặp. Bạn có thể sử dụng chúng trong các thao tác vòng lặp for bằng cách sử dụng thuật ngữ “item”. Ví dụ: chuỗi giá trị ký tự
Trong ví dụ này, một vòng lặp được thực hiện cho mỗi giá trị được xác định trước trong một vector ký tự của tên đất nước
# make vector of the country names
country_names <- unique(linelist$Country)
country_names # print## [1] "Guinea" "Nigeria"
## [3] "Sierra Leone" "Liberia"
## [5] "Senegal" "United States of America"
## [7] "Spain" "Mali"
## [9] "United Kingdom" "Italy"
Chúng tôi đã chọn thuật ngữ hosp để đại diện cho các giá trị từ vector country_names. Đối với lần lặp đầu tiên của vòng lặp, giá trị của hosp sẽ là country_names[[1]]. Đối với vòng lặp thứ hai, nó sẽ là country_names[[2]]. Và cứ như thế…
for (hosp in country_names)
Ví dụ: chuỗi tên cột
Đây là một biến thể của chuỗi ký tự ở trên, trong đó tên của một đối tượng R hiện có được trích xuất và trở thành vector. Ví dụ, tên cột của dataframe. Trong code hoạt động của vòng lặp for, tên cột có thể được sử dụng để lập chỉ mục (tập hợp con) dataframe ban đầu của chúng.
Dưới đây, chuỗi là names() (tên cột) của dataframe linelist. Tên “item” của chúng ta là col, sẽ đại diện cho từng tên cột khi các vòng lặp diễn ra.
Với ví dụ này, chúng tôi bao gồm code thao tác bên trong vòng lặp for, được chạy cho mọi giá trị trong chuỗi. Trong code này, các giá trị trình tự (tên cột) được sử dụng để chỉ mục (tập hợp con) từng phần tử một trong linelist. Như đã dạy trong chương R cơ bản, dấu ngoặc vuông kép [[]] được sử dụng cho tập hợp con. Cột kết quả được chuyển đến is.na(), sau đó đến sum() để tạo ra số giá trị trong cột bị thiếu. Kết quả được in ra console - một số cho mỗi cột.
Một lưu ý về lập chỉ mục với tên cột - bất cứ khi nào tham chiếu đến chính cột đó, đừng chỉ viết “col”! col chỉ đại diện cho tên cột ký tự! Để tham chiếu đến toàn bộ cột, bạn phải sử dụng tên cột dưới dạng chỉ mục trên linelist thông qua linelist[[col]].
for (col in names(linelist)){
print(sum(is.na(linelist[[col]])))
}## [1] 0
## [1] 0
## [1] 8
## [1] 0
Dãy số
Theo cách tiếp cận này, dãy số là một chuỗi các số liên tiếp. Do đó, giá trị của “item” không phải là giá trị ký tự (ví dụ: “Country” hoặc “date”) mà là một số. Điều này rất hữu ích cho việc lặp qua các dataframes, vì bạn có thể sử dụng số “item” bên trong vòng lặp for để lập chỉ mục dataframe theo số hàng.
Ví dụ: giả sử bạn muốn lặp qua mọi hàng trong dataframe của mình và trích xuất thông tin nhất định. “Item” của bạn sẽ là số hàng số. Thông thường, “item” trong trường hợp này được viết là i.
Quá trình vòng lặp for có thể được giải thích bằng lời là “đối với mọi mục trong chuỗi số từ 1 đến tổng số hàng trong dataframe của tôi, hãy thực hiện X”. Đối với lần lặp đầu tiên của vòng lặp, giá trị của “item” i sẽ là 1. Đối với lần lặp thứ hai,i sẽ là 2, v.v.
Đây là hình thức của chuỗi trong code: for (i in 1:nrow(linelist)) {OPERATIONS CODE} trong đó i đại diện cho “item”và 1:nrow(linelist) tạo ra một chuỗi liên tiếp số từ 1 đến số hàng trong linelist.
for (i in 1:nrow(linelist)){} Nếu bạn muốn chuỗi là số, nhưng bạn đang bắt đầu từ một vector (không phải dataframe), hãy sử dụng hàm tắt seq_along() để trả về một dãy số cho mỗi phần tử trong vector. Ví dụ: for (i in seq_along(hospital_names) {OPERATIONS CODE}.
Đoạn code dưới đây thực sự trả về các số, sẽ trở thành giá trị của i trong vòng lặp tương ứng của chúng
seq_along(country_names)## [1] 1 2 3 4 5 6 7 8 9 10
Một lợi thế của việc sử dụng các số trong chuỗi là cũng dễ dàng sử dụng số i để lập chỉ mục vùng chứa lưu trữ các kết quả đầu ra của vòng lặp.
Để kiểm tra vòng lặp của mình, bạn có thể chạy lệnh để gán tạm thời “item”, chẳng hạn như i <- 10 hoặc hosp <- “Country”. Thực hiện việc này bên ngoài vòng lặp và sau đó chỉ chạy code thao tác của bạn (code trong dấu ngoặc nhọn) để xem liệu kết quả mong đợi có được tạo ra hay không.
chúng ta hãy cố gắng vẽ biểu đồ đường cong dịch bệnh cho mỗi đất nước. bằng cách sử dụng package incidence2 như bên dưới:
# Convert the Date column to character vector (if not already)
linelist$Date <- as.character(linelist$Date)
# Create the incidence object
outbreak <- incidence2::incidence(
x = linelist,
date_index
= "Date",
interval = "week",
groups
= "Country"
)
# Plot
plot(outbreak,
fill = "Country",
color = "black",
title
= "Biểu đồ dịch bệnh"
)## Using more colors (10) than this palette can handle (6); some colors will be interpolated.
## Consider using `muted` palette instead?
Qua đồ thị trên, ta thấy tình hình dịch bệnh gây thiệt hại lớn về người và gián đoạn kinh tế xã hội trong khu vực, chủ yếu ở Guinea, Italy và Liberia. Được ghi nhận ở Guinea vào tháng 12 năm 2013. Sau đó, bệnh lây lan sang Liberia và Sierra Leone lân cận, với những đợt bùng phát nhỏ xảy ra ở những nơi khác.
Một vòng lặp có nhiều lần lặp có thể chạy trong nhiều phút hoặc thậm chí hàng giờ. Do đó, có thể hữu ích khi in tiến trình ra R console. Câu lệnh if dưới đây có thể được đặt trong các thao tác vòng lặp để in mỗi số thứ 100. Chỉ cần điều chỉnh nó để i là “item” trong vòng lặp của bạn.
for (i in seq_len(nrow(linelist))){
if(i %% 100==0){
print(i)
}}## [1] 100
## [1] 200
## [1] 300
## [1] 400
## [1] 500
## [1] 600
## [1] 700
## [1] 800
## [1] 900
## [1] 1000
## [1] 1100
## [1] 1200
## [1] 1300
## [1] 1400
## [1] 1500
## [1] 1600
## [1] 1700
## [1] 1800
## [1] 1900
## [1] 2000
## [1] 2100
## [1] 2200
## [1] 2300
## [1] 2400
Khi phân tích dữ liệu phức tạp, ta thường xuyên phải thực hiện một nhóm các phân tích tương tự nhau cho các nhóm dữ liệu khác nhau. Việc sử dụng các hàm làm đơn vị thao tác cơ bản và phối hợp các hàm với nhau được gọi là lập trình chức năng hàm (functional programming). Để đơn giản, ta xét ví dụ sau.
Sử dụng tập dữ liệu iris, với mỗi nhóm của Species, xây dựng mô hình hồi quy giữa Sepal.Length và Petal.Length, so sánh giá trị r.squared giữa các mô hình.
Với cách làm thông thường, ta sẽ phải thức hiện theo thứ tự sau:
Cách triển khai trên có thể sử dụng vòng lặp trong R với phương án như sau
library(dplyr)
library(purrr)
category <- iris$Species %>% levels %>% as.character()
model_result <- data.frame()
for (i in category){
df <- iris %>% filter(Species == i)
model <- lm(Sepal.Length ~ Sepal.Width, data = df)
model_summary <- summary(model)
df_temp <- data.frame(species = i,
r.square = model_summary$r.squared)
model_result <- bind_rows(model_result, df_temp)
}
head(model_result)## species r.square
## 1 setosa 0.5513756
## 2 versicolor 0.2765821
## 3 virginica 0.2090573
Tuy nhiên, với lập trình chức năng hàm, ta có thể làm rất đơn giản như sau.
library(purrr)
iris %>%
split(.$Species) %>%
map(~lm(Sepal.Length ~ Sepal.Width, data = .)) %>%
map(summary) %>%
map_dbl("r.squared")## setosa versicolor virginica
## 0.5513756 0.2765821 0.2090573
Trong bài viết này, chúng ta sẽ tìm hiểu các cách thức cơ bản lập trình chức năng hàm cơ bản với map qua package purrr. Việc nắm vững kiến thức và kỹ năng lập trình hàm có rất nhiều ứng dụng trong công việc phân tích, giúp giảm thiểu rất lớn thời gian phân tích, làm cho quá trình phân tích mạch lạc hơn rất nhiều trong các bài toán khám phá dữ liệu
Một hàm cốt lõi của purrr là map(), hàm này “maps” (áp dụng) một hàm cho từng phần tử đầu vào của danh sách/vector bạn cung cấp.
Cú pháp cơ bản là map(.x = SEQUENCE, .f = HÀM, CÁC ĐỐI SỐ KHÁC). Chi tiết hơn như sau:
.x = là các đầu vào mà hàm .f sẽ được áp dụng lặp đi lặp lại - ví dụ: vector của tên các khu vực pháp lý, các cột trong data frame hoặc danh sách các data frame .f = là hàm áp dụng cho từng phần tử của đầu vào .x - nó có thể là một hàm như print() đã tồn tại hoặc một hàm tùy chỉnh mà bạn xác định. Hàm thường được viết sau dấu ngã ~ .
Thêm một số lưu ý về cú pháp:
Nếu hàm không cần chỉ định thêm đối số, nó có thể được viết không có dấu ngoặc đơn và không có dấu ngã (ví dụ: .f = mean). Để cung cấp các đối số sẽ có cùng giá trị cho mỗi lần lặp, hãy cung cấp chúng trong map() nhưng bên ngoài đối số .f =, chẳng hạn như na.rm = T trong map(.x = my_list, .f = mean, na.rm=T). Bạn có thể sử dụng .x (hoặc đơn giản là .) bên trong hàm .f = làm trình giữ chỗ cho giá trị .x của lần lặp đó Sử dụng cú pháp dấu ngã (~) để kiểm soát hàm nhiều hơn - viết hàm như bình thường với dấu ngoặc đơn, chẳng hạn như: map(.x = my_list, .f = ~mean(., na.rm = T)). Sử dụng cú pháp này đặc biệt nếu giá trị của một đối số sẽ thay đổi mỗi lần lặp lại hoặc nếu nó là chính giá trị .x
Hàm map làm hàm tổng quát, ngoài ra, map còn có các biến thể chính sau
Câu lênh:
map(): tạo thành một danh sách.
map_lgl(): tạo ra một vectơ logic.
map_int(): tạo thành một vectơ số nguyên.
map_dbl(): tạo thành một vectơ kép
map_chr(): tạo thành một vectơ ký tự.
# Dạng list
iris %>% map(class)## $Sepal.Length
## [1] "numeric"
##
## $Sepal.Width
## [1] "numeric"
##
## $Petal.Length
## [1] "numeric"
##
## $Petal.Width
## [1] "numeric"
##
## $Species
## [1] "factor"
# Dạng char
iris %>% map_chr(class)## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## "numeric" "numeric" "numeric" "numeric" "factor"
# Dạng data.frame
iris %>% map_df(class)## # A tibble: 1 × 5
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## <chr> <chr> <chr> <chr> <chr>
## 1 numeric numeric numeric numeric factor
Map theo điều kiện với map_if và map_at
Tương tự với map, nhóm map_if và map_at cho phép tính toán theo điều kiện hoặc vị trí của list. Xem ví dụ sau.
# map_if
iris %>%
map_if(is.numeric, as.character) %>%
as.data.frame %>%
str## 'data.frame': 150 obs. of 5 variables:
## $ Sepal.Length: chr "5.1" "4.9" "4.7" "4.6" ...
## $ Sepal.Width : chr "3.5" "3" "3.2" "3.1" ...
## $ Petal.Length: chr "1.4" "1.4" "1.3" "1.5" ...
## $ Petal.Width : chr "0.2" "0.2" "0.2" "0.2" ...
## $ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
# map_at
iris %>%
map_at(c(1,2), as.character) %>%
str## List of 5
## $ Sepal.Length: chr [1:150] "5.1" "4.9" "4.7" "4.6" ...
## $ Sepal.Width : chr [1:150] "3.5" "3" "3.2" "3.1" ...
## $ Petal.Length: num [1:150] 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
## $ Petal.Width : num [1:150] 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
## $ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
Lưu ý: Với trường hợp có hai biến đầu vào, có thể sử dụng nhóm hàm map2. Ví dụ
# Không chạy
map_dbl(1:3, 4:6, sum)
map2_dbl(1:3, 4:6, sum)## [1] 5 7 9
Với các trường hợp phức tạp, ta cần vận dụng linh hoạt.
Ví dụ: Với mỗi dòng trong iris , tách thành dataframe riêng và xoay chiều dữ liêu. Tên các cột trở thành biến attribute, giá trị các cột trở thành biến value.
library(tidyverse)
get_data <- function(data, i){
df <- data %>%
slice(i) %>% t %>%
as.data.frame
result <- data.frame(attribute = rownames(df),
value = df[,1])
rownames(result) <- NULL
return(result)
}
get_data(mtcars, 3)## attribute value
## 1 mpg 22.80
## 2 cyl 4.00
## 3 disp 108.00
## 4 hp 93.00
## 5 drat 3.85
## 6 wt 2.32
## 7 qsec 18.61
## 8 vs 1.00
## 9 am 1.00
## 10 gear 4.00
## 11 carb 1.00
get_data(iris, 1)## attribute value
## 1 Sepal.Length 5.1
## 2 Sepal.Width 3.5
## 3 Petal.Length 1.4
## 4 Petal.Width 0.2
## 5 Species setosa
map2(replicate(3, iris, simplify = F),
c(1:3), get_data)## [[1]]
## attribute value
## 1 Sepal.Length 5.1
## 2 Sepal.Width 3.5
## 3 Petal.Length 1.4
## 4 Petal.Width 0.2
## 5 Species setosa
##
## [[2]]
## attribute value
## 1 Sepal.Length 4.9
## 2 Sepal.Width 3
## 3 Petal.Length 1.4
## 4 Petal.Width 0.2
## 5 Species setosa
##
## [[3]]
## attribute value
## 1 Sepal.Length 4.7
## 2 Sepal.Width 3.2
## 3 Petal.Length 1.3
## 4 Petal.Width 0.2
## 5 Species setosa
Dưới đây là dữ liệu điểm thi THPT năm 2018. Tải dữ liệu và trích xuất riêng điểm môn Toán.
library(tidyverse)df=read.csv("https://raw.githubusercontent.com/kinokoberuji/R-Tutorials/master/Provinces.csv")%>%as_tibble()
df%>%filter(Math!="NA")%>%dplyr::select(Math,Province)->math_dfObject math_df có thể xem như 1 quần thể, với đại lượng cần khảo sát là điểm thi môn Toán. Kích thước khá lớn của quần thể này nhằm chứng tỏ tốc độ tính toán rất nhanh khi sử dụng hàm map
Ví dụ đầu tiên, ta muốn đếm số thí sinh cho mỗi tỉnh thành. Phép đếm này có thể thực hiện dễ dàng bằng hàm group_by và tally của package dplyr:
math_df%>%group_by(Province)%>%tally()## # A tibble: 59 × 2
## Province n
## <chr> <int>
## 1 BA RIA - VUNG TAU 11831
## 2 BAC GIANG 19526
## 3 BAC KAN 2831
## 4 BAC LIEU 5335
## 5 BAC NINH 14790
## 6 BEN TRE 11705
## 7 BINH DINH 17724
## 8 BINH DUONG 11263
## 9 BINH PHUOC 10180
## 10 BINH THUAN 11675
## # ℹ 49 more rows
Tuy nhiên ta còn có thể làm bằng hàm map: Do kết quả đếm là 1 số nguyên, nên ta dùng hàm map_int
math_df%>%split(.$Province)%>%
map_int(~nrow(.x))## BA RIA - VUNG TAU BAC GIANG BAC KAN BAC LIEU
## 11831 19526 2831 5335
## BAC NINH BEN TRE BINH DINH BINH DUONG
## 14790 11705 17724 11263
## BINH PHUOC BINH THUAN CA MAU CAN THO
## 10180 11675 9222 13013
## CAO BANG DA NANG DAK LAK DAK NONG
## 4360 6094 21884 6327
## DIEN BIEN DONG NAI DONG THAP GIA LAI
## 5372 28595 14331 12719
## HA HA GIANG HA NAM HA NOI
## 16211 3089 8657 37993
## HAI DUONG HAI PHONG HAU GIANG HO CHI MINH
## 19933 5094 6176 78035
## HOA BINH HUNG YEN KIEN GIANG KON TUM
## 8903 12860 13413 4425
## KHANH HOA LAI CHAU LAM DONG LANG SON
## 13471 3204 14928 8813
## LAO CAO LONG AN NAM DINH NINH BINH
## 6187 14045 5086 9569
## NINH THUAN PHU THO PHU YEN QUANG BINH
## 5738 13633 10690 9546
## QUANG NAM QUANG NGAI QUANG TRI SOC TRANG
## 17463 12598 7775 9300
## TAY NINH TIEN GIANG TUYEN QUANG THAI BINH
## 8834 14088 7542 21401
## THAI NGUYEN THANH HOA THUA THIEN - HUE TRA VINH
## 8965 14038 12303 8152
## VINH LONG VINH PHUC YEN BAI
## 10550 12565 6974
Phép tính này có thể phức tạp hơn, như trong ví dụ sau đây , dùng 1 hàm map để thi hành hàng loạt quy trình ước tính bách Phân vị thứ 50,75,90,95,97.5 và 99 theo phương pháp Harell Davis cho điểm số mô toán ở mỗi tỉnh thành:
math_df %>% split(.$Province) %>%
map(.,~ Hmisc::hdquantile(.x$Math,probs =c(0.5,0.75,0.9,0.95,0.975,0.99)))->hdquantile
hdquantile## $`BA RIA - VUNG TAU`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.400000 6.199905 6.800000 7.200000 7.563219 7.914723
##
## $`BAC GIANG`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.600000 5.792610 6.600047 7.189397 7.590387 7.999703
##
## $`BAC KAN`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 3.802286 4.796259 5.620354 6.176663 6.656565 7.312336
##
## $`BAC LIEU`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.999999 5.799994 6.416026 6.839013 7.246998 7.757645
##
## $`BAC NINH`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.000000 6.200000 7.008658 7.589541 7.968934 8.289604
##
## $`BEN TRE`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.000639 5.803305 6.587759 6.964479 7.204021 7.601284
##
## $`BINH DINH`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.000000 5.999996 6.687782 7.184730 7.424387 7.835696
##
## $`BINH DUONG`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.200003 6.000000 6.637496 7.005745 7.401851 7.818840
##
## $`BINH PHUOC`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.000000 5.999208 6.602674 7.031692 7.400041 7.799399
##
## $`BINH THUAN`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.000279 5.800391 6.558797 6.915288 7.202357 7.604272
##
## $`CA MAU`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.600074 5.597877 6.204399 6.608821 7.000109 7.389151
##
## $`CAN THO`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.948913 5.800121 6.600000 7.001230 7.406297 7.814570
##
## $`CAO BANG`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 3.612234 4.796204 5.786611 6.268932 6.638211 7.109105
##
## $`DA NANG`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.200024 6.199945 6.929389 7.380615 7.680195 8.183563
##
## $`DAK LAK`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.600000 5.799993 6.600000 7.000304 7.400001 7.800323
##
## $`DAK NONG`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.689001 5.651065 6.404651 6.809554 7.213390 7.710722
##
## $`DIEN BIEN`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 3.999999 4.997339 5.846457 6.403061 6.894208 7.574555
##
## $`DONG NAI`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.000002 5.821474 6.600000 6.998716 7.215888 7.605066
##
## $`DONG THAP`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.005363 5.835720 6.599980 6.999996 7.308687 7.634320
##
## $`GIA LAI`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.600007 5.662857 6.528453 6.999836 7.390578 7.795967
##
## $HA
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.600001 5.793788 6.617909 7.200128 7.601877 8.000393
##
## $`HA GIANG`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 3.199973 4.197598 5.259416 6.229332 7.381538 8.859825
##
## $`HA NAM`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.389075 6.222072 7.000000 7.400356 7.798130 8.089240
##
## $`HA NOI`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.518258 6.400000 7.200000 7.600000 7.999888 8.232922
##
## $`HAI DUONG`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.002566 6.132803 6.879413 7.399975 7.784518 8.132778
##
## $`HAI PHONG`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.399776 6.306874 7.019763 7.532636 7.835441 8.348669
##
## $`HAU GIANG`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.497970 5.400000 6.199130 6.597712 6.979154 7.352020
##
## $`HO CHI MINH`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.402175 6.200000 6.847869 7.259305 7.600000 8.000000
##
## $`HOA BINH`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 3.400000 4.568732 5.942534 6.727072 7.382928 8.085400
##
## $`HUNG YEN`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.999759 6.114800 7.000000 7.520065 7.817950 8.208815
##
## $`KIEN GIANG`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.600000 5.580261 6.200390 6.601734 7.000125 7.400027
##
## $`KON TUM`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.740124 5.829327 6.759417 7.208534 7.582251 8.022827
##
## $`KHANH HOA`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.000000 5.999999 6.608153 7.029322 7.400095 7.799822
##
## $`LAI CHAU`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.199999 4.996757 5.796905 6.232124 6.623815 7.289272
##
## $`LAM DONG`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.200000 6.000000 6.600000 7.000000 7.370035 7.779855
##
## $`LANG SON`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 3.800000 4.799994 5.796326 6.355083 6.801790 7.343663
##
## $`LAO CAO`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.424726 5.448715 6.399516 6.805285 7.243621 7.705071
##
## $`LONG AN`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.000000 5.800012 6.599788 6.999757 7.333981 7.608846
##
## $`NAM DINH`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.629939 6.573560 7.200025 7.598604 7.894011 8.214838
##
## $`NINH BINH`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.200239 6.203598 7.084748 7.589983 7.884826 8.280619
##
## $`NINH THUAN`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.598125 5.599795 6.397556 6.798608 7.105838 7.539479
##
## $`PHU THO`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.600509 5.779899 6.600007 7.098902 7.585875 8.002866
##
## $`PHU YEN`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.799998 5.800000 6.599776 7.001897 7.404600 7.858065
##
## $`QUANG BINH`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.199971 5.372796 6.220980 6.798398 7.212988 7.762785
##
## $`QUANG NAM`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.800000 5.800351 6.603358 7.161329 7.542755 7.965503
##
## $`QUANG NGAI`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.999999 6.000000 6.799613 7.200003 7.597685 7.998864
##
## $`QUANG TRI`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.614974 5.800038 6.616468 7.183950 7.556546 7.896133
##
## $`SOC TRANG`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.450690 5.400004 6.202225 6.746961 7.109587 7.469744
##
## $`TAY NINH`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.001001 5.803440 6.598148 6.989110 7.238366 7.732689
##
## $`TIEN GIANG`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.318650 6.094053 6.800000 7.199996 7.451647 7.823484
##
## $`TUYEN QUANG`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 3.916420 4.823591 5.807896 6.397089 6.864155 7.381700
##
## $`THAI BINH`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.224243 6.313677 7.063399 7.594094 7.809082 8.203210
##
## $`THAI NGUYEN`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.389841 5.418470 6.399962 6.976738 7.361931 7.802083
##
## $`THANH HOA`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.403070 5.802741 6.808442 7.399999 7.799977 8.187719
##
## $`THUA THIEN - HUE`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.996273 6.000006 6.800311 7.259246 7.605339 8.011145
##
## $`TRA VINH`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.464179 5.400100 6.199054 6.599700 6.995502 7.389520
##
## $`VINH LONG`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.998800 5.800000 6.426894 6.843735 7.200953 7.604226
##
## $`VINH PHUC`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 4.999999 6.177329 6.999957 7.402583 7.799999 8.193502
##
## $`YEN BAI`
## 0.500 0.750 0.900 0.950 0.975 0.990
## 3.800545 4.960180 5.997619 6.602036 7.117912 7.639461
Ghi chú: phép ước tính phân vị theo Harell Davis có sử dụng bootstrap, nên rất phổ biến và không phụ thuộc vào đặc tính phân bố, khác với mô tả thông thường. Các bạn có thể tin chắc rằng nếu ở một tỉnh nào đó mà có HDquantile thứ 99 cao hơn trị số này cho quần thể chung, thì ở khu vực đó đã có điều bất thường xảy ra, thí dụ ở Hà Giang trị số HDQ 99 là 8.859, cao hơn rất nhiều so với cả nước (chỉ có 8.0 điểm)
Hmisc::hdquantile(math_df$Math,probs =c(0.5,0.75,0.9,0.95,0.975,0.99))## 0.500 0.750 0.900 0.950 0.975 0.990
## 5.0 6.0 6.8 7.2 7.6 8.0
Một ví dụ khác, lần này ta làm 1 thí nghiệm: Từ quần thể 741024 thí sinh cả nước, ta sẽ chọn mẫu ngẫu nhiên từ 1000 đến 30000 cá thể và tính giá trị trung bình, trung vị, bách phân vị thứ 5 và thứ 99 cho từng mẫu.
Đây là 1 vòng lặp có nội dung: Chọn mẫu n lần với kích thước tăng dần từ 1000 : 30000, và mỗi lần xuất ra 5 chỉ số thống kê mô tả như trên.
Ta có thể dùng vòng lặp for loop để thực hiện, nhưng ở đây em dùng hàm map.
Vì mỗi hàm map chỉ làm 1 công việc duy nhất, nên quy trình cần kết nối 2 hàm map, hàm map thứ nhất làm việc chọn mẫu, hàm map thứ hai làm thống kê mô tả. Do hàm map thứ hai xuất ra 1 dataframe nên nó sẽ là hàm map_df
math_pop<-math_df$Math
sample_n=seq(from=1000, to=30000,by=1000)
s <- map(sample_n,~sample(math_pop,.x, replace = F))%>%
map_df(~data_frame(Size=length(.x),
Mean=mean(.x),
Median=median(.x),
P5=quantile(.x,probs=0.05),
P99=quantile(.x,probs=0.99),
))## Warning: `data_frame()` was deprecated in tibble 1.1.0.
## ℹ Please use `tibble()` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
s## # A tibble: 30 × 5
## Size Mean Median P5 P99
## <int> <dbl> <dbl> <dbl> <dbl>
## 1 1000 4.92 5 2.4 8
## 2 2000 4.83 4.8 2.6 7.80
## 3 3000 4.89 5 2.6 8
## 4 4000 4.87 5 2.4 8
## 5 5000 4.90 5 2.4 8
## 6 6000 4.92 5 2.6 8
## 7 7000 4.87 5 2.4 8
## 8 8000 4.87 5 2.4 8
## 9 9000 4.89 5 2.4 8
## 10 10000 4.88 5 2.4 8
## # ℹ 20 more rows
sử dụng hàm ggplot để vẽ biểu đồ
s%>%ggplot(aes(x=Size))+
geom_pointrange(aes(y=Median,
ymin=P5,ymax=P99,
col=Size),
show.legend = F)+
theme_bw()+
scale_y_continuous(limits = c(0,10))+
geom_hline(yintercept = median(math_pop),linetype=2,col="red")+
coord_flip()Trong ví dụ tiếp theo, em sẽ dùng bộ dữ liệu gapminder, mục tiêu là dựng 5 mô hình khác nhau ước lượng tuổi thọ trung bình theo dân số, thu nhập bình quân, năm và yếu tố địa lý, cho từng quốc gia
library(gapminder)data(gapminder)5 mô hình cần dựng có nội dung
f1 = lifeExp ~ pop
f2 = lifeExp ~ gdpPercap
f3 = lifeExp ~ pop + gdpPercap
f4 = lifeExp ~ pop + gdpPercap + year
f5 = lifeExp ~ pop + gdpPercap + year + continent
f1## lifeExp ~ pop
f2## lifeExp ~ gdpPercap
f3## lifeExp ~ pop + gdpPercap
f4## lifeExp ~ pop + gdpPercap + year
f5 ## lifeExp ~ pop + gdpPercap + year + continent
Ta có thể kết hợp 2 hàm map, hàm thứ nhất dựng 5 mô hình với 5 công thức khác nhau, hàm map thứ 2 trích xuất kết quả summary cho từng mô hình
formulas <- list(f1,f2,f3,f4,f5)
mod <- map (formulas, ~ lm(.x, data=gapminder))%>%
map(~ summary(.x))
mod## [[1]]
##
## Call:
## lm(formula = .x, data = gapminder)
##
## Residuals:
## Min 1Q Median 3Q Max
## -35.70 -11.13 1.07 11.45 22.91
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 5.924e+01 3.243e-01 182.688 < 2e-16 ***
## pop 7.904e-09 2.943e-09 2.685 0.00731 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 12.89 on 1702 degrees of freedom
## Multiple R-squared: 0.004219, Adjusted R-squared: 0.003634
## F-statistic: 7.212 on 1 and 1702 DF, p-value: 0.007314
##
##
## [[2]]
##
## Call:
## lm(formula = .x, data = gapminder)
##
## Residuals:
## Min 1Q Median 3Q Max
## -82.754 -7.758 2.176 8.225 18.426
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 5.396e+01 3.150e-01 171.29 <2e-16 ***
## gdpPercap 7.649e-04 2.579e-05 29.66 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 10.49 on 1702 degrees of freedom
## Multiple R-squared: 0.3407, Adjusted R-squared: 0.3403
## F-statistic: 879.6 on 1 and 1702 DF, p-value: < 2.2e-16
##
##
## [[3]]
##
## Call:
## lm(formula = .x, data = gapminder)
##
## Residuals:
## Min 1Q Median 3Q Max
## -82.754 -7.745 2.055 8.212 18.534
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 5.365e+01 3.225e-01 166.36 < 2e-16 ***
## pop 9.728e-09 2.385e-09 4.08 4.72e-05 ***
## gdpPercap 7.676e-04 2.568e-05 29.89 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 10.44 on 1701 degrees of freedom
## Multiple R-squared: 0.3471, Adjusted R-squared: 0.3463
## F-statistic: 452.2 on 2 and 1701 DF, p-value: < 2.2e-16
##
##
## [[4]]
##
## Call:
## lm(formula = .x, data = gapminder)
##
## Residuals:
## Min 1Q Median 3Q Max
## -67.497 -7.075 1.121 7.701 19.640
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -4.115e+02 2.767e+01 -14.872 < 2e-16 ***
## pop 6.353e-09 2.218e-09 2.864 0.00423 **
## gdpPercap 6.729e-04 2.444e-05 27.529 < 2e-16 ***
## year 2.354e-01 1.400e-02 16.812 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 9.673 on 1700 degrees of freedom
## Multiple R-squared: 0.4402, Adjusted R-squared: 0.4392
## F-statistic: 445.6 on 3 and 1700 DF, p-value: < 2.2e-16
##
##
## [[5]]
##
## Call:
## lm(formula = .x, data = gapminder)
##
## Residuals:
## Min 1Q Median 3Q Max
## -28.4051 -4.0550 0.2317 4.5073 20.0217
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -5.185e+02 1.989e+01 -26.062 <2e-16 ***
## pop 1.791e-09 1.634e-09 1.096 0.273
## gdpPercap 2.985e-04 2.002e-05 14.908 <2e-16 ***
## year 2.863e-01 1.006e-02 28.469 <2e-16 ***
## continentAmericas 1.429e+01 4.946e-01 28.898 <2e-16 ***
## continentAsia 9.375e+00 4.719e-01 19.869 <2e-16 ***
## continentEurope 1.936e+01 5.182e-01 37.361 <2e-16 ***
## continentOceania 2.056e+01 1.469e+00 13.995 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 6.883 on 1696 degrees of freedom
## Multiple R-squared: 0.7172, Adjusted R-squared: 0.716
## F-statistic: 614.5 on 7 and 1696 DF, p-value: < 2.2e-16
Bây giờ ta thay đổi hàm map thứ hai thành map_df để trích xuất riêng trị số AIC và công thức mỗi mô hình
mod <- map (formulas, ~ lm(.x, data=gapminder))%>%
map_df(~ data_frame(formula = format(formula(.x)),
AIC=AIC(.x),
stringAsFactors=F))
mod## # A tibble: 5 × 3
## formula AIC stringAsFactors
## <chr> <dbl> <lgl>
## 1 lifeExp ~ pop 13553. FALSE
## 2 lifeExp ~ gdpPercap 12850. FALSE
## 3 lifeExp ~ pop + gdpPercap 12836. FALSE
## 4 lifeExp ~ pop + gdpPercap + year 12576. FALSE
## 5 lifeExp ~ pop + gdpPercap + year + continent 11420. FALSE
Một thủ thuật khác, đó là nêu trực tiếp tên của thành phần cần trích xuất từ dữ liệu đầu ra của hàm map đi trước (trở thành .x của hàm map đi sau)
gapminder %>% split(.$country) %>%
map (.,~ lm(lifeExp ~ year, data=.x))%>%
map(~ summary(.x))%>%
map_dbl("r.squared")## Afghanistan Albania Algeria
## 0.94771226 0.91057777 0.98511721
## Angola Argentina Australia
## 0.88781463 0.99556810 0.97964774
## Austria Bahrain Bangladesh
## 0.99213401 0.96673981 0.98936087
## Belgium Benin Bolivia
## 0.99454056 0.96660199 0.98454156
## Bosnia and Herzegovina Botswana Brazil
## 0.89569829 0.03402340 0.99804741
## Bulgaria Burkina Faso Burundi
## 0.54654217 0.91871050 0.76599597
## Cambodia Cameroon Canada
## 0.63869222 0.68017839 0.99638552
## Central African Republic Chad Chile
## 0.49324448 0.87237550 0.98279710
## China Colombia Comoros
## 0.87127734 0.96787344 0.99685076
## Congo, Dem. Rep. Congo, Rep. Costa Rica
## 0.34820278 0.51966079 0.96174767
## Cote d'Ivoire Croatia Cuba
## 0.28337240 0.93243047 0.92406716
## Czech Republic Denmark Djibouti
## 0.91668191 0.97066797 0.97437134
## Dominican Republic Ecuador Egypt
## 0.97060781 0.99456626 0.99030424
## El Salvador Equatorial Guinea Eritrea
## 0.95567201 0.99686864 0.95727334
## Ethiopia Finland France
## 0.96850263 0.99383835 0.99762458
## Gabon Gambia Germany
## 0.81276621 0.98923562 0.98950568
## Ghana Greece Guatemala
## 0.98409873 0.97196085 0.99666377
## Guinea Guinea-Bissau Haiti
## 0.97831518 0.98455829 0.98761338
## Honduras Hong Kong, China Hungary
## 0.97730026 0.97230183 0.79501875
## Iceland India Indonesia
## 0.97032657 0.96843652 0.99711418
## Iran Iraq Ireland
## 0.99501535 0.54578420 0.98414574
## Israel Italy Jamaica
## 0.99478290 0.99336612 0.80565904
## Japan Jordan Kenya
## 0.95959563 0.96975008 0.44255729
## Korea, Dem. Rep. Korea, Rep. Kuwait
## 0.70306306 0.98765101 0.95235423
## Lebanon Lesotho Liberia
## 0.94172582 0.08485635 0.51175640
## Libya Madagascar Malawi
## 0.98333149 0.99465364 0.83995446
## Malaysia Mali Mauritania
## 0.94650639 0.99545140 0.99767430
## Mauritius Mexico Mongolia
## 0.93478457 0.98520444 0.98731309
## Montenegro Morocco Mozambique
## 0.80186521 0.99458312 0.77427932
## Myanmar Namibia Nepal
## 0.87937750 0.43702163 0.99154171
## Netherlands New Zealand Nicaragua
## 0.98221566 0.95358464 0.99677615
## Niger Nigeria Norway
## 0.89768664 0.87010508 0.96290057
## Oman Pakistan Panama
## 0.97479461 0.99724965 0.95119516
## Paraguay Peru Philippines
## 0.98298650 0.98847401 0.99142260
## Poland Portugal Puerto Rico
## 0.83966315 0.96903508 0.90781912
## Reunion Romania Rwanda
## 0.96607180 0.80556666 0.01715964
## Sao Tome and Principe Saudi Arabia Senegal
## 0.95525936 0.97208439 0.99054417
## Serbia Sierra Leone Singapore
## 0.87880538 0.96015054 0.98794751
## Slovak Republic Slovenia Somalia
## 0.79174822 0.96604327 0.84442863
## South Africa Spain Sri Lanka
## 0.31246865 0.96489456 0.94771469
## Sudan Swaziland Sweden
## 0.99214243 0.06821087 0.99548216
## Switzerland Syria Taiwan
## 0.99739086 0.98416512 0.95707113
## Tanzania Thailand Togo
## 0.76421876 0.96738440 0.90580373
## Trinidad and Tobago Tunisia Turkey
## 0.79800744 0.98070422 0.98533264
## Uganda United Kingdom United States
## 0.34215382 0.98443596 0.98592016
## Uruguay Venezuela Vietnam
## 0.97683072 0.94652607 0.98941189
## West Bank and Gaza Yemen, Rep. Zambia
## 0.97048087 0.98117240 0.05983644
## Zimbabwe
## 0.05623196
Trong thí dụ minh họa tiếp theo, em sẽ dùng dữ liệu cigarette , với nội dung diễn tiến giá bán lẻ thuốc lá tại 49 bang của Mỹ từ năm 1962 đến 1992.
library(gamlss)nC<-detectCores()
cigarette <- read.csv("https://raw.githubusercontent.com/vincentarelbundock/Rdatasets/master/csv/plm/Cigar.csv")
head(cigarette)## X state year price pop pop16 cpi ndi sales pimin
## 1 1 1 63 28.6 3383 2236.5 30.6 1558.305 93.9 26.1
## 2 2 1 64 29.8 3431 2276.7 31.0 1684.073 95.4 27.5
## 3 3 1 65 29.8 3486 2327.5 31.5 1809.842 98.5 28.9
## 4 4 1 66 31.5 3524 2369.7 32.4 1915.160 96.4 29.5
## 5 5 1 67 31.6 3533 2393.7 33.4 2023.546 95.5 29.6
## 6 6 1 68 35.6 3522 2405.2 34.8 2202.486 88.4 32.0
cigarette$state=factor(cigarette$state)
cigarette$year=factor(cigarette$year-62)map_dbl(airquality, mean)## Ozone Solar.R Wind Temp Month Day
## NA NA 9.957516 77.882353 6.993464 15.803922
airquality %>% map_int(length)## Ozone Solar.R Wind Temp Month Day
## 153 153 153 153 153 153
airquality %>% map_dbl(mean, trim = 0.5)## Ozone Solar.R Wind Temp Month Day
## NA NA 9.7 79.0 7.0 16.0
Nếu viết dạng đầy đủ
models <- mtcars %>%
split(.$cyl) %>%
map(function(df) lm(mpg ~ wt, data = df))Viết dạng hàm ẩn (ngắn gọn hơn)
models <- mtcars %>%
split(.$cyl) %>%
map(~lm(mpg ~ wt, data = .))Chú ý: “.” thay cho object đã tạo ra trước đó (pronoun)
models %>%
map(summary) %>%
map_dbl(~.$r.squared)## 4 6 8
## 0.5086326 0.4645102 0.4229655
models %>%
map(summary) %>%
map_dbl("r.squared")## 4 6 8
## 0.5086326 0.4645102 0.4229655
Cú pháp của hàm:
map2(.x, .y, .f, …)
pmap(.l, .f, …)
Thay vì vòng lặp đối với 1 vector (1 agrument) như hàm map thì hàm map2_() và pmap_() tạo vòng lặp với 2 hay nhiều agrument
Ví dụ với map2
mu1 <- c(5, 10, 12)
sigma1 <- c(2, 4, 5)
map2(mu1, sigma1, rnorm, n = 5) %>% str()## List of 3
## $ : num [1:5] 4.06 3.5 7.89 4 4.89
## $ : num [1:5] 16.99 4.2 11.21 7.91 11.59
## $ : num [1:5] 8.61 11.87 9.86 11 18
Ví dụ với pmap
rnorm(n, mean, sd)
TH1: Thêm list n1 vào vòng lặp
n1 <- list(1, 3, 5)
args1 <- list(n1, mu1, sigma1) # Nếu tên khác agrument thì phải sắp xếp đúng thứ tự
set.seed(158)
args1 %>%
pmap(rnorm) %>%
str()## List of 3
## $ : num 6.66
## $ : num [1:3] 11.14 8.57 5.36
## $ : num [1:5] 14.43 7.29 12.29 11.62 5.45
Thêm vector n2 vào vòng lặp (thay đổi 1 chút thứ tự các agrument)
n2 <- c(1, 3, 5)
args2 <- list(mean = mu1, sd = sigma1, n = n2)
set.seed(158)
args2 %>%
pmap(rnorm) %>%
str()## List of 3
## $ : num 6.66
## $ : num [1:3] 11.14 8.57 5.36
## $ : num [1:5] 14.43 7.29 12.29 11.62 5.45
Vòng lặp với data.frame
set.seed(158)
params <- data.frame(mean = mu1, sd = sigma1, n = n2)
params %>%
pmap(rnorm) %>%
str()## List of 3
## $ : num 6.66
## $ : num [1:3] 11.14 8.57 5.36
## $ : num [1:5] 14.43 7.29 12.29 11.62 5.45
Chú ý:
Tương tự như map, modify cho áp dụng hàm vào một nhóm các list. Tuy nhiên, khác với map, modify cho ra kết quả với cấu trúc dữ liệu ban đâu.
library(tidyverse)library(tidyverse)
# map đổi cấu trúc của dataframe
iris %>%
map_if(is.factor, as.character) %>%
str## List of 5
## $ Sepal.Length: num [1:150] 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
## $ Sepal.Width : num [1:150] 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
## $ Petal.Length: num [1:150] 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
## $ Petal.Width : num [1:150] 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
## $ Species : chr [1:150] "setosa" "setosa" "setosa" "setosa" ...
# modify giữ nguyên cấu trúc
iris %>%
modify_if(is.factor, as.character) %>%
str## 'data.frame': 150 obs. of 5 variables:
## $ Sepal.Length: num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
## $ Sepal.Width : num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
## $ Petal.Length: num 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
## $ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
## $ Species : chr "setosa" "setosa" "setosa" "setosa" ...
as_mapper cho phép tạo hàm nhanh, đặc biệt hữu ích khi ta chỉ muốn tạo và sử dụng một hàm trong một vài trường hợp đặc biệt.
# Với một tham số
as_mapper(~f(.x))
# Với hai tham số
as_mapper(f(.x, .y))
# Cộng 10 vào mỗi giá trị
map_dbl(1:3, ~ .x+10)## [1] 11 12 13
# Cộng hai vector với nhau
map2_dbl(1:3, 5:7, ~.x + .y)## [1] 6 8 10
# Cách viết khác
map2_dbl(1:3, 5:7, as_mapper(~.x + .y))## [1] 6 8 10
Cho kết quả là 1 list gồm 2 phần tử:
result: Trả về kết quả của hàm. Nếu gặp lỗi thì = NULL
error : Trả về lỗi của object. Nếu không gặp lỗi thì = NULL
(hàm safely tương tự với hàm try của gói base)
Ví dụ với hàm log
safe_log <- safely(log)
str(safe_log(10))## List of 2
## $ result: num 2.3
## $ error : NULL
str(safe_log("a"))## List of 2
## $ result: NULL
## $ error :List of 2
## ..$ message: chr "non-numeric argument to mathematical function"
## ..$ call : language .Primitive("log")(x, base)
## ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
Thiết kế của hàm safely được sử dụng tốt với hàm map
x <- list(1, 10, "a")
y <- x %>% map(safely(log))
str(y)## List of 3
## $ :List of 2
## ..$ result: num 0
## ..$ error : NULL
## $ :List of 2
## ..$ result: num 2.3
## ..$ error : NULL
## $ :List of 2
## ..$ result: NULL
## ..$ error :List of 2
## .. ..$ message: chr "non-numeric argument to mathematical function"
## .. ..$ call : language .Primitive("log")(x, base)
## .. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
Kết hợp với hàm purrr:transpose sẽ tách được riêng list thành 2 phần: 1 phần result và 1 phần là error
y <- y %>% transpose()
str(y)## List of 2
## $ result:List of 3
## ..$ : num 0
## ..$ : num 2.3
## ..$ : NULL
## $ error :List of 3
## ..$ : NULL
## ..$ : NULL
## ..$ :List of 2
## .. ..$ message: chr "non-numeric argument to mathematical function"
## .. ..$ call : language .Primitive("log")(x, base)
## .. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
Đầu ra của result và error
is_ok <- y$error %>% map_lgl(is_null)
x[!is_ok]## [[1]]
## [1] "a"
y$result[is_ok] %>% flatten_dbl() # flatten_ tương tự với hàm unlist## [1] 0.000000 2.302585
Tương tự với hàm safely() luôn cho kết quả succeed. Nhưng hàm này đơn giản hơn safely() vì nó cho phép gán giá trị default khi gặp lỗi
x <- list(1, 10, "a")
x %>% map_dbl(possibly(log, NA_real_))## [1] 0.000000 2.302585 NA
Tương tự như safely() nhưng thay vì đầu ra là error, hàm này cho ra output, messages và warnings
x <- list(1, -1)
x %>% map(quietly(log)) %>% str()## List of 2
## $ :List of 4
## ..$ result : num 0
## ..$ output : chr ""
## ..$ warnings: chr(0)
## ..$ messages: chr(0)
## $ :List of 4
## ..$ result : num NaN
## ..$ output : chr ""
## ..$ warnings: chr "NaNs produced"
## ..$ messages: chr(0)
Chú ý: Các hàm bắt lỗi khác với các hàm thông thường ở chỗ: hàm thông thường ~ verb, còn các hàm này ~ adverb. Tức là chỉ ra cách thức thực hiện các hàm thông thường
Đầu tiên tôi ví dụ 1 đoạn code:
library(broom)lm(Sepal.Length ~ Species, data = iris) %>% tidy() %>% filter(p.value < 0.05)## # A tibble: 3 × 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) 5.01 0.0728 68.8 1.13e-113
## 2 Speciesversicolor 0.93 0.103 9.03 8.77e- 16
## 3 Speciesvirginica 1.58 0.103 15.4 2.21e- 32
lm(Sepal.Length ~ Species, data = iris) %>% tidy() %>% filter(p.value < 0.05)## # A tibble: 3 × 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) 5.01 0.0728 68.8 1.13e-113
## 2 Speciesversicolor 0.93 0.103 9.03 8.77e- 16
## 3 Speciesvirginica 1.58 0.103 15.4 2.21e- 32
lm(Sepal.Width ~ Species, data = iris) %>% tidy() %>% filter(p.value < 0.05)## # A tibble: 3 × 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) 3.43 0.0480 71.4 5.71e-116
## 2 Speciesversicolor -0.658 0.0679 -9.69 1.83e- 17
## 3 Speciesvirginica -0.454 0.0679 -6.68 4.54e- 10
lm(Sepal.Length ~ Species, data = iris) %>% tidy() %>% filter(p.value < 0.05)## # A tibble: 3 × 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) 5.01 0.0728 68.8 1.13e-113
## 2 Speciesversicolor 0.93 0.103 9.03 8.77e- 16
## 3 Speciesvirginica 1.58 0.103 15.4 2.21e- 32
Nếu chúng ta viết code như trên thông thường mắt của chúng ta sẽ chỉ tập trung vào những điểm giống nhau thay vì tập trung vào những điểm thay đổi của các dòng code. Vì vậy, coder rất dễ mắc lỗi khi viết nhiều dòng tương tự nhau. Chú ý rằng nếu code lặp lại trên 2 lần thì nên sử dụng function. Với purrr thay vì viết hàm mới thì chúng ta nên modify hoặc kết hợp các hàm lại với nhau.
So sánh 2 cách viết:
tidy(lm(Sepal.Length ~ Species, data = iris))## # A tibble: 3 × 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) 5.01 0.0728 68.8 1.13e-113
## 2 Speciesversicolor 0.93 0.103 9.03 8.77e- 16
## 3 Speciesvirginica 1.58 0.103 15.4 2.21e- 32
và cách 2 sử dụng hàm compose:
tidy_lm <- compose(tidy, lm)
tidy_lm(Sepal.Length ~ Species, data = iris)## # A tibble: 3 × 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) 5.01 0.0728 68.8 1.13e-113
## 2 Speciesversicolor 0.93 0.103 9.03 8.77e- 16
## 3 Speciesvirginica 1.58 0.103 15.4 2.21e- 32
Như vậy, bằng cách kết hợp hàm lm và tidy (theo nguyên tắc từ phải sang trái) chúng ta có hàm tidy_lm ngắn gọn
Sử dụng as_mapper để convert 1 object sang function và cũng sử dụng hàm compose
clean_lm <- compose(as_mapper(~ arrange(.x, desc(p.value))),
as_mapper(~ filter(.x, p.value < 0.05)),
tidy,
lm)
clean_lm(Sepal.Length ~ Sepal.Width, data = iris)## # A tibble: 1 × 5
## term estimate std.error statistic p.value
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) 6.53 0.479 13.6 6.47e-28
Cách viết thông thường khi tính giá trị trung bình với vector có giá trị NA
mean(airquality$Ozone, na.rm = TRUE)## [1] 42.12931
Sử dụng hàm partial:
mean_na_rm <- partial(mean, na.rm = TRUE)
mean_na_rm(airquality$Ozone)## [1] 42.12931
kết quả tương tự đoạn code trên nhưng ngắn gọn hơn
Gói purrr là một gói mạnh mẽ và linh hoạt trong R, cung
cấp các công cụ lập trình chức năng để làm việc với danh sách, vectơ và
các cấu trúc dữ liệu khác. Giống như bất kỳ gói nào, purrr
có những ưu điểm và nhược điểm riêng.
Gói purrr trong R cung cấp nhiều lợi thế cho lập trình chức năng và làm việc với danh sách và vectơ, nhưng nó cũng có một số nhược điểm tiềm ẩn:
Như với bất kỳ gói nào giới thiệu một mô hình lập trình khác, có thể có một đường cong học tập cho những người không quen thuộc với các khái niệm lập trình chức năng. Người dùng đã quen với lập trình mệnh lệnh có thể thấy khó thích ứng với cách tiếp cận chức năng.
Phụ thuộc vào tidyverse: Mặc dù purrr là một phần của tidyverse, là tập hợp các gói được sử dụng rộng rãi, nhưng điều đó cũng có nghĩa là việc sử dụng purrr mang lại các phụ thuộc bổ sung. Nếu bạn đang làm việc trong một môi trường không khuyến khích hoặc hạn chế việc thêm các gói bổ sung, thì đây có thể là một vấn đề đáng lo ngại.
Thách thức gỡ lỗi: Mã gỡ lỗi chủ yếu dựa vào lập trình chức năng có thể phức tạp hơn mã mệnh lệnh truyền thống. Việc hiểu quy trình hoạt động và gỡ lỗi các bước trung gian có thể cần thêm nỗ lực.
Độ phức tạp trừu tượng: Việc sử dụng các hàm bậc cao hơn và các hàm như map()có thể giới thiệu các lớp trừu tượng có thể làm cho mã ít đơn giản hơn đối với một số người đọc. Cân bằng tính trừu tượng và khả năng đọc là rất quan trọng để đảm bảo khả năng duy trì mã.
Trong một số trường hợp, việc sử dụng purrrvà các kỹ thuật lập trình chức năng có thể dẫn đến mã dài dòng hơn so với các lựa chọn thay thế vector hóa, đơn giản hơn. Điều này có thể làm cho mã khó đọc và khó bảo trì hơn.
Bất chấp những hạn chế tiềm ẩn này, điều cần thiết là phải nhớ rằng
purrrlập trình chức năng trong R mang lại lợi ích đáng kể cho nhiều vụ
thao tác dữ liệu. Nó là một công cụ mạnh mẽ để làm việc với các cấu trúc
dữ liệu phức tạp và có thể dẫn đến mã ngắn gọn, biểu cảm và dễ đọc hơn.
Quyết định sử dụng purrr nên xem xét trường hợp sử dụng cụ
thể, cấu trúc dữ liệu, yêu cầu hiệu suất cũng như mức độ quen thuộc và
sở thích của nhóm phát triển. Trong nhiều trường hợp, purrr
có thể nâng cao đáng kể khả năng thao tác dữ liệu và khả năng đọc mã
R.
Tài liệu tham khảo: