“Học, học nữa, học mãi.”
V. I. Lenin

Vectơ, ma trận và mảng rất hữu hiệu và thuận tiện trong việc lưu trữ dữ liệu nhưng chúng chỉ cho phép lưu trữ dữ liệu cùng kiểu. Chương này sẽ trình bày hai cấu trúc dữ liệu mới gọi là list và data frame. Chúng cho phép lưu trữ nhiều kiểu dữ liệu cùng lúc.

Danh sách (list) các đối tượng

List là một dạng cấu trúc dữ liệu cực kỳ hữu ích trong R. List có thể chứa vectơ, ma trận, mảng, chuỗi ký tự, factor và có thể chứa list khác. Để tạo list chúng ta dùng hàm list().

lst <- list(matrix(data = 1:4, nrow = 2, ncol = 2), c(T, F, T, T), "hello")
lst
## [[1]]
##      [,1] [,2]
## [1,]    1    3
## [2,]    2    4
## 
## [[2]]
## [1]  TRUE FALSE  TRUE  TRUE
## 
## [[3]]
## [1] "hello"

Chúng ta có thể tính độ dài, trích xuất và thay thế các phần tử của list.

length(lst)
## [1] 3
lst[[1]]
##      [,1] [,2]
## [1,]    1    3
## [2,]    2    4
lst[[3]]
## [1] "hello"
lst[[1]][1,2]
## [1] 3
lst[[1]][2,]
## [1] 2 4
lst[c(2,3)]
## [[1]]
## [1]  TRUE FALSE  TRUE  TRUE
## 
## [[2]]
## [1] "hello"

Chúng ta có thể đặt tên cho các phần tử của list bằng cách dùng hàm names() hoặc đặt tên trực tiếp khi tạo list.

names(lst) <- c("matran", "logic", "chuoi") 
lst
## $matran
##      [,1] [,2]
## [1,]    1    3
## [2,]    2    4
## 
## $logic
## [1]  TRUE FALSE  TRUE  TRUE
## 
## $chuoi
## [1] "hello"
lst_moi <- list(matran = matrix(data = 1:4, nrow = 2, ncol = 2), logic = c(T, F, T, T), chuoi = "hello")
lst_moi
## $matran
##      [,1] [,2]
## [1,]    1    3
## [2,]    2    4
## 
## $logic
## [1]  TRUE FALSE  TRUE  TRUE
## 
## $chuoi
## [1] "hello"

Lúc này chúng ta cũng có thể trích xuất các phần tử của list theo tên của chúng.

lst$matran
##      [,1] [,2]
## [1,]    1    3
## [2,]    2    4
lst$chuoi
## [1] "hello"

Chúng ta có thể thêm phần tử của list là một list khác bằng cách:

lst$danhsach = list(matran1 = matrix(data = 11:14, nrow = 2, ncol = 2), logic1 = c(F, F, F, F), chuoi1 = "xin chao")
lst
## $matran
##      [,1] [,2]
## [1,]    1    3
## [2,]    2    4
## 
## $logic
## [1]  TRUE FALSE  TRUE  TRUE
## 
## $chuoi
## [1] "hello"
## 
## $danhsach
## $danhsach$matran1
##      [,1] [,2]
## [1,]   11   13
## [2,]   12   14
## 
## $danhsach$logic1
## [1] FALSE FALSE FALSE FALSE
## 
## $danhsach$chuoi1
## [1] "xin chao"
lst$danhsach$chuoi1
## [1] "xin chao"

Bài tập 5.1.

  1. Hãy tạo một list theo thứ tự gồm một dãy 20 số cách đều nhau giữa \(-4\)\(4\), một ma trận \(3\times 3\) từ vectơ logic c(F, T, T, T, F, T, T, F, F) điền theo cột, một vectơ ký tự gồm 2 chuỗi "vladimir""lenin", và một factor chứa c("Thap", "Vua", "Thap", "Vua", "Vua", "Cao"). Từ list vừa tạo hãy:
    1. Trích các phần tử ở dòng 2 và 1, cột 2 và 3 của ma trận logic.
    2. Trích tất cả các giá trị lớn hơn \(1\) của dãy số giữa \(-4\)\(4\).
    3. Xác định chỉ số vị trí phần tử Vua của vectơ factor.
    4. Sử dụng hàm sub() để thay thế "vladimir""lenin" thành "Vladimir""Lenin". Sau đó hãy xuất ra màn hình câu nói như sau:
Học, học nữa, học mãi!
    -\Vladimir Lenin/-
  1. Tạo một list mới gồm: vectơ factor từ (a) đặt tên là facs, vectơ số c(3,2.1,3.3,4,1.5,4.9) đặt tên là nums, và một list chứa 3 phần tử đầu của list câu (a) đặt tên là oldlist. Từ list mới tạo hãy:
    1. Trích các phần tử của facs tương ứng với các phần tử lớn hơn hoặc bằng 3 của nums.
    2. Thêm một phần tử mới vào list đặt tên là flags. Phần tử này là vectơ logic có độ dài là 6 thu được từ việc lập lại 2 lần cột thứ 3 của ma trận logic của phần tử oldlist.
    3. Thay thế vectơ chuỗi ký tự của oldlist bằng chuỗi ký tự đơn "Vladimir Lenin".

Data frame

Data frame là một dạng cấu trúc dữ liệu tự nhiên nhất để lưu trữ tập dữ liệu chứa một hay nhiều biến. Chúng ta có thể xem data frame là một trường hợp đặc biệt của list trong đó tất cả các thành phần đều là vectơ có cùng độ dài. Để tạo data frame chúng ta dùng hàm data.frame(). Chúng ta sẽ dùng data frame để lưu trữ tập dữ liệu sau.
Tên Giới tính Tuổi
Lan Nữ 42
Hoa Nữ 40
Huệ Nữ 17
Tài Nam 14
Tâm Nữ 1
mydf <- data.frame(
  Tên = c("Lan", "Hoa", "Huệ", "Tài", "Tâm"),
  Giới.tính = factor(c("Nữ", "Nữ", "Nữ", "Nam", "Nữ")),
  Tuổi = c(42,40,17,14,1)
)
mydf
##   Tên Giới.tính Tuổi
## 1 Lan        Nữ   42
## 2 Hoa        Nữ   40
## 3 Huệ        Nữ   17
## 4 Tài       Nam   14
## 5 Tâm        Nữ    1

Vì data frame là trường hợp đặc biệt của list nên chúng ta có thể trích xuất các thành phần của data frame tương tự như list.

mydf[[2]]
## [1] Nữ  Nữ  Nữ  Nam Nữ 
## Levels: Nam Nữ
mydf$Giới.tính
## [1] Nữ  Nữ  Nữ  Nam Nữ 
## Levels: Nam Nữ
mydf$Giới.tính[4]
## [1] Nam
## Levels: Nam Nữ

Mặt khác, vì data frame cũng có dòng và cột khá giống ma trận nên chúng ta có thể trích xuất các thành phần của data frame giống như ma trận.

mydf[, 2]
## [1] Nữ  Nữ  Nữ  Nam Nữ 
## Levels: Nam Nữ
mydf[, c(1, 2)]
##   Tên Giới.tính
## 1 Lan        Nữ
## 2 Hoa        Nữ
## 3 Huệ        Nữ
## 4 Tài       Nam
## 5 Tâm        Nữ
mydf[4, 2]
## [1] Nam
## Levels: Nam Nữ
nrow(mydf)
## [1] 5
ncol(mydf)
## [1] 3
dim(mydf)
## [1] 5 3

Đối với data frame, mỗi dòng là dữ liệu của một đối tượng nghiên cứu.

mydf[1,]
##   Tên Giới.tính Tuổi
## 1 Lan        Nữ   42
mydf[3,]
##   Tên Giới.tính Tuổi
## 3 Huệ        Nữ   17

Chúng ta có thể thêm dòng (thêm đối tượng nghiên cứu) hoặc thêm cột (thêm biến) vào data frame bằng cách dùng hàm rbind()cbind() tương ứng.

doi.tuong.moi <- data.frame(
  Tên = "Tín",
  Giới.tính = factor("Nam", levels = levels(mydf$Giới.tính)),
  Tuổi = 7
)
doi.tuong.moi
##   Tên Giới.tính Tuổi
## 1 Tín       Nam    7
mydf <- rbind(mydf,doi.tuong.moi)
mydf
##   Tên Giới.tính Tuổi
## 1 Lan        Nữ   42
## 2 Hoa        Nữ   40
## 3 Huệ        Nữ   17
## 4 Tài       Nam   14
## 5 Tâm        Nữ    1
## 6 Tín       Nam    7
Tháng.sinh <- c("Tháng 4", "Tháng 1", "Tháng 1", "Tháng 9", "Tháng 11", "Tháng 7")
Tháng.sinh <- factor(Tháng.sinh, levels = c("Tháng 1", "Tháng 2", "Tháng 3", "Tháng 4", "Tháng 5", "Tháng 6", "Tháng 7", "Tháng 8", "Tháng 9", "Tháng 10", "Tháng 11", "Tháng 12" ), ordered = T)
Tháng.sinh
## [1] Tháng 4  Tháng 1  Tháng 1  Tháng 9  Tháng 11 Tháng 7 
## 12 Levels: Tháng 1 < Tháng 2 < Tháng 3 < Tháng 4 < Tháng 5 < ... < Tháng 12
mydf <- cbind(mydf, Tháng.sinh)
mydf
##   Tên Giới.tính Tuổi Tháng.sinh
## 1 Lan        Nữ   42    Tháng 4
## 2 Hoa        Nữ   40    Tháng 1
## 3 Huệ        Nữ   17    Tháng 1
## 4 Tài       Nam   14    Tháng 9
## 5 Tâm        Nữ    1   Tháng 11
## 6 Tín       Nam    7    Tháng 7

Chúng ta cũng có thể thêm biến vào data frame giống như thêm thành phần của list như trước đây.

mydf$Sự.hài.hước <- factor(c("Cao", "Cao", "Thấp", "Vừa", "Cao", "Vừa"), levels = c("Thấp", "Vừa", "Cao"), ordered = T)
mydf
##   Tên Giới.tính Tuổi Tháng.sinh Sự.hài.hước
## 1 Lan        Nữ   42    Tháng 4         Cao
## 2 Hoa        Nữ   40    Tháng 1         Cao
## 3 Huệ        Nữ   17    Tháng 1        Thấp
## 4 Tài       Nam   14    Tháng 9         Vừa
## 5 Tâm        Nữ    1   Tháng 11         Cao
## 6 Tín       Nam    7    Tháng 7         Vừa

Chúng ta có thể trích xuất các thành phần của data frame thỏa điều kiện cho trước.

mydf[mydf$Giới.tính == "Nam",]
##   Tên Giới.tính Tuổi Tháng.sinh Sự.hài.hước
## 4 Tài       Nam   14    Tháng 9         Vừa
## 6 Tín       Nam    7    Tháng 7         Vừa
mydf[mydf$Giới.tính == "Nam", -3]
##   Tên Giới.tính Tháng.sinh Sự.hài.hước
## 4 Tài       Nam    Tháng 9         Vừa
## 6 Tín       Nam    Tháng 7         Vừa
mydf[mydf$Giới.tính == "Nam", c("Tên", "Tuổi", "Sự.hài.hước")]
##   Tên Tuổi Sự.hài.hước
## 4 Tài   14         Vừa
## 6 Tín    7         Vừa
mydf[mydf$Tuổi > 10 | mydf$Sự.hài.hước == "Cao",]
##   Tên Giới.tính Tuổi Tháng.sinh Sự.hài.hước
## 1 Lan        Nữ   42    Tháng 4         Cao
## 2 Hoa        Nữ   40    Tháng 1         Cao
## 3 Huệ        Nữ   17    Tháng 1        Thấp
## 4 Tài       Nam   14    Tháng 9         Vừa
## 5 Tâm        Nữ    1   Tháng 11         Cao
mydf[mydf$Tuổi > 45,]
## [1] Tên         Giới.tính   Tuổi        Tháng.sinh  Sự.hài.hước
## <0 rows> (or 0-length row.names)

Bài tập 5.2.

  1. Tạo và đặt tên data frame sau là dframe, trong đó tên là vectơ chuỗi ký tự, giới tính và sự hài hước là vectơ factor.
    Tên Giới tính Sự hài hước
    Tình Nam Cao
    An Nữ Vừa
    Bảo Nam Thấp
    Phúc Nam Cao
    Thảo Nữ Vừa
    Đại Nam Vừa
  2. Thêm biến tuổi của các đối tượng vào dframe như sau: Lan và Hoa 41 tuổi, Huệ 15 tuổi, Tâm 21 tuổi, Tín 60 tuổi, Tài 1600 tuổi.
  3. Sắp xếp các cột của dframe theo thứ tự tên, giới tính, tuổi và sự hài hước.
  4. Tạo ra một data frame mới tên mydf2 từ mydf bằng cách loại bỏ biến tháng sinh.
  5. Kết hợp dframemydf2 thành data frame mới tên mydframe.
  6. Viết một dòng code xuất ra tên và tuổi của các đối tượng là nữ và có độ hài hước là vừa hoặc cao.
  7. Từ mydframe, trích ra thông tin các đối tượng có tên bắt đầu bằng chữ T. Hướng dẫn: sử dụng hàm substr().

Tóm tắt

Hàm/Toán tử Ý nghĩa
list() Tạo list
[[ ]] Trích một thành phần của list theo chỉ số vị trí
[c()] Trích nhiều thành phần của list theo chỉ số vị trí
$ Trích thành phần theo tên
data.frame() Tạo data frame
[ , ] Trích data frame theo dòng và cột

Tài liệu tham khảo

Davies, Tilman M. 2016. The Book of R: A First Course in Programming and Statistics. No Starch Press.