<- c("age", "sex", "bmi")
vars <- c("Tuổi", "Giới", "BMI")
varnames
print(vars)
[1] "age" "sex" "bmi"
print(varnames)
[1] "Tuổi" "Giới" "BMI"
names()
Trong ví dụ dưới đây chúng ta có tên các biến và nhãn của chúng.
<- c("age", "sex", "bmi")
vars <- c("Tuổi", "Giới", "BMI")
varnames
print(vars)
[1] "age" "sex" "bmi"
print(varnames)
[1] "Tuổi" "Giới" "BMI"
Làm thế nào để chúng ta lấy được nhãn của biến age
? Bạn sẽ cần sử dụng hàm which()
. Trước hết hãy xem hàm which()
làm gì.
which(vars == "age")
[1] 1
Như bạn đã thấy, hàm which()
trả về danh sách các vị trí trong vector thỏa mãn điều kiện được khai báo trong hàm. Vậy để lấy nhãn của biến age
, chúng ta sẽ làm như sau.
which(vars == "age")] varnames[
[1] "Tuổi"
R cung cấp một cách tham chiếu khác tới vector bằng tên gọi. Hãy “đặt tên” cho vector varnames
và xem sự khác biệt.
names(varnames) <- vars
print(varnames)
age sex bmi
"Tuổi" "Giới" "BMI"
Chúng ta có một vector trong đó mỗi phần tử có một tên gọi riêng. Bây giờ chúng ta có thể tham chiếu tới từng phần tử bằng tên gọi tương tự như dictionary trong Python. Tuy nhiên, khác với Python, tất cả các tên trong R sẽ được tự động chuyển về kiểu kí tự.
"age"] varnames[
age
"Tuổi"
Nếu muốn xóa tên, chúng ta sẽ gán cho nó giá trị NULL
.
names(varnames) <- NULL
varnames
[1] "Tuổi" "Giới" "BMI"
Bạn cũng có thể khai báo tên ngay khi tạo vector. Nếu trong tên có kí tự đặc biệt không phải chữ cái, số, và dấu gạch nối (_
), bạn có thể bao chúng trong cặp dấu ngoặc kép (""
) hoặc phẩy trên trái (\
``).
c(
hb_d0 = "Hemoglobin (baseline)",
"rbc d1" = "Red blood cell (day 1)",
`plt d2` = "Platelet (day 2)"
)
hb_d0 rbc d1 plt d2
"Hemoglobin (baseline)" "Red blood cell (day 1)" "Platelet (day 2)"
Bạn không cần phải gõ tên cho từng phần tử nếu các phần tử có tên tuân theo một quy luật. Chẳng hạn, chúng ta có một danh sách các mốc thời gian, và tên gọi của mốc thời gian “X” là “Ngày X”.
<- seq_len(7)
timepoints names(timepoints) <- paste0("Ngày ", timepoints)
timepoints
Ngày 1 Ngày 2 Ngày 3 Ngày 4 Ngày 5 Ngày 6 Ngày 7
1 2 3 4 5 6 7
Quay trở lại với ví dụ đầu tiên, làm sao để lấy được danh sách nhãn của một số biến?
<- c("age", "bmi")
vars_subset %in% vars_subset] varnames[vars
[1] "Tuổi" "BMI"
Với tên gọi, bạn có thể gọi thẳng trực tiếp thay vì dùng toán tử matching %in%
.
names(varnames) <- vars
varnames[vars_subset]
age bmi
"Tuổi" "BMI"
Bạn cũng có thể đặt cho danh sách. Thông thường chúng ta đặt tên cho danh sách trong lúc khai báo danh sách.
list(
id = c(1, 2, 3),
initials = c("PKL", "LHS", "MTNN")
)
$id
[1] 1 2 3
$initials
[1] "PKL" "LHS" "MTNN"
Nếu danh sách không có tên (ví dụ, được tạo ra từ hàm lặp), bạn có thể dùng hàm names()
để đặt tên.
<- c(1, 4, 2)
no_reps <- lapply(no_reps, function(x) rep(1, x))
results print(results)
[[1]]
[1] 1
[[2]]
[1] 1 1 1 1
[[3]]
[1] 1 1
names(results) <- no_reps
print(results)
$`1`
[1] 1
$`4`
[1] 1 1 1 1
$`2`
[1] 1 1
Hoặc chúng ta đặt tên cho vector no_reps
trước. Danh sách trả về sẽ sử dụng tên của vector này.
names(no_reps) <- c("1 element", "4 elements", "2 elements")
lapply(no_reps, function(x) rep(1, x))
$`1 element`
[1] 1
$`4 elements`
[1] 1 1 1 1
$`2 elements`
[1] 1 1
Hãy xem một ví dụ “nâng cao” để thấy giá trị của việc đặt tên. Trong ví dụ này, chúng ta sẽ viết một hàm để thống kê theo nhiều cách khác nhau, cách thức thống kê sẽ được quy định đối số method
của hàm.
<- function(data, method = "mean") {
get_stats switch(method,
"mean" = sprintf("%.2f (%.2f)", mean(data, na.rm = TRUE), sd(data, na.rm = TRUE)),
"median" = sprintf("%.2f [%.2f, %.2f]", median(data, na.rm = TRUE), quantile(data, .25, na.rm = TRUE), quantile(data, .75, na.rm = TRUE)),
"minmax" = sprintf("%.2f-%.2f", min(data, na.rm = TRUE), max(data, na.rm = TRUE)),
)
}
set.seed(0)
<- rgamma(100, 2, 1)
data
get_stats(data)
[1] "1.85 (1.10)"
get_stats(data, "median")
[1] "1.63 [1.01, 2.55]"
get_stats(data, "minmax")
[1] "0.15-5.34"
Thay vì việc gọi hàm get_stats()
3 lần, chúng ta có thể sử dụng vòng lặp để cung cấp thông tin tự động cho đối số method
.
<- c("mean", "minmax", "median")
methods sapply(methods, function(x) get_stats(data, x))
mean minmax median
"1.85 (1.10)" "0.15-5.34" "1.63 [1.01, 2.55]"
Hàm này còn có thể mạnh hơn nữa khi lặp lại trên nhiều biến.
<- data.frame(
data2 d0 = rgamma(100, 2, 1),
d1 = rgamma(100, 3, 1.2),
d2 = rgamma(100, 3.5, 2)
)
print(head(data2))
d0 d1 d2
1 2.4998077 1.560672 1.1883247
2 1.4112553 2.871651 2.6535716
3 0.3700219 0.856785 1.8037974
4 1.1291394 3.366226 0.5186375
5 0.9739632 4.965208 2.3719618
6 2.2215693 2.454356 1.4778128
<- c("d0", "d1", "d2")
vars_to_get_stats sapply(vars_to_get_stats,
function(x) sapply(methods, function(y) get_stats(data2[[x]], y))
|> t() |> as.data.frame() # Đổi hàng và cột cho nhau )
mean minmax median
d0 1.85 (1.39) 0.17-5.66 1.46 [0.81, 2.53]
d1 2.56 (1.41) 0.35-7.07 2.44 [1.47, 3.52]
d2 1.74 (1.09) 0.20-6.61 1.44 [1.00, 2.27]
Bạn có thể đi xa thêm một bước bằng cách đặt tên cho các vector methods
và vars_to_get_stats
. Chúng ta sẽ có một bảng tổng kết với tên gọi hoàn chỉnh sẵn sàng cho việc xuất bản.
names(methods) <- c("Mean (SD)", "Min-Max", "Median [Q1, Q3]")
names(vars_to_get_stats) <- c("Trước mổ", "Sau mổ - ngày 1", "Sau mổ - ngày 2")
sapply(vars_to_get_stats,
function(x) sapply(methods, function(y) get_stats(data2[[x]], y))
|> t() |> knitr::kable() )
Mean (SD) | Min-Max | Median [Q1, Q3] | |
---|---|---|---|
Trước mổ | 1.85 (1.39) | 0.17-5.66 | 1.46 [0.81, 2.53] |
Sau mổ - ngày 1 | 2.56 (1.41) | 0.35-7.07 | 2.44 [1.47, 3.52] |
Sau mổ - ngày 2 | 1.74 (1.09) | 0.20-6.61 | 1.44 [1.00, 2.27] |
Nếu đã làm việc với thư viện dplyr
, chắc hẳn bạn đã từng biết cú pháp của hàm rename()
: rename(<tên_mới> = <tên_cũ>)
. Cá nhân mình thấy đây là một cú pháp ngớ ngẩn (logic thông thường là <tên_cũ> = <tên_mới>
), nhưng dù là cú pháp nào thì bạn sẽ phải gõ bằng tay. Để tự động hóa việc này, chúng ta có thể sử dụng toán tử “ba chấm than” (!!!
) để “giải nén” một vector có tên, tương tự như kĩ thuật dictionary unpacking (**kwargs
) trong Python.
library(dplyr)
Attaching package: 'dplyr'
The following objects are masked from 'package:stats':
filter, lag
The following objects are masked from 'package:base':
intersect, setdiff, setequal, union
%>% rename(!!!vars_to_get_stats) %>% head() data2
Trước mổ Sau mổ - ngày 1 Sau mổ - ngày 2
1 2.4998077 1.560672 1.1883247
2 1.4112553 2.871651 2.6535716
3 0.3700219 0.856785 1.8037974
4 1.1291394 3.366226 0.5186375
5 0.9739632 4.965208 2.3719618
6 2.2215693 2.454356 1.4778128
Tương tự, bạn có thể thiết kế data dictionary để recode cho biến (sử dụng hàm dplyr::recode()
).
<- data.frame(
datadict var = c("sex", "sex", "has_insurance", "has_insurance"),
code = c(1, 2, 0, 1),
value = c("Female", "Male", "No", "Yes")
)
datadict
var code value
1 sex 1 Female
2 sex 2 Male
3 has_insurance 0 No
4 has_insurance 1 Yes
<- data.frame(
d sex = sample(c(1, 2), 10, replace = TRUE),
has_insurance = sample(c(0, 1), 10, replace = TRUE)
%>% tibble::as_tibble()
)
d
# A tibble: 10 × 2
sex has_insurance
<dbl> <dbl>
1 2 1
2 2 0
3 2 0
4 2 0
5 1 1
6 1 1
7 2 1
8 2 0
9 1 1
10 1 0
<- function(d, datadict, varname) {
recode_var # Lập từ điển recode cho biến
<- datadict %>% filter(var == varname) %>% pull(code)
codes <- datadict %>% filter(var == varname) %>% pull(value)
values names(values) <- codes
# Recode cho biến
# Lưu ý: cú pháp của recode() ngược với rename()
# recode(<giá_trị_cũ> = <giá_trị_mới>)
%>% mutate(!!varname := recode_factor(!!sym(varname), !!!values))
d
}
recode_var(d, datadict, "sex")
# A tibble: 10 × 2
sex has_insurance
<fct> <dbl>
1 Male 1
2 Male 0
3 Male 0
4 Male 0
5 Female 1
6 Female 1
7 Male 1
8 Male 0
9 Female 1
10 Female 0