Bài giảng này giới thiệu một số hàm cơ bản của thư viện dplyr cho mục đích làm sạch - chuẩn bị dữ liệu. Các hàm cơ bản này cover hầu hết các tình huống thường gặp trong thực tế về làm sạch và chuẩn bị dữ liệu với một bộ dữ liệu từ Department of Health Economic - HMU.
Bộ dữ liệu là một file SPSS và để đọc bộ dữ liệu này vào R chúng ta sử dụng hàm read_sav() của thư viện haven như sau:
# Clear R Environment:
rm(list = ls()) # Should use this command at the beginning of any project.
# Load haven package:
library(haven)
# Import data:
stigma_data <- read_sav("C:/Users/Admin/Documents/[ver1.3]NC2.QOJ.Stigma.sav")Lúc này ở cửa sổ có tên Global Environment ở bên phải, góc phía trên sẽ hiển thị một object có tên là stigma_data. Object này, theo cách gọi của R Users, là một Data Frame. Đến đây chúng ta có thể bắt đầu “chơi” với bộ dữ liệu này.
Tuy nhiên, trước khi chơi với dữ liệu này người học PHẢI (chữ này được được viết hoa với hàm ý nhấn mạnh) nắm được một số luật chơi cơ bản của ngôn ngữ R như sau:
Trước hết là các quy tắc trình bày codes đúng cách. R Codes viết những phải đúng cú pháp (syntax) mà còn phải tuân thủ cách trình bày đúng như người viết văn phải tuân thủ các quy tắc chính tả vậy. Những quy tắc này người học phải TỰ học/tham khảo tại đây.
Không sử dụng lệnh attach(). Một số tài liệu về R (như cuốn của tác giả NVT) thường xuyên sử dụng lệnh này. Cá nhân tôi lại cho rằng việc sử dụng lệnh này “làm hỏng” cách tư duy và làm việc với các ngôn ngữ OPP như R.
Sử dụng phép gán. Phép gán của ngôn ngữ R có hai cách biểu diễn. Cách thứ nhất là sử dụng nũi tên đi từ phải sang trái, kí hiệu <- như sau:
# An example of assignment - method 1:
stigma_data <- read_sav("C:/Users/Admin/Documents/[ver1.3]NC2.QOJ.Stigma.sav")Hoặc sử dụng mũi tên theo chiều ngược lại (cách thứ hai) như sau:
# An example of assignment - method 2:
read_sav("C:/Users/Admin/Documents/[ver1.3]NC2.QOJ.Stigma.sav") -> stigma_dataĐể tính y khi x = 10 chúng ta có thể làm tuần tự từng bước như sau:
# Define x:
x <- 10
# Calculate sin(x):
value_sin10 <- sin(x)
# Calculate value of y:
y <- tan(value_sin10)
# Print final result:
y## [1] -0.6049088
Kết quả -0.6049088 này có thể được tính toán với một cách trình bày khác với toán tử Pipe như sau:
# You must load magrittr package for using pipe operator:
library(magrittr)
# Calculate y by using pipe operator:
y_by_pipe <- x %>% sin() %>% tan()
# Alternative coding style with -> assignment:
x %>% sin() %>% tan() -> y_by_pipe. Ở các máy tính sử dụng hệ điều hành Window thì phím tắt cho toán tử pipe là tổ hợp phím Ctrl + Shift + M. Còn phím tắt cho -> là tổ hợp phím Alt + -.
Kiêm tra và so sánh y với y_by_pipe:
y_by_pipe## [1] -0.6049088
Đoạn mã lệnh x %>% sin() %>% tan() có thể được diễn giải như sau:
Nếu chúng ta muốn, chẳng hạn, lấy giá trị tuyệt đối của y thì:
x %>% sin() %>% tan() %>% abs()## [1] 0.6049088
Còn muốn khai căn bậc hai kết quả trên thì:
x %>% sin() %>% tan() %>% abs() %>% sqrt()## [1] 0.7777588
Nếu không sử dụng toán tử pipe thì kết quả 0.7777588 ở trên được trình bày như sau:
sqrt(abs(tan(sin(x))))## [1] 0.7777588
Cách trình bày là là khó đọc, khó theo dõi. Do vậy, bốn quy ước/luật chơi ở trên cần hiểu - nắm vững trước khi đi tiếp cuộc hành trình với R.
Hiểu sơ bộ dữ liệu đang xử lí là điều cần thiết. Chúng ta có thể xem dữ liệu bằng lệnh View():
# Use pipe operator:
stigma_data %>% View()
# Or not using pipe operator:
View(stigma_data)Kết quả như ta thấy dưới đây:
Để xem số dòng, số cột của bộ dữ liệu chúng ta có thể sử dụng một trong ba lệnh sau:
# Show number of rows:
nrow(stigma_data)## [1] 1121
# Show number of columns:
ncol(stigma_data)## [1] 153
# Show both:
dim(stigma_data)## [1] 1121 153
Your Assignment: Sử dụng pipe operator tính toán số dòng, số cột của stigma_data.
Chúng ta có thê xem 6 quan sát đầu/cuối của bộ dữ liệu:
head(stigma_data) # The first observations.
tail(stigma_data) # The last observations. Để “extract” tên các cột biến của Data Frame, sử dụng lệnh names() như sau:
column_names <- names(stigma_data)Để biết dạng dữ liệu/cấu trúc dữ liệu, ví dụ, của object có tên column_names chúng ta có thể sử dụng lệnh str():
str(column_names)Kết quả sẽ là chr [1:153] “STT” … hàm ý rằng object này có kiểu dữ liệu là text/character - kí hiệu là chr. Còn phần [1:153] có nghĩa là text này gồm 153 phần tử - chính là số cột của bộ dữ liệu.
Your Assignment: Sử dụng lệnh str() cho stigma_data và đọc hiểu kết quả.
Object có tên column_names ở trên được gọi là một vector. Cụ thể hơn có thể gọi là vector text - vì dữ dạng dữ liệu text của nó. Chúng ta có thể accesss vào từng phần tử của một vector theo vị trí, chẳng hạn, vị trí số 1 của nó như sau:
column_names[1]## [1] "STT"
Đây chính là tên của cột biến thứ nhất. Còn muốn access vào vị trí thứ 7:
column_names[7]## [1] "BAge"
Access vào vị các vị trí liên tiếp từ 7 đến 12:
column_names[7:11]## [1] "BAge" "BMartialstatus" "BEducationallevel"
## [4] "Professional" "Position"
Your Assignment: Lệnh column_names[200] sẽ trả về kết quả là NA. Hãy giải thích tại sao?
Trước hết cần biết ý nghĩa của các cột biến là điều cần thiết. Sử dụng lệnh View() như ở trên chúng ta có thể biết, ví dụ, cột biến có tên A2 có nghĩa là “Tên đơn vị làm việc”. Tuy nhiên chúng ta có thể khai thác nhiều thông tin hơn về một cột biến cụ thể bằng lệnh attributes() như sau:
# Extract description for a given column:
stigma_data$A2 %>% attributes() -> des_for_A2
# Show description:
des_for_A2Cho cột biến BSex:
# Extract description for BSex:
stigma_data$BSex %>% attributes() -> des_for_BSex
# Show description:
des_for_BSexĐối với tình huống của BSex thì ngoài các miêu tả như đã có cho A2 chúng ta còn biết rằng giá trị 1 ở cột biến BSex là Nam và 2 là Nữ. Như thế cột biến BSex thuộc loại categorical - kiểu dữ liệu giống A2. Như vậy các giá trị 1 và 2 của cột biến này chỉ mang tính hình thức: không nên hiểu sai rằng BSex là biến numeric, và do vậy mọi phép tính với cột biến này, như lấy trung bình là không có ý nghĩa.
Hai objects (des_for_A2 và des_for_BSex) thuộc một kiểu tổ chức dữ liệu là list. Chúng ta có thể khai thác list thông qua kí hiệu $ như sau:
# Extract label:
des_for_BSex$labels## Nam N<U+1EEF>
## 1 2
# Extract description for column:
des_for_BSex$label## [1] "Gi<U+1EDB>i tính"
# Extract data type:
des_for_BSex$class## [1] "haven_labelled" "vctrs_vctr" "double"
Một lần nữa nhắc lại rằng mặc dù thông tin về kiểu dữ liệu là “double” nhưng đây chỉ mang ý nghĩa hình thức mà thôi: cột biến BSex là categorical - loại dữ liệu chỉ có ý nghĩa phân biệt một quan sát có phải là Nam/Nữ mà thôi.
Với những hiểu biết cơ bản về bộ số liệu như trên chúng ta có thể thực hiện công đoạn làm sạch và chuẩn bị dữ liệu với các hàm của thư viện/gói dplyr.
Lệnh này của dplyr cho phép “lựa chọn” cột biến theo những cách thức sau:
# Load dplyr package:
library(dplyr)
# Method 1:
stigma_data %>% select(E1, B9) -> df1_select_m1
# Method 2:
some_columns <- c("E1", "B9")
stigma_data %>% select(some_columns) -> df_select_m2stigma_data %>% select(1, 7) -> df1_select_positioned# Only select numeric columns:
stigma_data %>% select_if(is.numeric) -> df_only_numericstigma_data %>% select(contains("hailong")) -> df_contains_hailongstigma_data %>% select(-STT) -> stigma_data_not_STTYour Assignment: Hãy xóa đồng thời hai cột biến là STT và BAge.
Như tên của lệnh này ngụ ý: đổi tên cho cột biến. Lệnh này sử dụng theo cú pháp như sau:
# Rename for STT:
df1_select_positioned %>% rename(so_thu_tu = STT)## # A tibble: 1,121 x 2
## so_thu_tu BAge
## <dbl> <dbl>
## 1 1 43
## 2 2 30
## 3 3 30
## 4 4 32
## 5 5 49
## 6 6 46
## 7 7 37
## 8 8 31
## 9 9 30
## 10 10 34
## # ... with 1,111 more rows
# Rename for both:
df1_select_positioned %>% rename(so_thu_tu = STT, tuoi = BAge)## # A tibble: 1,121 x 2
## so_thu_tu tuoi
## <dbl> <dbl>
## 1 1 43
## 2 2 30
## 3 3 30
## 4 4 32
## 5 5 49
## 6 6 46
## 7 7 37
## 8 8 31
## 9 9 30
## 10 10 34
## # ... with 1,111 more rows
Một cách thức khác để đổi tên cho cả hai cột biến (và rất tiện dụng trong nhiều trường hợp) như sau:
# Prepare new names:
new_names <- c("so_thu_tu", "tuoi")
# Rename for df1_select_positioned:
names(df1_select_positioned) <- new_names
# Check:
df1_select_positioned %>% head()## # A tibble: 6 x 2
## so_thu_tu tuoi
## <dbl> <dbl>
## 1 1 43
## 2 2 30
## 3 3 30
## 4 4 32
## 5 5 49
## 6 6 46
Bộ dữ liệu gốc có cả tên tiếng Anh (ngôn ngữ không dấu) và tiếng Việt (có dấu) lẫn lộn nhau. Cái này gọi là Inconsistency - một điều tệ nên tránh. Chúng ta có thể “chuẩn hóa” bằng cách đổi tên cho tất cả về chữ không dấu như sau:
# Load stringi package:
library(stringi)
# Convert to Latin character:
stri_trans_general(column_names, "Latin-ASCII") -> column_names_latin
# Rename for all columns:
names(stigma_data) <- column_names_latinLúc này chúng ta có thể xem 6 tên cột biến đầu tiên như sau:
stigma_data %>% names() %>% head()## [1] "STT" "Dauthoigian" "A1" "A2"
## [5] "AEconomicarea" "BSex"
Sử dụng lệnh này để:
stigma_data %>% filter(BAge > 30) -> df_age_over30stigma_data %>% filter(BAge > 30, BSex == 1) -> df_age_over30_sex1stigma_data %>%
filter(A2 == "Bệnh viện Tâm thần tỉnh Nghệ An") -> df_nghean# Load stringr package:
library(stringr)
# Observations contain "Nghệ An":
stigma_data %>%
filter(str_detect(A2, "Nghệ An")) -> df_only_ngheandf1_select_positioned %>%
filter(is.na(tuoi))## # A tibble: 1 x 2
## so_thu_tu tuoi
## <dbl> <dbl>
## 1 765 NA
Kết quả này cho ta biết quan sát ở dòng thứ 756 không có dữ liệu về tuổi. Nguyên nhân có thể là do sai sót của khâu nhập dữ liệu. Ngược lại, nếu chúng ta muốn lấy ra các quan sát mà có dữ liệu về tuổi thì:
df1_select_positioned %>%
filter(!is.na(tuoi))## # A tibble: 1,120 x 2
## so_thu_tu tuoi
## <dbl> <dbl>
## 1 1 43
## 2 2 30
## 3 3 30
## 4 4 32
## 5 5 49
## 6 6 46
## 7 7 37
## 8 8 31
## 9 9 30
## 10 10 34
## # ... with 1,110 more rows
Sử dụng lệnh này để:
# Create a new column tuoi2 from df1_select_positioned:
df1_select_positioned %>%
mutate(tuoi2 = tuoi - 2)## # A tibble: 1,121 x 3
## so_thu_tu tuoi tuoi2
## <dbl> <dbl> <dbl>
## 1 1 43 41
## 2 2 30 28
## 3 3 30 28
## 4 4 32 30
## 5 5 49 47
## 6 6 46 44
## 7 7 37 35
## 8 8 31 29
## 9 9 30 28
## 10 10 34 32
## # ... with 1,111 more rows
# Or create two new columns:
df1_select_positioned %>%
mutate(tuoi2 = tuoi - 1, stt2 = so_thu_tu*so_thu_tu)## # A tibble: 1,121 x 4
## so_thu_tu tuoi tuoi2 stt2
## <dbl> <dbl> <dbl> <dbl>
## 1 1 43 42 1
## 2 2 30 29 4
## 3 3 30 29 9
## 4 4 32 31 16
## 5 5 49 48 25
## 6 6 46 45 36
## 7 7 37 36 49
## 8 8 31 30 64
## 9 9 30 29 81
## 10 10 34 33 100
## # ... with 1,111 more rows
# Drop so_thu_tu column:
df1_select_positioned %>%
mutate(so_thu_tu = NULL)## # A tibble: 1,121 x 1
## tuoi
## <dbl>
## 1 43
## 2 30
## 3 30
## 4 32
## 5 49
## 6 46
## 7 37
## 8 31
## 9 30
## 10 34
## # ... with 1,111 more rows
Lệnh này để, ví dụ, lấy ra 5 quan sát mà tuổi là cao nhất:
df1_select_positioned %>%
top_n(n = 5, wt = tuoi)## # A tibble: 10 x 2
## so_thu_tu tuoi
## <dbl> <dbl>
## 1 310 60
## 2 419 59
## 3 450 67
## 4 510 59
## 5 545 63
## 6 564 59
## 7 622 59
## 8 669 59
## 9 945 60
## 10 981 59
Lệnh này không nhất thiết phải lấy ra đúng 5 quan sát có tuổi cao nhất. Các quan sát có tuổi như nhau (59 chẳng hạn) thì đều được chọn.
Sử dụng lệnh này để sắp xếp lại các quan sát theo chiều tăng dần của, ví dụ, tuổi:
df1_select_positioned %>%
arrange(tuoi)## # A tibble: 1,121 x 2
## so_thu_tu tuoi
## <dbl> <dbl>
## 1 636 21
## 2 698 22
## 3 738 22
## 4 741 22
## 5 912 22
## 6 171 23
## 7 550 23
## 8 602 23
## 9 733 23
## 10 848 23
## # ... with 1,111 more rows
Còn nếu sắp xếp theo chiều giảm dần của tuổi:
df1_select_positioned %>%
arrange(-tuoi)## # A tibble: 1,121 x 2
## so_thu_tu tuoi
## <dbl> <dbl>
## 1 450 67
## 2 545 63
## 3 310 60
## 4 945 60
## 5 419 59
## 6 510 59
## 7 564 59
## 8 622 59
## 9 669 59
## 10 981 59
## # ... with 1,111 more rows
Nếu biến được chọn để sắp xếp là categorical thì thứ tự xuất hiện sẽ theo trật tự của alphabet. Ví dụ là sắp xếp lại thứ tự các quan sát theo A2- tức là nơi làm việc người được khảo sát:
stigma_data %>%
arrange(A2)Lệnh này cho phép lấy ra một số quan sát theo vị trí của dòng. Ví dụ, muốn lấy ra các quan sát ở dòng 1, 10, và 100 từ df1_select_positioned:
df1_select_positioned %>%
slice(c(1, 10, 100))## # A tibble: 3 x 2
## so_thu_tu tuoi
## <dbl> <dbl>
## 1 1 43
## 2 10 34
## 3 100 32
Hoặc lấy ra 5 quan sát liên tiếp từ 11 đến 15:
df1_select_positioned %>%
slice(c(11:15))## # A tibble: 5 x 2
## so_thu_tu tuoi
## <dbl> <dbl>
## 1 11 48
## 2 12 35
## 3 13 37
## 4 14 34
## 5 15 32
Hoặc lấy ra 5 quan sát liên tiếp từ 11 đến 15 ở vị trí dòng 21:
df1_select_positioned %>%
slice(c(11:15, 21))## # A tibble: 6 x 2
## so_thu_tu tuoi
## <dbl> <dbl>
## 1 11 48
## 2 12 35
## 3 13 37
## 4 14 34
## 5 15 32
## 6 21 54
Lệnh slice() này có thể được sử dụng để trả lời câu hỏi lấy ra 3 quan sát có tuổi lớn nhất. Trước hết ta sắp xếp lại các quan sát theo chiều giảm của tuổi:
# Stage 1:
df1_select_positioned %>%
arrange(-tuoi) -> df_arrangedSau đó lấy ra ba quan sát liên tiếp từ vị trí 1 đến 3:
# Stage 2:
df_arranged %>%
slice(c(1:3))## # A tibble: 3 x 2
## so_thu_tu tuoi
## <dbl> <dbl>
## 1 450 67
## 2 545 63
## 3 310 60
Đến đây chúng ta trở lại với tính y = tan(sin(x)) một lần nữa. Điểm tương tự là cách làm thông qua hai bước như trên chúng ta phải tạo ra một object trung gian là df_arranged. Tương tự như vấn đề tính y = tan(sin(x)), chúng ta có thể không cần phải tạo ra object trung gian df_arranged mà vẫn có câu trả lời nhờ sử dụng toán tử pipe như sau:
df1_select_positioned %>%
arrange(-tuoi) %>%
slice(c(1:3))## # A tibble: 3 x 2
## so_thu_tu tuoi
## <dbl> <dbl>
## 1 450 67
## 2 545 63
## 3 310 60
Your Assignment: Giải thích ý nghĩa của đoạn R codes dưới đây:
stigma_data %>%
select(STT, BAge) %>%
rename(tuoi = BAge) %>%
mutate(tuoi_binh_phuong = tuoi^2)Lệnh count() như tên gọi của nó gợi ý - là đếm quan sát. Tuy nhiên nó hiếm khi được sử dụng đơn lẻ mà thường được kết hợp với lệnh group_by(). Chẳng hạn, chúng ta muốn biết trình độ học vấn của các quan sát xuất hiện với tần suất bao nhiêu:
stigma_data %>%
group_by(BEducationallevel) %>%
count() Nghĩa là: trình độ học vấn được mã hóa là 1 xuất hiện 2 lần còn trình độ học vấn được mã hóa là 3 xuất hiện với tần suất lớn nhất là 505.
Nếu chúng ta muốn sắp xếp theo chiều giảm dần của tần suất xuất hiện của BEducationallevel thì:
# Count frequencies by BEducationallevel:
stigma_data %>%
group_by(BEducationallevel) %>%
count(sort = TRUE) -> education_reportĐến đây nếu muốn tính tỉ lệ các nhóm trình độ học vấn thì:
# Method 1:
education_report %>%
mutate(ti_le = n / 1121)Cách tính này rõ ràng chúng ta phải nhớ con số 1121 - chính là số dòng của bộ dữ liệu. Đây là cách làm không được khuyến khích. Nên tuân thủ cách làm thứ hai dưới đây:
# Method 2:
education_report %>%
ungroup() %>%
mutate(n_obs = sum(n)) %>%
mutate(rate = n / n_obs)## # A tibble: 5 x 4
## BEducationallevel n n_obs rate
## <dbl+lbl> <int> <int> <dbl>
## 1 3 [Cao d<U+1EB3>ng tru<U+1EDD>ng ngh<U+1EC1>] 505 1121 0.450
## 2 4 [Ð<U+1EA1>i h<U+1ECD>c] 391 1121 0.349
## 3 5 [Sau d<U+1EA1>i h<U+1ECD>c] 130 1121 0.116
## 4 2 [Trung h<U+1ECD>c Ph<U+1ED5> Thông] 93 1121 0.0830
## 5 1 [Trung h<U+1ECD>c co s<U+1EDF>] 2 1121 0.00178
Cách làm này chúng ta sử dụng lệnh ungroup() để vô hiệu hóa hiệu lực của group_by() rồi tạo cột biến mới có tên n_obs - tức là tổng số quan sát. Cuối cùng lấy số quan sát n chia cho tổng số quan sát n_obs để tính ra tỉ lệ của các nhóm học vấn.
Sử dụng lệnh này (đồng thời kết hợp với các lệnh khác) để:
Dưới đây là R codes thực hiện phân loại này theo hai cách:
# Method 1:
df1_select_positioned %>%
mutate(nhom_tuoi = case_when(tuoi < 30 ~ "Group1",
tuoi >= 30 & tuoi < 40 ~ "Group2",
tuoi >= 40 ~ "Group3")) -> df_ageGrouped
# Method 2:
df1_select_positioned %>%
mutate(nhom_tuoi = case_when(tuoi < 30 ~ "Group1",
tuoi >= 30 & tuoi < 40 ~ "Group2",
TRUE ~ "Group3")) -> df_ageGroupedĐến đây chúng ta có thể đếm tần suất của các nhóm tuổi và sắp xếp theo tần suất giảm dần:
df_ageGrouped %>%
group_by(nhom_tuoi) %>%
count(sort = TRUE)## # A tibble: 3 x 2
## # Groups: nhom_tuoi [3]
## nhom_tuoi n
## <chr> <int>
## 1 Group2 537
## 2 Group3 375
## 3 Group1 209
Your Assignment: Nhóm tuổi có tên “Group3” trong đoạn R codes dưới đây xuất hiện bao nhiêu lần:
df1_select_positioned %>%
mutate(nhom_tuoi = case_when(tuoi < 30 ~ "Group1",
tuoi >= 30 & tuoi < 40 ~ "Group2",
TRUE ~ "Group3")) %>%
group_by(nhom_tuoi) %>%
count(sort = TRUE)Your Assignment: Tính tỉ lệ các nhóm tuổi theo cách phân loại ở trên.
stigma_data$BEducationallevel %>% attributes()Dựa trên hiểu biết / ý nghĩa của cột biến BEducationallevel này chúng ta có thể mã hóa lại 5 nhóm học vấn như sau:
stigma_data %>%
mutate(edu_level = case_when(BEducationallevel == 1 ~ "Trung_hocCS",
BEducationallevel == 2 ~ "Trung_hocPT",
BEducationallevel == 3 ~ "Cao_dang/Nghe",
BEducationallevel == 4 ~ "DaiHoc",
TRUE ~ "SauDaiHoc")) -> stigma_data_newNếu muốn tính tỉ lệ của các nhóm học vấn thì:
stigma_data_new %>%
group_by(edu_level) %>%
count(sort = TRUE) %>%
ungroup() %>%
mutate(total = sum(n)) %>%
mutate(ti_le_edu = n / total)## # A tibble: 5 x 4
## edu_level n total ti_le_edu
## <chr> <int> <int> <dbl>
## 1 Cao_dang/Nghe 505 1121 0.450
## 2 DaiHoc 391 1121 0.349
## 3 SauDaiHoc 130 1121 0.116
## 4 Trung_hocPT 93 1121 0.0830
## 5 Trung_hocCS 2 1121 0.00178
Lệnh summarise() thường được sử dụng kết hợp với các lệnh khác để tính toán các thống kê hoặc bất kì chỉ số nào theo nhóm. Chẳng hạn chúng ta muốn tính min, max, mean, median, standard deviation của mucdohailongveluong của các nhóm học vấn:
stigma_data_new %>%
group_by(edu_level) %>%
summarise(min_HL = min(mucdohailongveluong),
max_HL = max(mucdohailongveluong),
avg_HL = mean(mucdohailongveluong),
med_HL = median(mucdohailongveluong),
sd_HL = sd(mucdohailongveluong))## # A tibble: 5 x 6
## edu_level min_HL max_HL avg_HL med_HL sd_HL
## * <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 Cao_dang/Nghe 0 5 3.66 3.75 1.16
## 2 DaiHoc 0 5 3.73 4 1.09
## 3 SauDaiHoc 1 5 3.66 3.75 1.03
## 4 Trung_hocCS 2.5 4.5 3.5 3.5 1.41
## 5 Trung_hocPT 1 5 3.73 3.75 1.19
Kết quả này cho thấy, ví dụ, nhóm Trung_hocCS có mức độ hài lòng trung bình là 3.5. Tuy nhiên con số này không nên được dựa vào để đưa ra bất kì kết luận hay khuyến nghị nào. Vì rằng: số quan sát của nhóm học vấn này chỉ là 2.
Do vậy, có lẽ nên đưa thêm thông tin về số lượng của các nhóm quan sát như sau:
stigma_data_new %>%
group_by(edu_level) %>%
summarise(min_HL = min(mucdohailongveluong),
max_HL = max(mucdohailongveluong),
avg_HL = mean(mucdohailongveluong),
med_HL = median(mucdohailongveluong),
sd_HL = sd(mucdohailongveluong),
n_obs = n()) -> hailong_income_by_educ
hailong_income_by_educ## # A tibble: 5 x 7
## edu_level min_HL max_HL avg_HL med_HL sd_HL n_obs
## * <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <int>
## 1 Cao_dang/Nghe 0 5 3.66 3.75 1.16 505
## 2 DaiHoc 0 5 3.73 4 1.09 391
## 3 SauDaiHoc 1 5 3.66 3.75 1.03 130
## 4 Trung_hocCS 2.5 4.5 3.5 3.5 1.41 2
## 5 Trung_hocPT 1 5 3.73 3.75 1.19 93
Nếu cần báo cáo mức độ hài lòng về lương của các nhóm cho bộ trưởng Y tế thì chúng ta có thể lưu lại báo cáo này ở dạng, ví dụ, định dạng csv (để có thể đọc bằng phần mềm Excel) như sau:
write.csv(hailong_income_by_educ, "F:/hailong_income_by_educ.csv")Lúc này tại ổ cứng F của máy tính sẽ có một file có tên là hailong_income_by_educ.csv có thể mở bằng Excel hoặc các phần mềm phân tích dữ liệu khác như SPSS, Stata. Lưu ý rằng không phải máy tính của ai cũng có ổ F. Và do vậy đường dẫn trong lệnh lưu dữ liệu trên có thể phải được thay đổi cho phù hợp.
Lệnh này cho phép tách ra một cột biến từ một data frame đã có. Ví dụ, tách ra BAge:
stigma_data %>% pull(BAge) -> vector_tuoi_m1Object có tên vector_tuoi_m1 ở trên là một vector trong khi stigma_data là một data frame. Chúng ta cũng có thể sử dụng dấu $ để hoàn thành công việc này:
stigma_data$BAge -> vector_tuoi_m211 lệnh/hàm của dplyr được giới thiệu ở trên có thể giải quyết hầu hết các tình huống của nhóm công việc báo cáo. Để minh họa, chúng ta sẽ thực hiện tái tạo lại một phần báo cáo ở Bảng 2 dưới đây:
Giả sử rằng thông tin về cái gọi là “vị trí làm việc” nằm ở cột biến Professional thì việc đầu tiên là chúng ta cần kiểm tra lại một lần nữa về biến số này:
stigma_data$Professional %>% attributes()Như vậy có 10 nhóm khác nhau được mã hóa từ 1 đến 10 nhưng báo cáo trên chỉ phân thành 4 nhóm là “Bác sĩ tâm thần”, “Điều dưỡng”, “Cán bộ chăm sóc sức khỏe” và “Khác”. Nhóm có tên “Chung” có lẽ là không tính toán theo bốn nhóm này mà là gộp chung tất cả lại.
Trước hết tính cho nhóm có tên “Chung”:
stigma_data %>%
summarise(Mean_HL_luong = mean(mucdohailongveluong),
SD_HL_luong = sd(mucdohailongveluong),
Mean_TTien = mean(mucdohailongcohoithangtien),
SD_TTien = sd(mucdohailongcohoithangtien))## # A tibble: 1 x 4
## Mean_HL_luong SD_HL_luong Mean_TTien SD_TTien
## <dbl> <dbl> <dbl> <dbl>
## 1 3.69 1.12 3.30 1.11
Trung bình thì mức độ hài lòng về lương là 3.69 tuy nhiên kết quả trong bảng 2 lại là 18.8. Đây là một kết quả không chính xác vì rằng mucdohailongveluong (cũng như các biến có chữ hailong khác) có giá trị lớn nhất là 5. Do vậy trung bình của nó không thể lớn hơn 5 được.
Kế tiếp mã hóa lại vị trí công việc thành 4 nhóm như trong báo cáo này với giả định rằng “Cận lâm sàng” được mã hóa lại thành “Can_bo_cham_soc”:
stigma_data %>%
mutate(job_position = case_when(Professional == 2 ~ "Bac_si_tam_than",
Professional == 3 ~ "Dieu_duong",
Professional == 4 ~ "Can_bo_cham_soc",
TRUE ~ "Khac")) -> stigmaDataNewRồi tính toán Mean, SD cho bốn nhóm này và có cả thông tin về số lượng quan sát của mỗi nhóm:
stigmaDataNew %>%
group_by(job_position) %>%
summarise(Mean_HL_luong = mean(mucdohailongveluong),
SD_HL_luong = sd(mucdohailongveluong),
Mean_TTien = mean(mucdohailongcohoithangtien),
SD_TTien = sd(mucdohailongcohoithangtien),
n_obs = n())## # A tibble: 4 x 6
## job_position Mean_HL_luong SD_HL_luong Mean_TTien SD_TTien n_obs
## * <chr> <dbl> <dbl> <dbl> <dbl> <int>
## 1 Bac_si_tam_than 3.49 1.10 3.29 1.16 181
## 2 Can_bo_cham_soc 3.87 1.29 3.63 1.09 31
## 3 Dieu_duong 3.69 1.14 3.28 1.12 578
## 4 Khac 3.78 1.08 3.31 1.05 331
Your Assignment: Thực hiện tính toán các kết quả còn lại như ở Bảng 2.
Bài giảng này giới thiệu chỉ 11 lệnh của dplyr. Đây là những lệnh quan trọng thường sử dụng và chúng cover hầu hết các tình huống của công việc báo cáo.
Trình bày cách sử dụng chỉ một số lệnh (trong số 11 lệnh) để tạo ra báo cáo như Bảng 2. Có bằng chứng rõ ràng rằng các kết quả tính toán trong Bảng 2 là không đúng. Lí do đã phân tích ở trên.
Các câu hỏi về đo lường cái gọi là “Hài Lòng” ở bộ dữ liệu này có lẽ sử dụng thang đo Likert - là loại dữ liệu thuộc nhóm Ordinal data nên cần có những mô hình thống kê chuyên biệt cho loại dữ liệu này.
Chúng ta có thể kiểm tra một số biến thuộc thang đo Likert (hay loại biến là Ordinal data) như sau:
sapply(stigma_data %>% select(E1a:E3a), function(x) {attributes(x)$labels})## E1a E2a E3a
## Strongly agree 1 1 1
## Agree 2 2 2
## Somewhat agree 3 3 3
## Somewhat disagree 4 4 4
## Disagree 5 5 5
## Strongly disagree 6 6 6
Thông thường các biến là thang đo Likert sẽ sử dụng một số lẻ (3, 5, 7) thang đo để sẽ có một điểm gọi là trung dung (neutral) nhằm ghi nhận thông tin khi mà thái độ của người phỏng vấn không nghiêng hẳn về bên nào. Phổ biến là 5. Nhưng bộ dữ liệu này lại sử dụng 6 ngưỡng - một điều rất hiếm thấy (thực tế tôi chưa gặp nghiên cứu nào sử dụng Likert 6).