## function (X, FUN, ...)
X,FUN là tham số (parameter).Vòng lặp for hữu ích trong lập trình nhưng không dễ để sử dụng. Vì thế, R xây dựng một số hàm giúp cho vòng lặp trở nên dễ dàng hơn
lapply(): Lặp dựa trên các thành phần của list. Hàm số mà ta chỉ định sẽ được thực hiện đối với mỗi thành phần này.
sapply(): Tương tự như lapply nhưng kết quả được đơn giản hóa thành dạng Vector hay Data Frame.
apply(): Áp dụng hàm số theo lề của một bảng.
tapply(): Áp dụng hàm số cho từng tập hợp con của một vector.
lapply()Trong bài này, ta sẽ làm việc với bộ dữ liệu airqualityđược tích hợp sẵn trong R.
Câu hỏi: Bộ dữ liệu airquality có 6 biến. Hãy dùng vòng lặp for, tạo một vector 6 thành phần, mà mỗi thành phần là số trung bình của từng biến số trong airquality.
Giải đáp:
Cách 1
colnames <- names(airquality)
varMean <- numeric(6)
names(varMean) <- colnames
for (i in colnames) {
varMean[i] <- mean(airquality[,i], na.rm = TRUE)
}
varMean## Ozone Solar.R Wind Temp Month Day
## 42.129310 185.931507 9.957516 77.882353 6.993464 15.803922
Cách 2
colnames <- names(airquality)
varMean2 <- numeric(6)
for (i in 1:length(colnames)) {
varMean2[i] <- mean(airquality[,i], na.rm = TRUE)
}
varMean2## [1] 42.129310 185.931507 9.957516 77.882353 6.993464 15.803922
Để giải quyết câu hỏi trên, ta cần gọi nhiều hàm số và cần quen với tư duy sử dụng vòng lặp for. Các công đoạn trên có thể được làm ngắn lại với lapply().
lapply() hoạt động theo từng bước sau:Lặp qua từng thành phần của list đó
Áp dụng hàm số vào mỗi thành phần của list đó (một hàm số được chỉ định)
Trả về một list
Hàm số này nhận 3 đối số: (1) list X; (2) hàm số (hoặc tên của hàm số) FUN; (3) những đối số khác trong đối số .... Nếu X không phải là list, nó sẽ được chuyển dạng sang list bằng hàm số as.list().
Một điều quan trọng là lapply() luôn trả về list, cho dù đối số X được truyền vào là nhóm nào đi nữa.
## $a
## [1] 3
##
## $b
## [1] -0.5941557
Ta có thể sử dụng lapply() để đánh giá một hàm số nhiều lần với những đối số khác nhau.
## [[1]]
## [1] 0.239534
##
## [[2]]
## [1] 0.9044524 0.5214814
##
## [[3]]
## [1] 0.6431543 0.3466180 0.5789678
##
## [[4]]
## [1] 0.6118357 0.4173622 0.9518173 0.6476397
Khi ta truyền một hàm số tới lapply(), lapply() lấy từng thành phần của danh sách và truyền chúng vào đối số đầu tiên của hàm số được chỉ định. Ở ví dụ trên, đối số đầu tiên của runif() là n, và do đó từng thành phần của chuỗi 1:4 được truyền vào đối số n của runif().
Hàm số mà ta truyền vào lapply() có những đối số của nó. Ví dụ, hàm runif() có đối số min và max. Những đối số này sẽ được đặt ở đối số ... của hàm lapply().
Ở trường hợp này, đối số min = 0 và max = 10 được truyền vào hàm runìf() mỗi khi nó được gọi.
## [[1]]
## [1] 4.159846
##
## [[2]]
## [1] 3.455416 1.893661
##
## [[3]]
## [1] 7.162459 1.927981 7.113403
##
## [[4]]
## [1] 2.470389 8.222483 1.679206 1.868843
Do đó, thay vì những con số ngẫu nhiên từ 0 đến 1 (mặc định), chúng dao động từ 0 đến 10.
Cuối cùng, bài toán trên có thể được giải theo cách khác.
## $Ozone
## [1] 42.12931
##
## $Solar.R
## [1] 185.9315
##
## $Wind
## [1] 9.957516
##
## $Temp
## [1] 77.88235
##
## $Month
## [1] 6.993464
##
## $Day
## [1] 15.80392
Ta có được kết quả là một list với từng thành phần là số trung bình của mỗi biến số của airquality.
Tuy nhiên, đầu ra vẫn không phải là một vector.
Vì thế ta cần một cách khác, đó là sapply().
sapply()Hàm sapply() hoạt động tương tự như lapply(); sự khác biệt duy nhất là giá trị trả về. sapply() sẽ làm đơn giản kết quả khi có thể. Về bản chất sapply() hoạt động như sau:
Gọi hàm lapply().
Nếu kết quả là list với mỗi thành phần có độ dài 1, vector được trả về.
Nếu kết quả là list với mỗi thành phần cùng độ dài và lớn hơn 1, matrix được trả về.
Nếu không phải những trường hợp trên, list được trả về. Lúc này kết quả giống như ta sử dụng lapply().
## $a
## [1] 2.5
##
## $b
## [1] -0.8675274
##
## $c
## [1] 1.322438
##
## $d
## [1] 5.077016
Lưu ý rằng lapply() trả về list như mọi khi. Mỗi thành phần của list này có độ dài là 1.
Đây là kết quả khi ta gọi sapply() trên cùng một list.
## a b c d
## 2.5000000 -0.8675274 1.3224383 5.0770161
Bởi vì kết quả của lapply() là list với mỗi thành phần có độ dài là 1, sapply() thu gọn đầu ra thành một vector kiểu numeric.
Ta áp dụng sapply() để giải quyết bài toán trên. Vì kết quả từ lapply() là một list với độ dài mỗi thành phần là 1, đầu ra của sapply() sẽ là một vector.
## Ozone Solar.R Wind Temp Month Day
## 42.129310 185.931507 9.957516 77.882353 6.993464 15.803922
split()Đề bài: Sử dụng vòng lặp for, hãy tính số trung bình Ozone theo từng tháng.
Cách trên giúp ta đạt được mục tiêu, nhưng trông không gọn gàng và hơi phức tạp. Với hàm split(), ta có thể làm cho chúng gọn nhẹ hơn.
split()Hàm split() nhận vào một vector và tách nó thành nhiều nhóm theo một factor, hoặc list của những factor.
Các đối số của split() bao gồm
## function (x, f, drop = FALSE, ...)
Trong đó
x là vector hoặc list hoặc data frame.
f là factor (hoặc được ép kiểu thành factor) hoặc một list của những factor.
drop chỉ rằng factor trống có nên được bỏ hay không.
Sự kết hợp giữa split và hàm số như lapply() hoặc sapply() là thường gặp trong R. Về cơ bản, ta muốn nhận một cấu trúc dữ liệu, phân tách nó thành những tập hợp con theo một biến số khác, và áp dụng một hàm vào những tập hợp con đó.
## $`1`
## [1] -1.74163069 -1.17232295 1.41421991 -0.68943470 0.15135942 1.31186861
## [7] -0.01650557 0.01440528 -0.05464505 0.43808892
##
## $`2`
## [1] 0.25957667 0.33551607 0.91171408 0.64484045 0.55282049 0.04975694
## [7] 0.47542621 0.20121836 0.91547593 0.22188966
##
## $`3`
## [1] 0.51749099 1.65492296 0.76318368 -0.07187649 0.61071065 1.51427794
## [7] -0.62854999 0.49869919 1.29134474 0.93867488
Sự kết hợp thường là split() được truyền vào lapply().
## $`1`
## [1] -0.03445968
##
## $`2`
## [1] 0.4568235
##
## $`3`
## [1] 0.7088879
Bằng sự kết hợp giữa sapply() và split(). Ta giải quyết bài toán ở đầu phần này như sau.
## 5 6 7 8 9
## 23.61538 29.44444 59.11538 59.96154 31.44828
## Ozone Solar.R Wind Temp Month Day
## 1 41 190 7.4 67 5 1
## 2 36 118 8.0 72 5 2
## 3 12 149 12.6 74 5 3
## 4 18 313 11.5 62 5 4
## 5 NA NA 14.3 56 5 5
## 6 28 NA 14.9 66 5 6
Ta có thể tách data frame airquality theo biến số Month. Từ đó ta có những data frame nhỏ cho mỗi tháng.
## List of 5
## $ 5:'data.frame': 31 obs. of 6 variables:
## ..$ Ozone : int [1:31] 41 36 12 18 NA 28 23 19 8 NA ...
## ..$ Solar.R: int [1:31] 190 118 149 313 NA NA 299 99 19 194 ...
## ..$ Wind : num [1:31] 7.4 8 12.6 11.5 14.3 14.9 8.6 13.8 20.1 8.6 ...
## ..$ Temp : int [1:31] 67 72 74 62 56 66 65 59 61 69 ...
## ..$ Month : int [1:31] 5 5 5 5 5 5 5 5 5 5 ...
## ..$ Day : int [1:31] 1 2 3 4 5 6 7 8 9 10 ...
## $ 6:'data.frame': 30 obs. of 6 variables:
## ..$ Ozone : int [1:30] NA NA NA NA NA NA 29 NA 71 39 ...
## ..$ Solar.R: int [1:30] 286 287 242 186 220 264 127 273 291 323 ...
## ..$ Wind : num [1:30] 8.6 9.7 16.1 9.2 8.6 14.3 9.7 6.9 13.8 11.5 ...
## ..$ Temp : int [1:30] 78 74 67 84 85 79 82 87 90 87 ...
## ..$ Month : int [1:30] 6 6 6 6 6 6 6 6 6 6 ...
## ..$ Day : int [1:30] 1 2 3 4 5 6 7 8 9 10 ...
## $ 7:'data.frame': 31 obs. of 6 variables:
## ..$ Ozone : int [1:31] 135 49 32 NA 64 40 77 97 97 85 ...
## ..$ Solar.R: int [1:31] 269 248 236 101 175 314 276 267 272 175 ...
## ..$ Wind : num [1:31] 4.1 9.2 9.2 10.9 4.6 10.9 5.1 6.3 5.7 7.4 ...
## ..$ Temp : int [1:31] 84 85 81 84 83 83 88 92 92 89 ...
## ..$ Month : int [1:31] 7 7 7 7 7 7 7 7 7 7 ...
## ..$ Day : int [1:31] 1 2 3 4 5 6 7 8 9 10 ...
## $ 8:'data.frame': 31 obs. of 6 variables:
## ..$ Ozone : int [1:31] 39 9 16 78 35 66 122 89 110 NA ...
## ..$ Solar.R: int [1:31] 83 24 77 NA NA NA 255 229 207 222 ...
## ..$ Wind : num [1:31] 6.9 13.8 7.4 6.9 7.4 4.6 4 10.3 8 8.6 ...
## ..$ Temp : int [1:31] 81 81 82 86 85 87 89 90 90 92 ...
## ..$ Month : int [1:31] 8 8 8 8 8 8 8 8 8 8 ...
## ..$ Day : int [1:31] 1 2 3 4 5 6 7 8 9 10 ...
## $ 9:'data.frame': 30 obs. of 6 variables:
## ..$ Ozone : int [1:30] 96 78 73 91 47 32 20 23 21 24 ...
## ..$ Solar.R: int [1:30] 167 197 183 189 95 92 252 220 230 259 ...
## ..$ Wind : num [1:30] 6.9 5.1 2.8 4.6 7.4 15.5 10.9 10.3 10.9 9.7 ...
## ..$ Temp : int [1:30] 91 92 93 93 87 84 80 78 75 73 ...
## ..$ Month : int [1:30] 9 9 9 9 9 9 9 9 9 9 ...
## ..$ Day : int [1:30] 1 2 3 4 5 6 7 8 9 10 ...
Tiếp theo, ta có thể tính số trung bình cho biến Ozone, Solar.R, và Wind ở mỗi data frame nhỏ.
## $`5`
## Ozone Solar.R Wind
## NA NA 11.62258
##
## $`6`
## Ozone Solar.R Wind
## NA 190.16667 10.26667
##
## $`7`
## Ozone Solar.R Wind
## NA 216.483871 8.941935
##
## $`8`
## Ozone Solar.R Wind
## NA NA 8.793548
##
## $`9`
## Ozone Solar.R Wind
## NA 167.4333 10.1800
Sử dụng sapply() để có đầu ra dễ nhìn hơn.
## 5 6 7 8 9
## Ozone NA NA NA NA NA
## Solar.R NA 190.16667 216.483871 NA 167.4333
## Wind 11.62258 10.26667 8.941935 8.793548 10.1800
Dẫu vậy, có NA ở bộ dữ liệu nên một số kết quả số trung bình trả lại NA. Lúc này, ta thêm đối số na.rm = TRUE của colMeans bằng cách truyền vào đối số ... của sapply().
## 5 6 7 8 9
## Ozone 23.61538 29.44444 59.115385 59.961538 31.44828
## Solar.R 181.29630 190.16667 216.483871 171.857143 167.43333
## Wind 11.62258 10.26667 8.941935 8.793548 10.18000
Thi thoảng, ta muốn tách đối tượng R thành những cấp độ được xác định bởi hơn 1 biến. Ta có thể sử dụng hàm interaction().
## [1] 1 1 1 1 1 2 2 2 2 2
## Levels: 1 2
## [1] 1 1 2 2 3 3 4 4 5 5
## Levels: 1 2 3 4 5
## [1] 1.1 1.1 1.2 1.2 1.3 2.3 2.4 2.4 2.5 2.5
## Levels: 1.1 2.1 1.2 2.2 1.3 2.3 1.4 2.4 1.5 2.5
Việc này có thể tạo ra nhiều cấp độ mà ở đó không có dữ liệu.
## List of 10
## $ 1.1: num [1:2] 0.618 -0.505
## $ 2.1: num(0)
## $ 1.2: num [1:2] 0.298 1.081
## $ 2.2: num(0)
## $ 1.3: num 0.236
## $ 2.3: num 0.3
## $ 1.4: num(0)
## $ 2.4: num [1:2] 0.428 0.261
## $ 1.5: num(0)
## $ 2.5: num [1:2] -0.21 0.799
Lưu ý rằng có 4 nhóm/cấp độ không có dữ liệu. Để loại bỏ những cấp độ trống, ta thêm vào đối số drop = TRUE.
## List of 6
## $ 1.1: num [1:2] 0.618 -0.505
## $ 1.2: num [1:2] 0.298 1.081
## $ 1.3: num 0.236
## $ 2.3: num 0.3
## $ 2.4: num [1:2] 0.428 0.261
## $ 2.5: num [1:2] -0.21 0.799
tapply()tapply() áp dụng một hàm số vào những tập hợp con của một vector. tapply() là kết hợp giữa split() và sapply() chỉ dành cho vector.
## function (X, INDEX, FUN = NULL, ..., default = NA, simplify = TRUE)
Các đối số của tapply() bao gồm
X là vector.INDEX là factor hoặc một list của factor (hoặc có thể được ép kiểu thành factor).FUN làm hàm số được áp dụng.... chứa những đối số để truyền vào hàm FUN.simplify nhằm làm đơn giản hóa đầu ra. Được mặc định là TRUE.Đối với một vector những con số, chỉ cần một phép tính đơn giản để ra được số trung bình mỗi nhóm.
## [1] 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3
## Levels: 1 2 3
## 1 2 3
## -0.5453991 0.4677234 0.8644814
Ta có thể tính số trung bình từng nhóm mà không đơn giản hóa kết quả. Phép tính này trả về list.
## $`1`
## [1] -0.5453991
##
## $`2`
## [1] 0.4677234
##
## $`3`
## [1] 0.8644814
Ta có thể áp dụng hàm số mà trả về hơn 1 giá trị. Lúc này, tapply() sẽ không đơn giản hóa kết quả và trả về list.
## $`1`
## [1] -3.200912 1.391144
##
## $`2`
## [1] 0.1602670 0.9965533
##
## $`3`
## [1] -0.5259968 2.0865810
apply()Hàm apply() giúp đánh giá một hàm số theo lề của một array. Ta sử dụng nó để áp dụng một hàm số theo các hàng, hoặc các cột của một matrix (một dạng array 2 chiều).
## function (X, MARGIN, FUN, ...)
Các đối số của apply() bao gồm
X là một array.MARGIN là một vector số nguyên chỉ định lề nào được sử dụng. 1 là hàng, và 2 là cột.FUN là hàm số được áp dụng.... là nơi các đối số khác được truyền vào FUN.Ở đây, ta tính số trung bình của mỗi cột.
## [1] -0.02161585 -0.29224971 -0.08093589 -0.27779348 -0.17595958 -0.09126083
## [7] 0.05675241 0.10946790 -0.14345166 -0.10677869
Ta cũng có thể tính tổng của mỗi hàng.
## [1] -1.0599644 0.2033402 -1.3316518 3.9335807 -2.0345666 1.3139554
## [7] -3.8891804 -4.0311925 1.9417970 0.7039226 -1.2854550 -8.1881165
## [13] -2.5078882 4.1156069 0.1856165 -5.2364001 0.5832373 0.8171430
## [19] -5.6262757 0.9159837
Lưu ý rằng ở cả 2 lần gọi hàm apply(), đầu ra là vector số.
Ta có các cách nhanh hơn để tính tổng hoặc số trung bình theo hàng/cột.
split() có thể được sử dụng để chia đối tượng R thành những tập hợp con theo một biến số kiểu factor. Sau đó hàm vòng lặp có thể được lặp qua các tập hợp con này.