Tác giả: Hoàng Đức Anh | 2019-04-15
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:
- Tạo các data.frame cho từng giá trị của Species
- Với mỗi data.frame vừa tạo, xây dựng mô hình
lm
- Với mỗi mô hình vừa tạo, chiết xuất giá trị
r.squared
và lưu vào một data.frame
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)
}
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
Công thức tổng quát của nhóm hàm map
map(.x, .f, ...)
Giải thích: Với mỗi giá trị của .x
, thực hiện .f
. Trong đó, x là một list.
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 | Kết quả |
---|---|
map | list |
map_dbl | vector dạng double |
map_int | vector dạng int |
map_chr | vector dạng character |
map_df | data.frame |
# 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 x 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: Factor w/ 35 levels "4.3","4.4","4.5",..: 9 7 5 4 8 12 4 8 2 7 ...
## $ Sepal.Width : Factor w/ 23 levels "2","2.2","2.3",..: 15 10 12 11 16 19 14 14 9 11 ...
## $ Petal.Length: Factor w/ 43 levels "1","1.1","1.2",..: 5 5 4 6 5 8 5 6 5 6 ...
## $ Petal.Width : Factor w/ 22 levels "0.1","0.2","0.3",..: 2 2 2 2 2 4 3 2 2 1 ...
## $ 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
Như vậy, chúng ta đã vừa được học cách sử dụng nhóm hàm map
cơ bản với purrr
. Chúc các bạn học tập và làm việc hiệu quả với Ranalytics.vn
!