8 Làm sạch số liệu và các hàm quan trọng

Chương này trình bày các bước phổ biến được sử dụng trong quá trình “làm sạch” bộ dữ liệu và cũng giải thích việc sử dụng các hàm quản lý dữ liệu thiết yếu trong R.

Để giải thích rõ hơn quá trình làm sạch dữ liệu, chương này bắt đầu từ cách nhập bộ dữ liệu thô có tên linelist và tiến hành từng bước trong quá trình làm sạch. Trong code R, quy trình này được biểu thị dưới dạng một chuỗi “pipe”, tham chiếu đến toán tử “pipe” %>% để chuyển tiếp từ thao tác này sang thao tác tiếp theo trong cùng một bộ dữ liệu.

Các hàm quan trọng

Cuốn sách này nhấn mạnh việc sử dụng các hàm từ hệ sinh thái của package tidyverse. Các hàm thiết yếu trong R trình bày trong chương này được liệt kê dưới đây.

Nhiều hàm thuộc về package dplyr, mà cung cấp các hàm dạng “verb” để giải quyết các thách thức trong thao tác xử lý dữ liệu (tên package được viết đầy đủ là "data frame-plier. dplyr là một phần trong hệ sinh thái của package tidyverse (mà bao gồm các package khác như ggplot2, tidyr, stringr, tibble, purrr, magrittr, và forcats).

Hàm Chức năng Package
%>% “pipe” (chuyển tiếp) dữ liệu từ một hàm sang hàm tiếp theo magrittr
mutate() tạo, chuyển dạng, và định nghĩa lại cột dplyr
select() giữ lại, xóa, chọn, hoặc đổi tên cột dplyr
rename() đổi tên cột dplyr
clean_names() chuẩn hóa cú pháp tên cột janitor
as.character(), as.numeric(), as.Date(), etc. chuyển đổi định dạng cột base R
across() biến đổi đồng thời nhiều cột dplyr
các hàm tidyselect sử dụng logic để chọn cột tidyselect
filter() giữ lại các dòng theo điều kiện dplyr
distinct() lọc quan sát trùng dplyr
rowwise() xử lý theo/trong mỗi dòng dplyr
add_row() thêm dòng thủ công tibble
arrange() săp xếp dòng dplyr
recode() recode lại giá trị trong cột dplyr
case_when() recode lại giá trị trong cột kèm các điều kiện phức tạp dplyr
replace_na(), na_if(), coalesce() các hàm đặc biệt để recode tidyr
age_categories() and cut() tạo biến phân nhóm từ biến định lượng epikitbase R
clean_variable_spelling() recode/làm sạch giá trị sử dụng từ điển dữ liệu linelist
which() áp dụng các điều kiện logic; trả về các chỉ số base R

Nếu bạn muốn xem các hàm này so sánh với các câu lệnh trong Stata hoặc SAS, hãy xem chương Chuyển đổi sang R.

Bạn cũng có thể gặp một framework quản lý dữ liệu khác từ package data.table trong R với các toán tử như := và thường xuyên sử dụng dấu ngoặc [ ]. Cách tiếp cận và cú pháp này được giải thích ngắn gọn trong chương Data Table.

Thuật ngữ

Trong cuốn sách này, chúng tôi quy ước chung “cột” và “hàng” thay cho “biến” và “quan sát”. Như đã giải thích trong phần dẫn trên “dữ liệu gọn gàng - tidy data”, hầu hết các bộ dữ liệu dịch tễ-thống kê đều có cấu trúc gồm các hàng, cột và giá trị.

Variables (biến) chứa các giá trị đo lường cùng một thuộc tính cơ bản (như nhóm tuổi, kết quả hoặc ngày bắt đầu). Observations (quan sát) chứa tất cả các giá trị được đo trên cùng một đơn vị (ví dụ: một người, địa điểm hoặc mẫu phòng thí nghiệm). Vì vậy, những khía cạnh này có thể khó xác định một cách hữu hình hơn.

Trong bộ dữ liệu “tidy”, mỗi cột là một biến, mỗi hàng là một quan sát và mỗi ô là một giá trị duy nhất. Tuy nhiên, một số bộ dữ liệu bạn gặp phải sẽ không giống với mô tả này - bộ dữ liệu định dạng “wide (ngang)” có thể có một biến số được chia thành nhiều cột (xem ví dụ trong chương Xoay trục dữ liệu). Tương tự như vậy, các quan sát có thể được chia thành nhiều hàng.

Cuốn sách này tập trung về quản lý và biến đổi dữ liệu, do đó, việc đề cập đến cấu trúc dữ liệu cụ thể của các hàng và cột sẽ thích hợp hơn là các quan sát và biến vốn khá trừu tượng. Các trường hợp ngoại lệ chủ yếu xảy ra trong các chương về phân tích dữ liệu, nơi bạn sẽ thấy nhiều tham chiếu hơn đến các biến và quan sát.

8.1 Quy trình làm sạch

Chương này tiến hành các bước làm sạch điển hình, bằng cách thêm chúng tuần tự vào một chuỗi pipe.

Trong phân tích dịch tễ học và xử lý dữ liệu, các bước làm sạch thường được thực hiện tuần tự, liên kết với nhau. Trong R, việc này thường được thực hiện dưới dạng một “pipeline - đường ống” làm sạch, trong đó bộ dữ liệu thô được chuyển tiếp hoặc “dẫn” từ bước làm sạch này sang bước làm sạch khác.

Các chuỗi như vậy sử dụng các hàm dạng “verb” trong package dplyr và toán tử pipe %>% trong package magrittr. Chuỗi pipe bắt đầu từ dữ liệu “thô” (“linelist_raw.xlsx”) và kết thúc bằng bộ dữ liệu “sạch” trên R (linelist) có thể được sử dụng, lưu, xuất, v.v.

Trong quy trình làm sạch dữ liệu, thứ tự của các bước là quan trọng. Các bước làm sạch có thể bao gồm:

  • Nhập dữ liệu
  • Làm sạch hoặc đổi tên cột
  • Loại bỏ trùng lặp
  • Tạo và chuyển đổi cột (ví dụ: mã hóa lại hoặc chuẩn hóa các giá trị)
  • Lọc hoặc thêm hàng

8.2 Gọi package

Đoạn code này trình bày cách gọi các package cần thiết cho phân tích. Trong cuốn sách này, chúng tôi nhấn mạnh đến hàm p_load() từ package pacman, cài đặt package nếu cần gọi package ra để sử dụng. Bạn cũng có thể gọi các package đã cài đặt với hàm library() sẵn có trong base R. Xem chương R cơ bản để biết thêm thông tin về các package trong R.

pacman::p_load(
  rio,        # importing data  
  here,       # relative file pathways  
  janitor,    # data cleaning and tables
  lubridate,  # working with dates
  epikit,     # age_categories() function
  tidyverse   # data management and visualization
)

8.3 Nhập dữ liệu

Nhập

Ở đây, chúng ta sẽ nhập tệp tin Excel “thô” bằng cách sử dụng hàm import() từ package rio. Package rio có thể xử lý linh hoạt nhiều loại tệp tin (ví dụ: .xlsx, .csv, .tsv, .rds. Xem chương về Nhập xuất dữ liệu để biết thêm thông tin và mẹo về các tình huống bất thường (ví dụ: loại bỏ hàng, thiết lập giá trị trống, nạp trang tính Google, v.v.).

Nếu bạn muốn hiểu rõ hơn, hãy tải xuống tệp dữ liệu linelist “thô” (với file dạng .xlsx).

Nếu bộ dữ liệu của bạn quá lớn và mất nhiều thời gian để nhập, sẽ hữu ích khi bạn đặt lệnh nhập dữ liệu riêng biệt với chuỗi pipe và dữ liệu “thô” sẽ được lưu thành một tệp riêng biệt. Điều này cũng cho phép dễ dàng so sánh giữa phiên bản gốc và phiên bản đã làm sạch.

Dưới đây, chúng ta nạp một tệp Excel thô và lưu nó dưới dạng dataframe linelist_raw. Chúng tôi giả định rằng tệp này nằm trong thư mục làm việc của bạn hoặc thư mục gốc của dự án R và vì vậy không có thư mục con nào được chỉ định trong đường dẫn tệp.

linelist_raw <- import("linelist_raw.xlsx")

Bạn có thể xem 50 hàng đầu tiên của bộ dữ liệu bên dưới. Lưu ý: hàm head(n) trong base R cho phép bạn chỉ xem n hàng đầu tiên trong R console.

Đánh giá

Bạn có thể sử dụng hàm skim() từ package skimr để có cái nhìn tổng quan về toàn bộ bộ dữ liệu (xem chương Bảng mô tả để biết thêm thông tin). Các cột được tóm tắt theo phân lớp/định dạng như ký tự và số. Lưu ý: “POSIXct” là một loại phân lớp ngày thô (xem chương Làm việc với ngày tháng.

skimr::skim(linelist_raw)
Table 8.1: Data summary
Name linelist_raw
Number of rows 6611
Number of columns 28
_______________________
Column type frequency:
character 17
numeric 8
POSIXct 3
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
case_id 137 0.98 6 6 0 5888 0
date onset 293 0.96 10 10 0 580 0
outcome 1500 0.77 5 7 0 2 0
gender 324 0.95 1 1 0 2 0
hospital 1512 0.77 5 36 0 13 0
infector 2323 0.65 6 6 0 2697 0
source 2323 0.65 5 7 0 2 0
age 107 0.98 1 2 0 75 0
age_unit 7 1.00 5 6 0 2 0
fever 258 0.96 2 3 0 2 0
chills 258 0.96 2 3 0 2 0
cough 258 0.96 2 3 0 2 0
aches 258 0.96 2 3 0 2 0
vomit 258 0.96 2 3 0 2 0
time_admission 844 0.87 5 5 0 1091 0
merged_header 0 1.00 1 1 0 1 0
…28 0 1.00 1 1 0 1 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100
generation 7 1.00 16.60 5.71 0.00 13.00 16.00 20.00 37.00
lon 7 1.00 -13.23 0.02 -13.27 -13.25 -13.23 -13.22 -13.21
lat 7 1.00 8.47 0.01 8.45 8.46 8.47 8.48 8.49
row_num 0 1.00 3240.91 1857.83 1.00 1647.50 3241.00 4836.50 6481.00
wt_kg 7 1.00 52.69 18.59 -11.00 41.00 54.00 66.00 111.00
ht_cm 7 1.00 125.25 49.57 4.00 91.00 130.00 159.00 295.00
ct_blood 7 1.00 21.26 1.67 16.00 20.00 22.00 22.00 26.00
temp 158 0.98 38.60 0.95 35.20 38.30 38.80 39.20 40.80

Variable type: POSIXct

skim_variable n_missing complete_rate min max median n_unique
infection date 2322 0.65 2012-04-09 2015-04-27 2014-10-04 538
hosp date 7 1.00 2012-04-20 2015-04-30 2014-10-15 570
date_of_outcome 1068 0.84 2012-05-14 2015-06-04 2014-10-26 575

8.4 Tên cột

Trong R, tên cột là “tiêu đề” hoặc giá trị “trên đỉnh” của một cột. Chúng được sử dụng để tham chiếu đến các cột trong đoạn code và đóng vai trò như một nhãn mặc định trong các bảng biểu.

Các phần mềm thống kê khác như SAS và STATA sử dụng “nhãn” là tên cột phiên bản dài hơn khi in, cùng tồn tại song song với tên cột. Mặc dù R cung cấp tính năng thêm nhãn của cột vào dữ liệu, nhưng điều này hầu hết không được nhấn mạnh trong thực tế. Để đặt tên cột “dễ nhìn” cho các bảng biểu, người ta thường điều chỉnh hiển thị của chúng trong các lệnh vẽ biểu đồ để tạo ra kết quả (ví dụ: tiêu đề trục hoặc chú giải của một biểu đồ hoặc tiêu đề cột trong bảng - xem thêm mục scales trong chương Các tips với ggplot và chương Trình bày bảng). Nếu bạn muốn gán nhãn cột trong dữ liệu, hãy đọc thêm tài liệu trực tuyến tại đâytại đây.

Vì tên cột trong R được sử dụng rất thường xuyên, vì vậy chúng phải có cú pháp “sạch”. Chúng tôi đề xuất những tiêu chí sau:

  • Tên ngắn
  • Không có khoảng trắng (thay thế bằng dấu gạch dưới_)
  • Không có ký tự lạ (&, #, <, >, …)
  • Thống nhất cách định danh (vd: toàn bộ tên cột ngày như date_onset, date_report, date_death…)

Tên các cột trong linelist_raw được in bên dưới bằng cách sử dụng hàm names() từ base R. Ban đầu chúng ta có thể thấy rằng:

  • Một số tên chứa khoảng trắng (vd: infection date)
  • Một số mẫu tên khác nhau được sử dụng cho biến ngày (date onsetinfection date)
  • Phải có một tiêu đề được hợp nhất ở hai cột cuối cùng trong tệp .xlsx. Chúng tôi biết điều này vì tên của hai cột được hợp nhất (“merge_header”) được R gán cho cột đầu tiên và cột thứ hai được gán tên giữ chỗ “… 28” (vì lúc đó nó trống và là cột thứ 28).
names(linelist_raw)
##  [1] "case_id"         "generation"      "infection date"  "date onset"      "hosp date"       "date_of_outcome" "outcome"         "gender"         
##  [9] "hospital"        "lon"             "lat"             "infector"        "source"          "age"             "age_unit"        "row_num"        
## [17] "wt_kg"           "ht_cm"           "ct_blood"        "fever"           "chills"          "cough"           "aches"           "vomit"          
## [25] "temp"            "time_admission"  "merged_header"   "...28"

CHÚ Ý: Để tham chiếu tên một cột có chứa khoảng trắng, hãy bao quanh tên cột bằng dấu huyền, ví dụ: linelist$` '\x60infection date\x60'`. Lưu ý là trên bàn phím, dấu huyền (`) được phân biệt với dấu ngoặc đơn (’).

Làm sạch tự động

Hàm clean_names() từ package janitor chuẩn hóa tên cột và biến chúng thành duy nhất bằng cách thực hiện như sau:

  • Chuyển đổi tất cả các tên cột thành chỉ bao gồm dấu gạch dưới, số và chữ cái
  • Các ký tự có dấu được chuyển ngữ sang dạng ASCII (ví dụ: o tiếng Đức với âm sắc trở thành “o”, “enye” tiếng Tây Ban Nha trở thành “n”)
  • Tùy chọn viết hoa cho tên cột mới có thể được chỉ định bằng cách sử dụng đối số case = (mặc định là “snake”, các lựa chọn thay thế bao gồm “sentence”, “title”, “small_camel”…)
  • Bạn có thể chỉ định các tên thay thế cụ thể bằng cách cung cấp một vectơ tới đố số replace = (ví dụ: replace = c(onset = "date_of_onset"))
  • Đây là một hướng dẫn trực tuyến

Sau đây, quy trình làm sạch bắt đầu bằng cách sử dụng hàm clean_names() trên bộ dữ liệu linelist thô.

# pipe the raw dataset through the function clean_names(), assign result as "linelist"  
linelist <- linelist_raw %>% 
  janitor::clean_names()

# see the new column names
names(linelist)
##  [1] "case_id"         "generation"      "infection_date"  "date_onset"      "hosp_date"       "date_of_outcome" "outcome"         "gender"         
##  [9] "hospital"        "lon"             "lat"             "infector"        "source"          "age"             "age_unit"        "row_num"        
## [17] "wt_kg"           "ht_cm"           "ct_blood"        "fever"           "chills"          "cough"           "aches"           "vomit"          
## [25] "temp"            "time_admission"  "merged_header"   "x28"

LƯU Ý: Tên cột cuối cùng “… 28” đã được đổi thành “x28”.

Làm sạch tên cột thủ công

Việc đặt tên lại các cột theo cách thủ công thường là cần thiết, ngay cả sau bước chuẩn hóa ở trên. Dưới đây, việc đổi tên được thực hiện bằng cách sử dụng hàm rename() từ package dplyr, như một phần của quy trình. rename() sử dụng cú pháp NEW = OLD - tên cột mới được đặt trước tên cột cũ.

Dưới đây, một lệnh đổi tên được thêm vào quy trình làm sạch. Các khoảng trắng đã được thêm vào một cách chọn lọc để căn chỉnh code dễ đọc hơn.

# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)
##################################################################################
linelist <- linelist_raw %>%
    
    # standardize column name syntax
    janitor::clean_names() %>% 
    
    # manually re-name columns
           # NEW name             # OLD name
    rename(date_infection       = infection_date,
           date_hospitalisation = hosp_date,
           date_outcome         = date_of_outcome)

Bây giờ bạn có thể thấy rằng tên các cột đã được thay đổi:

##  [1] "case_id"              "generation"           "date_infection"       "date_onset"           "date_hospitalisation" "date_outcome"        
##  [7] "outcome"              "gender"               "hospital"             "lon"                  "lat"                  "infector"            
## [13] "source"               "age"                  "age_unit"             "row_num"              "wt_kg"                "ht_cm"               
## [19] "ct_blood"             "fever"                "chills"               "cough"                "aches"                "vomit"               
## [25] "temp"                 "time_admission"       "merged_header"        "x28"

Thay đối tên cột theo vị trí

Bạn cũng có thể đổi tên theo vị trí cột, thay vì tên cột, ví dụ:

rename(newNameForFirstColumn  = 1,
       newNameForSecondColumn = 2)

Đổi tên bằng hàm select()summarise()

Như một lối tắt, bạn cũng có thể đổi tên các cột bằng hàm select()summarise() thuộc package dplyr. Hàm select() được sử dụng để chỉ giữ một số cột nhất định (sẽ được đề cập sau trong chương này). Hàm summarise() được đề cập trong các chương Nhóm dữ liệuBảng mô tả. Các hàm này cũng sử dụng định dạng new_name = old_name. Đây là một ví dụ:

linelist_raw %>% 
  select(# NEW name             # OLD name
         date_infection       = `infection date`,    # rename and KEEP ONLY these columns
         date_hospitalisation = `hosp date`)

Các thách thức khác

Cột trống tên trên file Excel

R không thể nhận diện các cột trong bộ dữ liệu mà không có tên cột (tiêu đề). Vì vậy, nếu bạn nhập một bộ dữ liệu Excel có dữ liệu nhưng không có tiêu đề cột, R sẽ điền vào các tiêu đề đó mặc định như “… 1” hoặc “… 2”. Phần số đại diện cho số cột (ví dụ: nếu cột thứ 4 trong bộ dữ liệu không có tiêu đề, thì R sẽ mặc định đặt tên là “… 4”).

Bạn có thể làm sạch các tên cột này theo cách thủ công bằng cách tham chiếu số vị trí của chúng (xem ví dụ ở trên) hoặc tên được chỉ định của chúng (linelist_raw$...1).

Hợp nhất tên cột và ô Excel

Các ô được hợp nhất trong file Excel là một sự cố thường xảy ra khi nhận dữ liệu. Như đã giải thích trong chương Chuyển đổi sang R, các ô được hợp nhất có thể dễ nhìn cho con người đọc dữ liệu, nhưng không phải là “cấu trúc dữ liệu cơ bản” và gây ra nhiều vấn đề cho máy khi nhận diện dữ liệu. R không thể chứa các ô đã hợp nhất.

Nhắc nhở người nhập liệu rằng dữ liệu có thể đọc được bởi con người không giống với dữ liệu mà máy có thể đọc được. Hãy cố gắng đào tạo người dùng về các nguyên tắc về dữ liệu tidy. Nếu có thể, hãy cố gắng thay đổi quy trình để dữ liệu có định dạng gọn gàng hơn mà không hợp nhất các ô.

  • Mỗi biến phải là một cột riêng biệt
  • Mỗi quan sát phải là một dòng riêng biệt
  • Mỗi giá trị phải là một ô riêng biệt

Khi sử dụng hàm import() của package rio, giá trị trong một ô đã hợp nhất sẽ được gán cho ô đầu tiên và các ô tiếp theo sẽ trống.

Một giải pháp để xử lý các ô đã hợp nhất là nạp dữ liệu bằng hàm readWorkbook() từ package openxlsx. Thiết lập đối số fillMergedCells = TRUE. Điều này cho phép giá trị trong ô hợp nhất được tham chiếu cho tất cả các ô nằm trong phạm vi hợp nhất.

linelist_raw <- openxlsx::readWorkbook("linelist_raw.xlsx", fillMergedCells = TRUE)

NGUY HIỂM: Nếu các tên cột được hợp nhất với readWorkbook(), có thể sẽ trả về tên cột trùng lặp, mà bạn sẽ cần phải sửa một cách thủ công - R không làm việc được với các tên cột trùng lặp! Bạn có thể đặt lại tên cho chúng bằng cách tham chiếu vị trí của chúng (ví dụ: cột 5), như được giải thích trong mục làm sạch tên cột thủ công.

8.5 Chọn hoặc đổi vị trí cột

Sử dụng hàm select() từ package dplyr để chọn các cột bạn muốn giữ lại và sắp xếp lại thứ tự của chúng trong bộ dữ liệu.

CẨN TRỌNG: Trong các ví dụ dưới đây, bộ dữ liệu linelist được điều chỉnh với hàm select() và được hiển thị, nhưng không được lưu. Việc này được sử dụng phục vụ mục đích mô tả. Tên cột đã sửa đổi được in ra bằng cách chuyển tiếp bộ dữ liệu tới hàm names().

Dưới đây là TẤT CẢ các tên cột trong bộ dữ liệu linelist tại thời điểm làm sạch:

names(linelist)
##  [1] "case_id"              "generation"           "date_infection"       "date_onset"           "date_hospitalisation" "date_outcome"        
##  [7] "outcome"              "gender"               "hospital"             "lon"                  "lat"                  "infector"            
## [13] "source"               "age"                  "age_unit"             "row_num"              "wt_kg"                "ht_cm"               
## [19] "ct_blood"             "fever"                "chills"               "cough"                "aches"                "vomit"               
## [25] "temp"                 "time_admission"       "merged_header"        "x28"

Giữ cột

Giữ lại những cột mong muốn

Viết tên cột cần giữ trong hàm select(), không có dấu ngoặc kép. Kết quả trả về sẽ xuất hiện trong bộ dữ liệu theo thứ tự mà bạn cung cấp. Lưu ý rằng nếu bạn chỉ định một cột không tồn tại, R sẽ báo lỗi (xem cách sử dụng hàm any_of() bên dưới nếu bạn không muốn gặp lỗi trong trường hợp này).

# linelist dataset is piped through select() command, and names() prints just the column names
linelist %>% 
  select(case_id, date_onset, date_hospitalisation, fever) %>% 
  names()  # display the column names
## [1] "case_id"              "date_onset"           "date_hospitalisation" "fever"

Hàm trợ giúp “tidyselect”

Các hàm trợ giúp này được xây dựng để giúp bạn dễ dàng chỉ định các cột cần giữ, loại bỏ hoặc chuyển đổi. Chúng nằm trong package tidyselect, thuộc hệ sinh thái tidyverse và làm cơ sở cho cách chọn cột trong các hàm dplyr.

Ví dụ: nếu bạn muốn sắp xếp lại các cột, hàm everything() là một hàm hữu ích để biểu thị “tất cả các cột khác chưa được đề cập”. Lệnh dưới đây di chuyển các cột date_onsetdate_hospitalisation lên cột đầu tiên (bên trái) của bộ dữ liệu, nhưng vẫn giữ tất cả các cột khác sau đó. Lưu ý rằng hàm everything() được viết bằng dấu ngoặc đơn trống:

# move date_onset and date_hospitalisation to beginning
linelist %>% 
  select(date_onset, date_hospitalisation, everything()) %>% 
  names()
##  [1] "date_onset"           "date_hospitalisation" "case_id"              "generation"           "date_infection"       "date_outcome"        
##  [7] "outcome"              "gender"               "hospital"             "lon"                  "lat"                  "infector"            
## [13] "source"               "age"                  "age_unit"             "row_num"              "wt_kg"                "ht_cm"               
## [19] "ct_blood"             "fever"                "chills"               "cough"                "aches"                "vomit"               
## [25] "temp"                 "time_admission"       "merged_header"        "x28"

Dưới đây là các hàm trợ giúp “tidyselect” khác cũng hoạt động trong các hàm dplyr như select(), across(), và summarise():

  • everything() - tất cả các cột khác chưa được đề cập

  • last_col() - cột cuối cùng

  • where() - áp dụng một hàm cho tất cả các cột và chọn những cột trả về giá trị TRUE

  • contains() - cột chứa một chuỗi ký tự

    • Ví dụ: select(contains("time"))
  • starts_with() - khớp với các tiền tố được chỉ định

    • Ví dụ: select(starts_with("date_"))
  • ends_with() - khớp với các hậu tố được chỉ định

    • Ví dụ: select(ends_with("_post"))
  • matches() - để áp dụng một mệnh đề chính quy (regex)

    • Ví dụ: select(matches("[pt]al"))
  • num_range() - một khoảng số học như x01, x02, x03

  • any_of() - khớp NẾU cột tồn tại nhưng không báo lỗi nếu không tìm thấy

    • Ví dụ: select(any_of(date_onset, date_death, cardiac_arrest))

Ngoài ra, sử dụng các toán tử bình thường như c() để liệt kê danh sách cột, hoặc : cho các cột liên tiếp, ! cho đối lập, & cho VÀ, và | cho HOẶC.

Sử dụng where() để cụ thể tiêu chí logic cho các cột. Nếu đưa một hàm vào trong where(), hãy nhớ không bao gồm dấu ngoặc đơn trống của hàm đó. Câu lệnh bên dưới chọn các cột kiểu Số.

# select columns that are class Numeric
linelist %>% 
  select(where(is.numeric)) %>% 
  names()
## [1] "generation" "lon"        "lat"        "row_num"    "wt_kg"      "ht_cm"      "ct_blood"   "temp"

Sử dụng contains() để chọn các cột trong đó tên cột chứa một chuỗi ký tự được chỉ định. ends_with()starts_with() cung cấp thêm các lựa chọn khác nhau.

# select columns containing certain characters
linelist %>% 
  select(contains("date")) %>% 
  names()
## [1] "date_infection"       "date_onset"           "date_hospitalisation" "date_outcome"

Hàm matches() hoạt động tương tự như hàm contains() nhưng có thể được cung cấp một biểu thức chính quy (xem chương Ký tự và chuỗi), chẳng hạn như nhiều chuỗi được phân tách bằng toán tự OR trong dấu ngoặc đơn:

# searched for multiple character matches
linelist %>% 
  select(matches("onset|hosp|fev")) %>%   # note the OR symbol "|"
  names()
## [1] "date_onset"           "date_hospitalisation" "hospital"             "fever"

CẨN TRỌNG: Nếu tên cột mà bạn đưa ra không tồn tại trong dữ liệu, máy có thể báo lỗi và dừng code của bạn. Cân nhắc sử dụng hàm any_of() để trích dẫn các cột có thể hoặc không thể tồn tại, đặc biệt hữu ích trong các lựa chọn loại trừ (loại bỏ).

Chỉ có một trong các cột này tồn tại, nhưng không có lỗi được báo và code sẽ tiếp tục các bước trong quy trình làm sạch.

linelist %>% 
  select(any_of(c("date_onset", "village_origin", "village_detection", "village_residence", "village_travel"))) %>% 
  names()
## [1] "date_onset"

Xóa cột

Chỉ ra cột nào cần loại bỏ bằng cách đặt biểu tượng dấu trừ “-” ở phía trước tên cột (ví dụ: select(-outcome)) hoặc một vectơ tên các cột (như bên dưới). Tất cả các cột khác sẽ được giữ nguyên.

linelist %>% 
  select(-c(date_onset, fever:vomit)) %>% # remove date_onset and all columns from fever to vomit
  names()
##  [1] "case_id"              "generation"           "date_infection"       "date_hospitalisation" "date_outcome"         "outcome"             
##  [7] "gender"               "hospital"             "lon"                  "lat"                  "infector"             "source"              
## [13] "age"                  "age_unit"             "row_num"              "wt_kg"                "ht_cm"                "ct_blood"            
## [19] "temp"                 "time_admission"       "merged_header"        "x28"

Bạn cũng có thể xóa một cột bằng cú pháp trong base R, bằng cách định nghĩa nó là NULL. Ví dụ:

linelist$date_onset <- NULL   # deletes column with base R syntax 

Hoạt động độc lập

select() cũng có thể được sử dụng như một lệnh độc lập (không bắt buộc trong chuỗi các bước làm sạch). Trong trường hợp này, đối số đầu tiên trong bộ dữ liệu gốc sẽ được tham chiếu.

# Create a new linelist with id and age-related columns
linelist_age <- select(linelist, case_id, contains("age"))

# display the column names
names(linelist_age)
## [1] "case_id"  "age"      "age_unit"

Thêm vào quy trình làm sạch

Trong bộ dữ liệu linelist_raw, có một vài cột chúng ta không cần: row_num, merged_header, và x28. Chúng ta xóa chúng bằng lệnh select() trong chuỗi các bước làm sạch như sau:

# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)
##################################################################################

# begin cleaning pipe chain
###########################
linelist <- linelist_raw %>%
    
    # standardize column name syntax
    janitor::clean_names() %>% 
    
    # manually re-name columns
           # NEW name             # OLD name
    rename(date_infection       = infection_date,
           date_hospitalisation = hosp_date,
           date_outcome         = date_of_outcome) %>% 
    
    # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED
    #####################################################

    # remove column
    select(-c(row_num, merged_header, x28))

8.6 Loại bỏ trùng lặp

Xem chương Loại bỏ trùng lặp để biết các tùy chọn mở rộng về cách loại bỏ dữ liệu trùng lặp. Chỉ một ví dụ rất đơn giản về cách loại bỏ hàng trùng lặp được trình bày ở chương này.

Package dplyr cung cấp hàm distinct(). Hàm này kiểm tra tất cả các hàng và rút gọn bộ dữ liệu cho chỉ còn các hàng là duy nhất. Nghĩa là, nó loại bỏ 100% các hàng trùng lặp.

Khi đánh giá các hàng trùng lặp, hàm này sẽ thực hiện trên các cột được chỉ định - mặc định nó sẽ xem xét tất cả các cột. Như trình bày trong chương loại bỏ trùng lặp, bạn có thể điều chỉnh phạm vi cột để việc khảo sát tính duy nhất của các hàng sẽ chỉ được đánh giá trong phạm vi một số cột nhất định.

Trong ví dụ đơn giản này, chúng ta chỉ cần thêm câu lệnh trống distinct() vào chuỗi các bước làm sạch. Điều này đảm bảo không có hàng nào là bản sao 100% của các hàng khác (được đánh giá trên tất cả các cột).

Chúng ta bắt đầu với nrow(linelist) hàng trong bộ linelist.

linelist <- linelist %>% 
  distinct()

Sau khi loại bỏ trùng lặp, thì bộ dữ liệu mới có nrow(linelist) hàng. Bất kỳ hàng nào bị xóa cũng có thể là 100% bản sao của các hàng khác.

Dưới đây, lệnh distinct() được thêm vào quy trình làm sạch:

# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)
##################################################################################

# begin cleaning pipe chain
###########################
linelist <- linelist_raw %>%
    
    # standardize column name syntax
    janitor::clean_names() %>% 
    
    # manually re-name columns
           # NEW name             # OLD name
    rename(date_infection       = infection_date,
           date_hospitalisation = hosp_date,
           date_outcome         = date_of_outcome) %>% 
    
    # remove column
    select(-c(row_num, merged_header, x28)) %>% 
  
    # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED
    #####################################################
    
    # de-duplicate
    distinct()

8.7 Tạo và biến đổi cột

Chúng tôi khuyến khích sử dụng hàm mutate() trong package dplyr để thêm một cột mới hoặc để sửa đổi một cột sẵn có.

Dưới đây là một ví dụ về tạo cột mới với hàm mutate(). Cú pháp là: mutate(new_column_name = value or transformation)

Trong Stata, điều này tương tự như lệnh generate, nhưng hàm mutate() trong R còn có thể được sử dụng để điều chỉnh cột đã tồn tại.

Tạo cột mới

Tính năng cơ bản nhất của hàm mutate() là để tạo một cột mới, có thể thấy trong ví dụ dưới đây. Nó tạo một cột mới new_col trong đó giá trị trong mỗi hàng đều là 10.

linelist <- linelist %>% 
  mutate(new_col = 10)

Bạn cũng có thể tham chiếu các giá trị trong các cột khác, để thực hiện phép tính toán. Dưới đây, cột mới bmi được tạo để tính chỉ số khối cơ thể (BMI) cho mỗi trường hợp - như được tính bằng công thức BMI = kg/m^2, sử dụng cột ht_cm và cột wt_kg.

linelist <- linelist %>% 
  mutate(bmi = wt_kg / (ht_cm/100)^2)

Nếu tạo nhiều cột mới, hãy tách riêng từng cột bằng dấu phẩy và dòng mới. Dưới đây là các ví dụ về tạo các cột mới, bao gồm một cột mà chứa các giá trị từ các cột khác được kết hợp bằng hàm str_glue() từ package stringr (xem chương Ký tự và chuỗi).

new_col_demo <- linelist %>%                       
  mutate(
    new_var_dup    = case_id,             # new column = duplicate/copy another existing column
    new_var_static = 7,                   # new column = all values the same
    new_var_static = new_var_static + 5,  # you can overwrite a column, and it can be a calculation using other variables
    new_var_paste  = stringr::str_glue("{hospital} on ({date_hospitalisation})") # new column = pasting together values from other columns
    ) %>% 
  select(case_id, hospital, date_hospitalisation, contains("new"))        # show only new columns, for demonstration purposes

Kiểm tra các cột mới. Đối với mục tiêu minh họa, chỉ các cột mới và các cột được sử dụng để tạo cột mới được hiển thị:

MẸO: Một biến thể của hàm mutate() là hàm transmute(). Hàm này thêm một cột mới giống như mutate(), nhưng cũng bỏ/xóa tất cả các cột khác mà bạn không đề cập trong dấu ngoặc đơn của hàm.

# HIDDEN FROM READER
# removes new demo columns created above
# linelist <- linelist %>% 
#   select(-contains("new_var"))

Chuyển đổi kiểu dữ liệu của cột

Các cột chứa giá trị là ngày tháng, số hoặc giá trị logic (TRUE/FALSE) sẽ chỉ hoạt động như mong đợi nếu chúng được phân loại chính xác. Có sự khác biệt giữa “2” của phân lớp ký tự và 2 của phân lớp số!

Có nhiều cách để thiết lập kiểu dữ liệu cho cột trong các lệnh nhập, nhưng chúng thường phức tạp. Xem chương R cơ bản về các lớp đối tượng để tìm hiểu thêm về cách chuyển đổi phân lớp đối tượng và cột.

Đầu tiên, hãy kiểm tra các cột quan trọng để xem chúng đã đúng định dạng chính xác chưa. Chúng ta có thể phát hiện điều này ngay từ khi chúng ta chạy lệnh skim().

Hiện tại, kiểu của cột age là dạng ký tự. Để thực hiện các phân tích định lượng, chúng ta cần những giá trị số này được nhận biết là dạng số!

class(linelist$age)
## [1] "character"

Phân lớp của cột date_onset cũng là ký tự! Để thực hiện phân tích, những cột ngày này phải được nhận biết là dạng ngày tháng!

class(linelist$date_onset)
## [1] "character"

Để giải quyết vấn đề này, hãy sử dụng hàm mutate() để định dạng và chuyển đổi lại một cột. Chúng ta giữ nguyên cột đó và định dạng lại sang một phân lớp khác. Sau đây là một ví dụ cơ bản, chuyển đổi hoặc đảm bảo rằng cột age là phân lớp dạng số:

linelist <- linelist %>% 
  mutate(age = as.numeric(age))

Theo cách tương tự, bạn có thể sử dụng hàm as.character() và hàm as.logical(). Để chuyển đổi sang kiểu Factor, bạn có thể sử dụng hàm factor() từ base R hoặc hàm as_factor() từ package forcats. Đọc thêm ở chương Factors.

Bạn phải cẩn thận khi chuyển đổi sang phân lớp Ngày. Một số phương pháp được giải thích rõ hơn ở chương Làm việc với ngày tháng. Thông thường, tất cả các giá trị ngày ban đầu phải ở cùng một định dạng cần chuyển đổi để có thể sử dụng (ví dụ: “MM/DD/YYYY” hoặc “DD MM YYYY”). Sau khi chuyển đổi thành thành phân lớp Ngày, hãy kiểm tra dữ liệu của bạn để xác nhận rằng mỗi giá trị đã được chuyển đổi chính xác.

Dữ liệu được nhóm

Nếu bộ dữ liệu của bạn đã được nhóm (xem chương Nhóm dữ liệu), hàm mutate() có thể hoạt động khác so với khi bộ dữ liệu không được nhóm. Bất kỳ hàm tổng hợp nào, như mean(), median(), max(), v.v. sẽ tính theo nhóm, không phải theo tất cả các hàng.

# age normalized to mean of ALL rows
linelist %>% 
  mutate(age_norm = age / mean(age, na.rm=T))

# age normalized to mean of hospital group
linelist %>% 
  group_by(hospital) %>% 
  mutate(age_norm = age / mean(age, na.rm=T))

Đọc thêm về cách sử dụng hàm mutate() trên các bộ dữ liệu được nhóm ở tài liệu về mutate trong package tidyverse.

Biến đổi nhiều cột

Thông thường, để viết mã ngắn gọn, bạn muốn áp dụng cùng một biến đổi trên nhiều cột cùng một lúc. Một cách biến đổi có thể được áp dụng trên nhiều cột cùng một lúc bằng cách sử dụng hàm across() từ package dplyr (cũng trong package tidyverse). Hàm across() có thể được sử dụng với bất kỳ hàm nào trong packe dplyr, nhưng thường được sử dụng với hàm select(), mutate(), filter(), hoặc summarise(). Xem cách sử dụng hàm summarise() trong chương Bảng mô tả.

Chỉ định các cột bằng đối số .cols = và các hàm được sử dụng bằng .fns =. Bất kỳ đối số bổ sung nào để cung cấp cho hàm .fns đều có thể được bao gồm sau dấu phẩy, vẫn nằm trong hàm across().

Lựa chọn cột với hàm across()

Chỉ định các cột tới đối số .cols =. Bạn có thể đặt tên cho chúng một cách riêng lẻ hoặc sử dụng các hàm trợ giúp “tidyselect”. Cụ thể hàm tới đối số .fns =. Lưu ý rằng cách sử dụng hàm được trình bày bên dưới, hàm được viết mà không có dấu ngoặc đơn ().

Ở đây, phép biến đổi as.character() được áp dụng cho tên cột cụ thể trong hàm across().

linelist <- linelist %>% 
  mutate(across(.cols = c(temp, ht_cm, wt_kg), .fns = as.character))

Các hàm trợ giúp “tidyselect” có sẵn để hỗ trợ bạn trong việc chỉ định các cột. Chúng được trình bày chi tiết ở trên trong mục Chọn và sắp xếp thứ tự cột, bao gồm: everything(), last_col(), where(), starts_with(), ends_with(), contains(), matches(), num_range()any_of().

Đây là một ví dụ về cách thay đổi tất cả các cột thành phân lớp dạng ký tự:

#to change all columns to character class
linelist <- linelist %>% 
  mutate(across(.cols = everything(), .fns = as.character))

Chuyển đổi tất cả các cột thành dạng ký tự có tên chứa chuỗi ký tự “date” (lưu ý vị trí của dấu phẩy và dấu ngoặc đơn):

#to change all columns to character class
linelist <- linelist %>% 
  mutate(across(.cols = contains("date"), .fns = as.character))

Dưới đây, một ví dụ về việc thay đổi các cột hiện là phân lớp POSIXct (một kiểu dữ liệu ngày tháng thô hiển thị mốc thời gian) - hay nói cách khác, khi hàm is.POSIXct() đánh giá là TRUE. Sau đó, chúng ta muốn áp dụng hàm as.Date() cho các cột này để chuyển đổi chúng thành kiểu Ngày thông thường.

linelist <- linelist %>% 
  mutate(across(.cols = where(is.POSIXct), .fns = as.Date))
  • Lưu ý rằng trong hàm across(), chúng tôi cũng sử dụng hàm where() tương tự như hàm is.POSIXct được đánh giá là TRUE hoặc FALSE.
  • Lưu ý rằng hàm is.POSIXct() là từ package lubridate. Các hàm “is” tương tự khác như is.character(), is.numeric(), và is.logical() thì thuộc base R

Hàm across()

Bạn có thể đọc tài liệu với hàm ?across để biết chi tiết về cách sử dụng hàm across(). Một vài điểm tóm tắt: có một số cách để chỉ định (các) hàm để thực hiện trên một cột và bạn thậm chí có thể tự chỉ định các hàm riêng của mình:

  • Bạn có thể cung cấp tên hàm đứng một mình (ví dụ: mean hoặc as.character)

  • Bạn có thể cung cấp hàm theo phong cách purrr (ví dụ: ~ mean(.x, na.rm = TRUE)) (xem chương Lặp, vòng lặp, và danh sách)

  • Bạn có thể chỉ định nhiều hàm bằng cách cung cấp danh sách (ví dụ: list(mean = mean, n_miss = ~ sum(is.na(.x))).

    • Nếu bạn cung cấp nhiều hàm, nhiều cột được chuyển đổi sẽ được trả về dựa trên mỗi cột ban đầu, với các tên duy nhất theo định dạng col_fn. Bạn có thể điều chỉnh cách đặt tên các cột mới với đối số .names = bằng cách sử dụng cú pháp glue (xem chương Ký tự và chuỗi) trong đó {.col}{.fn} được viết tắt cho cột và hàm đầu vào.

Dưới đây là một số tài nguyên trực tuyến về cách sử dụng hàm across(): creator Hadley Wickham’s thoughts/rationale

Hàm coalesce()

Hàm dplyr này tìm giá trị không bị thiếu đầu tiên tại mỗi vị trí. Nó “điền vào” các giá trị còn thiếu với giá trị có sẵn đầu tiên theo thứ tự bạn chỉ định.

Đây là một ví dụ ngoài phạm vi của bộ dữ liệu: Giả sử bạn có hai vectơ, một vectơ chứa thông tin về làng nơi bệnh nhân được phát hiện và một vectơ chứa thông tin làng nơi bệnh nhân cư trú . Bạn có thể sử dụng hàm coalesce() để chọn giá trị không bị thiếu đầu tiên cho mỗi biến số:

village_detection <- c("a", "b", NA,  NA)
village_residence <- c("a", "c", "a", "d")

village <- coalesce(village_detection, village_residence)
village    # print
## [1] "a" "b" "a" "d"

Điều này hoạt động tương tự nếu bạn cung cấp các cột trong bộ dữ liệu: đối với mỗi hàng, hàm sẽ gán giá trị cột mới với giá trị không bị thiếu đầu tiên trong các cột bạn đã cung cấp (theo thứ tự được cung cấp).

linelist <- linelist %>% 
  mutate(village = coalesce(village_detection, village_residence))

Đây là một ví dụ về thao tác “theo hàng (row-wise)”. Để biết các phép tính theo hàng phức tạp hơn, hãy xem chương bên dưới về Tính toán theo hàng.

Toán lũy tích

Nếu bạn muốn một cột thể hiện tổng tích lũy/trung bình/tối thiểu/tối đa, v.v. như đánh giá các hàng của bộ dữ liệu cho đến thời điểm đó, hãy sử dụng các hàm sau:

cumsum() trả về tổng tích lũy, như được hiển thị bên dưới:

sum(c(2,4,15,10))     # returns only one number
## [1] 31
cumsum(c(2,4,15,10))  # returns the cumulative sum at each step
## [1]  2  6 21 31

Điều này có thể được sử dụng trong bộ dữ liệu khi tạo một cột mới. Ví dụ: để tính toán số ca lũy tích mỗi ngày trong một đợt bùng dịch, hãy xem xét đoạn code như sau:

cumulative_case_counts <- linelist %>%  # begin with case linelist
  count(date_onset) %>%                 # count of rows per day, as column 'n'   
  mutate(cumulative_cases = cumsum(n))  # new column, of the cumulative sum at each row

Dưới đây là 10 hàng đầu tiên:

head(cumulative_case_counts, 10)
##    date_onset n cumulative_cases
## 1  2012-04-15 1                1
## 2  2012-05-05 1                2
## 3  2012-05-08 1                3
## 4  2012-05-31 1                4
## 5  2012-06-02 1                5
## 6  2012-06-07 1                6
## 7  2012-06-14 1                7
## 8  2012-06-21 1                8
## 9  2012-06-24 1                9
## 10 2012-06-25 1               10

Xem chương Đường cong dịch bệnh để biết cách lập biểu đồ các trường hợp mới mắc tích lũy với epicurve.

Xem thêm:
cumsum(), cummean(), cummin(), cummax(), cumany(), cumall()

Sử dụng base R

Để định nghĩa một cột mới (hoặc tái định nghĩa lại một cột) bằng cách sử dụng base R, hãy viết tên của bộ dữ liệu, được liên kết với $, vào tên cột mới (hoặc cột được sửa đổi). Sử dụng toán tử gán <- để xác định (các) giá trị mới. Hãy nhớ rằng khi sử dụng base R, bạn phải chỉ định tên bộ dữ liệu trước tên cột (ví dụ: dataframe$column). Đây là một ví dụ về cách tạo cột bmi bằng cách sử dụng base R:

linelist$bmi = linelist$wt_kg / (linelist$ht_cm / 100) ^ 2)

Thêm vào chuỗi pipe

Dưới đây, một cột mới được thêm vào chuỗi pipe và một số phân lớp được chuyển đổi.

# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)
##################################################################################

# begin cleaning pipe chain
###########################
linelist <- linelist_raw %>%
    
    # standardize column name syntax
    janitor::clean_names() %>% 
    
    # manually re-name columns
           # NEW name             # OLD name
    rename(date_infection       = infection_date,
           date_hospitalisation = hosp_date,
           date_outcome         = date_of_outcome) %>% 
    
    # remove column
    select(-c(row_num, merged_header, x28)) %>% 
  
    # de-duplicate
    distinct() %>% 
  
    # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED
    ###################################################
    # add new column
    mutate(bmi = wt_kg / (ht_cm/100)^2) %>% 
  
    # convert class of columns
    mutate(across(contains("date"), as.Date), 
           generation = as.numeric(generation),
           age        = as.numeric(age)) 

8.8 Mã hóa lại giá trị

Dưới đây là một số tình huống mà bạn cần mã hóa lại (thay đổi) các giá trị:

  • để chỉnh sửa một giá trị cụ thể (ví dụ: một ngày có năm hoặc định dạng không chính xác)
  • để hợp nhất các giá trị được viết sai chính tả
  • để tạo một cột chứa giá trị phân loại mới
  • để tạo một cột chứa danh mục số mới (ví dụ: danh mục độ tuổi)

Giá trị cụ thể

Để thay đổi các giá trị theo cách thủ công, bạn có thể sử dụng hàm recode() bên trong hàm mutate().

Hãy tưởng tượng có một ngày không bình thường trong dữ liệu (ví dụ: “2014-14-15”): bạn có thể sửa ngày theo cách thủ công trong dữ liệu thô hoặc, bạn có thể ghi thay đổi trong chuỗi các bước làm sạch thông qua mutate()recode(). Cách thứ hai sẽ rõ ràng hơn và có thể được tái lập lại cho bất kỳ ai khác muốn hiểu hoặc lặp lại phân tích của bạn.

# fix incorrect values                   # old value       # new value
linelist <- linelist %>% 
  mutate(date_onset = recode(date_onset, "2014-14-15" = "2014-04-15"))

Dòng mutate() ở trên có thể được đọc là: “biến đổi cột date_onset bằng với cột date_onset sau khi được mã hóa lại để GIÁ TRỊ CŨ được đổi thành GIÁ TRỊ MỚI”. Lưu ý rằng cú pháp (CŨ = MỚI) này của hàm recode() ngược lại với hầu hết các cú pháp trong R (mới = cũ). Cộng đồng phát triển R đang làm việc để sửa đổi điều này.

Sau đây là một ví dụ khác về việc mã hóa lại nhiều giá trị trong một cột.

Trong bộ dữ liệu linelist, các giá trị trong cột “hospital” phải được làm sạch. Có một số cách viết khác nhau và nhiều giá trị bị thiếu.

table(linelist$hospital, useNA = "always")  # print table of all unique values, including missing  
## 
##                      Central Hopital                     Central Hospital                           Hospital A                           Hospital B 
##                                   11                                  457                                  290                                  289 
##                     Military Hopital                    Military Hospital                     Mitylira Hopital                    Mitylira Hospital 
##                                   32                                  798                                    1                                   79 
##                                Other                         Port Hopital                        Port Hospital St. Mark's Maternity Hospital (SMMH) 
##                                  907                                   48                                 1756                                  417 
##   St. Marks Maternity Hopital (SMMH)                                 <NA> 
##                                   11                                 1512

Lệnh recode() bên dưới tái định nghĩa lại cột “hospital” theo cột hiện tại “hospital”, nhưng với các thay đổi mã hóa được chỉ định. Đừng quên dấu phẩy sau mỗi dòng mã hóa lại!

linelist <- linelist %>% 
  mutate(hospital = recode(hospital,
                     # for reference: OLD = NEW
                      "Mitylira Hopital"  = "Military Hospital",
                      "Mitylira Hospital" = "Military Hospital",
                      "Military Hopital"  = "Military Hospital",
                      "Port Hopital"      = "Port Hospital",
                      "Central Hopital"   = "Central Hospital",
                      "other"             = "Other",
                      "St. Marks Maternity Hopital (SMMH)" = "St. Mark's Maternity Hospital (SMMH)"
                      ))

Bây giờ chúng ta thấy các cách viết trong cột hospital đã được sửa chữa và hợp nhất:

table(linelist$hospital, useNA = "always")
## 
##                     Central Hospital                           Hospital A                           Hospital B                    Military Hospital 
##                                  468                                  290                                  289                                  910 
##                                Other                        Port Hospital St. Mark's Maternity Hospital (SMMH)                                 <NA> 
##                                  907                                 1804                                  428                                 1512

MẸO: Số lượng khoảng trắng trước và sau dấu bằng không quan trọng. Hãy làm cho code của bạn dễ đọc hơn bằng cách căn chỉnh dấu = cho tất cả hoặc hầu hết các hàng. Ngoài ra, hãy cân nhắc thêm hàng bình luận để làm rõ cho người đọc biết bên nào CŨ và bên nào MỚI.

MẸO: Đôi khi tồn tại một giá trị ký tự trống trong bộ dữ liệu (không được nhận dạng là giá trị missing của R - NA). Bạn có thể tham chiếu giá trị này bằng hai dấu ngoặc kép không có khoảng trắng ở giữa ("").

Theo logic

Dưới đây, chúng ta trình bày cách mã hóa lại các giá trị trong một cột bằng cách sử dụng logic và điều kiện:

  • Sử dụng hàm replace(), ifelse()if_else() cho logic đơn giản
  • Sử dụng hàm case_when() cho logic phức tạp

Logic đơn giản

replace()

Để mã hóa lại với các tiêu chí logic đơn giản, bạn có thể sử dụng hàm replace() bên trong hàm mutate(). replace() là một hàm từ base R. Sử dụng một điều kiện logic để cụ thể các hàng cần thay đổi. Cú pháp chung là:

mutate(col_to_change = replace(col_to_change, criteria for rows, new value)).

Một tình huống phổ biến để sử dụng replace()chỉ thay đổi một giá trị trong một hàng, bằng cách sử dụng định danh hàng duy nhất. Dưới đây, giới tính được thay đổi thành “Female” trong hàng mà cột case_id có giá trị là “2195”.

# Example: change gender of one specific observation to "Female" 
linelist <- linelist %>% 
  mutate(gender = replace(gender, case_id == "2195", "Female"))

Lệnh tương tự sử dụng cú pháp trong base R và dấu ngoặc vuông [] ở bên dưới. Nó có nội dung là “Thay đổi giá trị của cột gender trong bộ dữ liệu linelist (đối với các hàng mà cột case_id của linelist có giá trị ‘2195’) thành ‘Female’”.

linelist$gender[linelist$case_id == "2195"] <- "Female"

ifelse()if_else()

Một cách khác áp dụng cho logic đơn giản là hàm ifelse() và hàm tương tự là if_else(). Tuy nhiên, trong hầu hết các trường hợp để mã hóa lại, cách rõ ràng hơn là sử dụng hàm case_when() (chi tiết bên dưới). Câu lệnh “if else” này là các phiên bản đơn giản hóa của câu lệnh lập trình ifelse. Cú pháp chung là:
ifelse(condition, value to return if condition evaluates to TRUE, value to return if condition evaluates to FALSE)

Dưới đây, cột source_known được định nghĩa. Giá trị của nó trong một hàng được đặt thành “known” nếu giá trị của hàng trong cột source không bị thiếu. Nếu thiếu giá trị trong cột source, thì giá trị trong source_known được đặt thành “unknown”.

linelist <- linelist %>% 
  mutate(source_known = ifelse(!is.na(source), "known", "unknown"))

if_else() là một phiên bản đặc biệt từ package dplyr để xử lý ngày tháng. Lưu ý rằng nếu giá trị ‘true’ là một ngày thì giá trị ‘false’ cũng phải đủ điều kiện là một ngày, do đó sử dụng giá trị đặc biệt NA_real_ thay vì chỉ NA.

# Create a date of death column, which is NA if patient has not died.
linelist <- linelist %>% 
  mutate(date_death = if_else(outcome == "Death", date_outcome, NA_real_))

Tránh lồng ghép nhiều lệnh ifelse với nhau… thay vào đó hãy sử dụng case_when()! case_when() dễ đọc hơn và bạn sẽ mắc ít lỗi hơn.

Ngoài phạm vi của bộ dữ liệu, nếu bạn muốn một đối tượng được sử dụng trong code của mình chuyển đổi giá trị của nó, hãy xem xét sử dụng hàm switch() từ base R.

Logic phức tạp

Sử dụng case_when() của package dplyr nếu bạn đang mã hóa lại thành nhiều nhóm mới hoặc nếu bạn cần sử dụng các câu lệnh logic phức tạp để mã hóa lại các giá trị. Hàm này đánh giá mọi hàng trong bộ dữ liệu, đánh giá xem các hàng có đáp ứng các tiêu chí đã chỉ định hay không và chỉ định giá trị mới chính xác.

Câu lệnh case_when() bao gồm hai cấu phần là Bên tay Phải (RHS) và Bên tay Trái (LHS) được phân tách bằng “dấu ngã” ~. Các tiêu chí logic nằm ở phía bên trái và các giá trị tương ứng nằm ở phía bên phải của mỗi mệnh đề. Các mệnh đề được phân tách bằng dấu phẩy.

Ví dụ: chúng tôi sử dụng cột ageage_unit để tạo cột age_years:

linelist <- linelist %>% 
  mutate(age_years = case_when(
            age_unit == "years"  ~ age,       # if age is given in years
            age_unit == "months" ~ age/12,    # if age is given in months
            is.na(age_unit)      ~ age,       # if age unit is missing, assume years
            TRUE                 ~ NA_real_)) # any other circumstance, assign missing

Khi mỗi hàng trong dữ liệu được đánh giá, các tiêu chí được áp dụng/đánh giá theo thứ tự đã viết trong case_when() - từ trên xuống dưới. Nếu tiêu chí hàng đầu đánh giá là TRUE cho một hàng nhất định, giá trị Bên tay phải sẽ được chỉ định và các tiêu chí còn lại thậm chí không được kiểm tra cho hàng đó. Vì vậy, tốt nhất là bạn nên viết các tiêu chí cụ thể nhất trước và cuối cùng là tiêu chí chung chung.

Dọc theo những dòng đó, trong mệnh đề cuối cùng của bạn, hãy đặt TRUE ở phía bên trái để ghi lại bất kỳ hàng nào không đáp ứng bất kỳ tiêu chí nào trước đó. Phía bên phải của câu lệnh này có thể được gán một giá trị như “kiểm tra tôi! (check me)” hoặc missing.

NGUY HIỂM: Các giá trị ở phía bên phải đều phải cùng một phần lớp - số, ký tự, ngày tháng, lôgic, v.v. Để gán giá trị thiếu (NA), bạn có thể cần sử dụng các biến thể đặc biệt của NA như NA_character_, NA_real_ (cho số hoặc POSIX) và as.Date(NA). Đọc thêm trong chương Làm việc với ngày tháng.

Giá trị missing

Dưới đây là các hàm đặc biệt để xử lý các giá trị bị thiếu trong khi làm sạch dữ liệu.

Xem chương về Dữ liệu Missing để biết thêm các mẹo chi tiết về xác định và xử lý các giá trị bị thiếu. Ví dụ, hàm is.na() kiểm tra một cách logic sự thiếu giá trị.

replace_na()

Để thay đổi các giá trị bị thiếu (NA) thành một giá trị cụ thể, chẳng hạn như “Missing”, hãy sử dụng hàm replace_na() trong hàm mutate() trong package dplyr. Lưu ý rằng điều này được sử dụng theo cách tương tự như hàm recode ở trên - tên của biến phải được lặp lại trong replace_na().

linelist <- linelist %>% 
  mutate(hospital = replace_na(hospital, "Missing"))

fct_explicit_na()

Đây là một hàm từ package forcats. Package forcats xử lý các cột dạng Factor. Factor là cách của R để xử lý các giá trị có thứ tự như c("First", "Second", "Third") hoặc để thiết lập thứ tự cho các giá trị (ví dụ: bệnh viện) xuất hiện trong bảng và biểu đồ. Xem chương Factors.

Nếu dữ liệu của bạn là kiểu Factor và bạn đang cố gắng chuyển giá trị NA thành “Missing” bằng cách sử dụng hàm replace_na(), bạn sẽ gặp lỗi này: invalid factor level, NA generated. Bạn đã cố gắng thêm “Missing” làm giá trị, trong khi nó đã không được xác định là một thứ bậc trong cột Factor, và câu lệnh bị từ chối.

Cách dễ nhất để giải quyết vấn đề này là sử dụng hàm forcats fct_explicit_na() để chuyển đổi một cột thành kiểu Factor và chuyển đổi các giá trị NA thành ký tự “(Missing)”.

linelist %>% 
  mutate(hospital = fct_explicit_na(hospital))

Một giải pháp thay thế chậm hơn sẽ là thêm thứ bậc cho cột Factor bằng cách sử dụng hàm fct_expand() và sau đó chuyển đổi các giá trị bị thiếu.

na_if()

Để chuyển đổi một giá trị cụ thể thành NA, hãy sử dụng hàm na_if() trong package dplyr. Hàm này thực hiện thao tác ngược lại với hàm replace_na(). Trong ví dụ bên dưới, bất kỳ giá trị nào bị “Missing” trong cột hospital đều được chuyển đổi thành NA.

linelist <- linelist %>% 
  mutate(hospital = na_if(hospital, "Missing"))

Lưu ý: na_if() không thể sử dụng cho điều kiện logic (ví dụ: “all values > 99”) - hãy sử dụng replace() hoặc case_when() cho điều này:

# Convert temperatures above 40 to NA 
linelist <- linelist %>% 
  mutate(temp = replace(temp, temp > 40, NA))

# Convert onset dates earlier than 1 Jan 2000 to missing
linelist <- linelist %>% 
  mutate(date_onset = replace(date_onset, date_onset > as.Date("2000-01-01"), NA))

Từ điển làm sạch

Sử dụng package linelist trong R và hàm clean_variable_spelling() để làm sạch bộ dữ liệu bằng từ điển làm sạch. linelist là một package được phát triển bởi RECON - R Epidemics Consortium.

  1. Tạo từ điển làm sạch với 3 cột:

    • Cột “từ” (giá trị không chính xác)
    • Cột “tới” (giá trị chính xác)
    • Một cột chỉ định cột cho các thay đổi sẽ được áp dụng (hoặc “.global” để áp dụng cho tất cả các cột)

Lưu ý: các mục từ điển .global sẽ bị ghi đè bởi các mục từ điển theo cột cụ thể.

  1. Nạp tệp từ điển vào R. Ví dụ này có thể được tải xuống theo hướng dẫn trong chương Tải sách và dữ liệu.
cleaning_dict <- import("cleaning_dict.csv")
  1. Chuyển bộ dữ liệu linelist ban đầu đến hàm clean_variable_spelling(), xác định wordlists = là bộ dữ liệu từ điển làm sạch. Đối số spelling_vars = có thể được sử dụng để chỉ định cột nào trong từ điển tham chiếu đến các cột khác (cột thứ 3 theo mặc định) hoặc có thể được đặt thành NULL để từ điển áp dụng cho tất cả các cột dạng ký tự và phân loại. Lưu ý rằng hàm này có thể mất nhiều thời gian để chạy.
linelist <- linelist %>% 
  linelist::clean_variable_spelling(
    wordlists = cleaning_dict,
    spelling_vars = "col",        # dict column containing column names, defaults to 3rd column in dict
  )

Bây giờ, hãy cuộn sang bên phải để xem các giá trị đã thay đổi như thế nào - đặc biệt là gender (chữ thường thành chữ hoa) và tất cả các cột triệu chứng đã được chuyển từ có/không thành 1/0.

Lưu ý rằng tên cột của bạn trong từ điển làm sạch phải tương ứng với tên tại thời điểm trong script làm sạch của bạn. Xem tài liệu tham khảo trực tuyến về package linelist để biết thêm chi tiết.

Thêm vào chuỗi làm sạch

Dưới đây, một số cột mới và biến đổi cột được thêm vào chuỗi các bước làm sạch.

# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)
##################################################################################

# begin cleaning pipe chain
###########################
linelist <- linelist_raw %>%
    
    # standardize column name syntax
    janitor::clean_names() %>% 
    
    # manually re-name columns
           # NEW name             # OLD name
    rename(date_infection       = infection_date,
           date_hospitalisation = hosp_date,
           date_outcome         = date_of_outcome) %>% 
    
    # remove column
    select(-c(row_num, merged_header, x28)) %>% 
  
    # de-duplicate
    distinct() %>% 
  
    # add column
    mutate(bmi = wt_kg / (ht_cm/100)^2) %>%     

    # convert class of columns
    mutate(across(contains("date"), as.Date), 
           generation = as.numeric(generation),
           age        = as.numeric(age)) %>% 
    
    # add column: delay to hospitalisation
    mutate(days_onset_hosp = as.numeric(date_hospitalisation - date_onset)) %>% 
    
   # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED
   ###################################################

    # clean values of hospital column
    mutate(hospital = recode(hospital,
                      # OLD = NEW
                      "Mitylira Hopital"  = "Military Hospital",
                      "Mitylira Hospital" = "Military Hospital",
                      "Military Hopital"  = "Military Hospital",
                      "Port Hopital"      = "Port Hospital",
                      "Central Hopital"   = "Central Hospital",
                      "other"             = "Other",
                      "St. Marks Maternity Hopital (SMMH)" = "St. Mark's Maternity Hospital (SMMH)"
                      )) %>% 
    
    mutate(hospital = replace_na(hospital, "Missing")) %>% 

    # create age_years column (from age and age_unit)
    mutate(age_years = case_when(
          age_unit == "years" ~ age,
          age_unit == "months" ~ age/12,
          is.na(age_unit) ~ age,
          TRUE ~ NA_real_))

8.9 Biến danh mục số

Ở đây chúng tôi mô tả một số cách tiếp cận đặc biệt để tạo cột dạng danh mục từ các cột dạng số. Các ví dụ phổ biến bao gồm danh mục độ tuổi, nhóm giá trị trong phòng thí nghiệm, v.v. Ở đây chúng ta sẽ thảo luận:

  • age_categories(), từ package epikit
  • cut(), từ base R
  • case_when()
  • Chia khoảng phân vị với quantile() and ntile()

Đánh giá phân phối

Trong ví dụ này, chúng tôi sẽ tạo một cột age_cat bằng cách sử dụng cột age_years.

#check the class of the linelist variable age
class(linelist$age_years)
## [1] "numeric"

Trước tiên, hãy kiểm tra phân phối dữ liệu của bạn để đưa ra các điểm cắt thích hợp. Xem chương ggplot cơ bản.

# examine the distribution
hist(linelist$age_years)
summary(linelist$age_years, na.rm=T)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##    0.00    6.00   13.00   16.04   23.00   84.00     107

CẨN TRỌNG: Đôi khi, các biến số sẽ được nhập vào dưới dạng kiểu “ký tự”. Điều này xảy ra nếu có các ký tự không phải số trong một số giá trị, ví dụ: có bản ghi nhập “2 tháng” cho biến tuổi hoặc (tùy thuộc vào cài đặt ngôn ngữ R của bạn) nếu dấu phẩy được sử dụng cho số thập phân (ví dụ: “4,5”nghĩa là bốn năm rưỡi).

age_categories()

Với package epikit, bạn có thể sử dụng hàm age_categories() để dễ dàng phân loại và gắn nhãn các cột dạng số (lưu ý: hàm này cũng có thể được áp dụng cho các biến ngoài biến tuổi). Như một “phần thưởng”, cột trả về tự động là một cột dạng factor có thứ tự.

Dưới đây là các yêu cầu đầu vào:

  • Một vectơ (cột) dạng số
  • Đối sốc breakers = - cung cấp một vectơ dạng số của các điểm cắt cho các phân nhóm mới.

Đầu tiên, ví dụ đơn giản nhất:

# Simple example
################
pacman::p_load(epikit)                    # load package

linelist <- linelist %>% 
  mutate(
    age_cat = age_categories(             # create new column
      age_years,                            # numeric column to make groups from
      breakers = c(0, 5, 10, 15, 20,        # break points
                   30, 40, 50, 60, 70)))

# show table
table(linelist$age_cat, useNA = "always")
## 
##   0-4   5-9 10-14 15-19 20-29 30-39 40-49 50-59 60-69   70+  <NA> 
##  1227  1223  1048   827  1216   597   251    78    27     7   107

Các giá trị cắt bạn chỉ định mặc định là các giới hạn thấp hơn - nghĩa là chúng được đưa vào nhóm “cao hơn”/các nhóm “mở” ở phía dưới/bên trái. Như trình bày dưới đây, bạn có thể thêm 1 vào mỗi giá trị cắt để đạt được các nhóm mà được “mở” ở trên cùng/bên phải.

# Include upper ends for the same categories
############################################
linelist <- linelist %>% 
  mutate(
    age_cat = age_categories(
      age_years, 
      breakers = c(0, 6, 11, 16, 21, 31, 41, 51, 61, 71)))

# show table
table(linelist$age_cat, useNA = "always")
## 
##   0-5  6-10 11-15 16-20 21-30 31-40 41-50 51-60 61-70   71+  <NA> 
##  1469  1195  1040   770  1149   547   231    70    24     6   107

Bạn có thể điều chỉnh cách hiển thị nhãn bằng separator =. Giá trị mặc định là “-”

Bạn có thể điều chỉnh cách xử lý các số ở hàng đầu, với đối số ceiling =. Thiết lập mức giới hạn cắt trên với ceiling = TRUE. Trong cách sử dụng này, giá trị cắt cao nhất được cung cấp là “ceiling” và danh mục “XX+” không được tạo. Bất kỳ giá trị nào trên giá trị cắt cao nhất (hoặc upper =, nếu được xác định) đều được phân loại là NA. Dưới đây là một ví dụ với ceiling = TRUE, do đó không có danh mục nào là XX + và các giá trị trên 70 (giá trị cắt cao nhất) được chỉ định là NA.

# With ceiling set to TRUE
##########################
linelist <- linelist %>% 
  mutate(
    age_cat = age_categories(
      age_years, 
      breakers = c(0, 5, 10, 15, 20, 30, 40, 50, 60, 70),
      ceiling = TRUE)) # 70 is ceiling, all above become NA

# show table
table(linelist$age_cat, useNA = "always")
## 
##   0-4   5-9 10-14 15-19 20-29 30-39 40-49 50-59 60-70  <NA> 
##  1227  1223  1048   827  1216   597   251    78    28   113

Ngoài ra, thay vì dùng breakers =, bạn có thể sử dụng hàm lower =, upper =, và by =:

  • lower = Mức giới hạn thấp nhất bạn muốn xem xét - mặc định là 0
  • upper = Mức giới hạn cao nhất bạn muốn xem xét
  • by = Khoảng cách “năm” giữa các nhóm
linelist <- linelist %>% 
  mutate(
    age_cat = age_categories(
      age_years, 
      lower = 0,
      upper = 100,
      by = 10))

# show table
table(linelist$age_cat, useNA = "always")
## 
##   0-9 10-19 20-29 30-39 40-49 50-59 60-69 70-79 80-89 90-99  100+  <NA> 
##  2450  1875  1216   597   251    78    27     6     1     0     0   107

Xem thêm trang trợ giúp của hàm để biết thêm chi tiết (nhập ?age_categories trong R console).

cut()

Hàm cut() trong base R là một thay thế cho hàm age_categories(), nhưng tôi nghĩ bạn sẽ thấy lý do tại sao hàm age_categories() được phát triển để đơn giản hóa quá trình này. Một số khác biệt đáng chú ý với hàm age_categories() là:

  • Bạn không cần cài đặt/gọi package khác
  • Bạn có thể chỉ định xem các nhóm được mở/đóng ở bên phải/bên trái
  • Bạn cần phải cung cấp nhãn chính xác
  • Nếu bạn muốn 0 được thêm vào trong nhóm thấp nhất, bạn cần phải cụ thể điều này

Cú pháp cơ bản trong cut() trước tiên là cung cấp cột dạng số cần xử lý (age_years), sau đó là đối số break, một vectơ dạng số c() bao gồm các điểm cắt. Sử dụng cut(), kết quả trả về là cột kiểu factor có thứ tự.

Mặc định, việc phân loại xảy ra sao cho phía bên phải/phía trên là “mở” và có tính dung nạp (và phía bên trái/phía dưới là “đóng” hoặc tách biệt). Cách vận hành của hàm này ngược lại với hàm age_categories(). Các nhãn mặc định sử dụng ký hiệu “(A,B]”, có nghĩa là A không được bao gồm nhưng B thì có. Đảo ngược hành động này bằng cách cung cấp đối số right = TRUE.

Do đó, theo mặc định, các giá trị “0” bị loại trừ khỏi nhóm thấp nhất và được phân loại là NA! Giá trị “0” có thể là trẻ sơ sinh được mã hóa là 0 tuổi, vì vậy hãy thật thận trọng! Để thay đổi điều này, hãy thêm argument include.lowest = TRUE để mọi giá trị “0” sẽ được đưa vào nhóm thấp nhất. Nhãn được tạo tự động cho danh mục thấp nhất sau đó sẽ là “[A],B]”. Lưu ý rằng nếu bạn thêm include.lowest = TRUE right = TRUE, hành động này bây giờ sẽ áp dụng cho điểm cắt giá trị cao nhất và phân loại, không phải điểm thấp nhất.

Bạn có thể cung cấp một vectơ nhãn tùy chỉnh bằng cách sử dụng argument labels =. Vì chúng được viết theo cách thủ công, hãy rất cẩn thận để đảm bảo chúng chính xác! Kiểm tra bằng cách lập bảng chéo, như được mô tả bên dưới.

Dưới đây là một ví dụ về hàm cut() được áp dụng cho biến age_years để tạo biến age_cat mới:

# Create new variable, by cutting the numeric age variable
# lower break is excluded but upper break is included in each category
linelist <- linelist %>% 
  mutate(
    age_cat = cut(
      age_years,
      breaks = c(0, 5, 10, 15, 20,
                 30, 50, 70, 100),
      include.lowest = TRUE         # include 0 in lowest group
      ))

# tabulate the number of observations per group
table(linelist$age_cat, useNA = "always")
## 
##    [0,5]   (5,10]  (10,15]  (15,20]  (20,30]  (30,50]  (50,70] (70,100]     <NA> 
##     1469     1195     1040      770     1149      778       94        6      107

Kiểm tra đoạn code của bạn!!! Hãy chắc chắn rằng mỗi giá trị độ tuổi đã được chỉ định đúng danh mục bằng cách lập bảng chéo giữa các cột số và danh mục. Kiểm tra việc gán các giá trị ranh giới (ví dụ: 15, nếu các danh mục lân cận là 10-15 và 16-20).

# Cross tabulation of the numeric and category columns. 
table("Numeric Values" = linelist$age_years,   # names specified in table for clarity.
      "Categories"     = linelist$age_cat,
      useNA = "always")                        # don't forget to examine NA values
##                     Categories
## Numeric Values       [0,5] (5,10] (10,15] (15,20] (20,30] (30,50] (50,70] (70,100] <NA>
##   0                    136      0       0       0       0       0       0        0    0
##   0.0833333333333333     1      0       0       0       0       0       0        0    0
##   0.25                   2      0       0       0       0       0       0        0    0
##   0.333333333333333      6      0       0       0       0       0       0        0    0
##   0.416666666666667      1      0       0       0       0       0       0        0    0
##   0.5                    6      0       0       0       0       0       0        0    0
##   0.583333333333333      3      0       0       0       0       0       0        0    0
##   0.666666666666667      3      0       0       0       0       0       0        0    0
##   0.75                   3      0       0       0       0       0       0        0    0
##   0.833333333333333      1      0       0       0       0       0       0        0    0
##   0.916666666666667      1      0       0       0       0       0       0        0    0
##   1                    275      0       0       0       0       0       0        0    0
##   1.5                    2      0       0       0       0       0       0        0    0
##   2                    308      0       0       0       0       0       0        0    0
##   3                    246      0       0       0       0       0       0        0    0
##   4                    233      0       0       0       0       0       0        0    0
##   5                    242      0       0       0       0       0       0        0    0
##   6                      0    241       0       0       0       0       0        0    0
##   7                      0    256       0       0       0       0       0        0    0
##   8                      0    239       0       0       0       0       0        0    0
##   9                      0    245       0       0       0       0       0        0    0
##   10                     0    214       0       0       0       0       0        0    0
##   11                     0      0     220       0       0       0       0        0    0
##   12                     0      0     224       0       0       0       0        0    0
##   13                     0      0     191       0       0       0       0        0    0
##   14                     0      0     199       0       0       0       0        0    0
##   15                     0      0     206       0       0       0       0        0    0
##   16                     0      0       0     186       0       0       0        0    0
##   17                     0      0       0     164       0       0       0        0    0
##   18                     0      0       0     141       0       0       0        0    0
##   19                     0      0       0     130       0       0       0        0    0
##   20                     0      0       0     149       0       0       0        0    0
##   21                     0      0       0       0     158       0       0        0    0
##   22                     0      0       0       0     149       0       0        0    0
##   23                     0      0       0       0     125       0       0        0    0
##   24                     0      0       0       0     144       0       0        0    0
##   25                     0      0       0       0     107       0       0        0    0
##   26                     0      0       0       0     100       0       0        0    0
##   27                     0      0       0       0     117       0       0        0    0
##   28                     0      0       0       0      85       0       0        0    0
##   29                     0      0       0       0      82       0       0        0    0
##   30                     0      0       0       0      82       0       0        0    0
##   31                     0      0       0       0       0      68       0        0    0
##   32                     0      0       0       0       0      84       0        0    0
##   33                     0      0       0       0       0      78       0        0    0
##   34                     0      0       0       0       0      58       0        0    0
##   35                     0      0       0       0       0      58       0        0    0
##   36                     0      0       0       0       0      33       0        0    0
##   37                     0      0       0       0       0      46       0        0    0
##   38                     0      0       0       0       0      45       0        0    0
##   39                     0      0       0       0       0      45       0        0    0
##   40                     0      0       0       0       0      32       0        0    0
##   41                     0      0       0       0       0      34       0        0    0
##   42                     0      0       0       0       0      26       0        0    0
##   43                     0      0       0       0       0      31       0        0    0
##   44                     0      0       0       0       0      24       0        0    0
##   45                     0      0       0       0       0      27       0        0    0
##   46                     0      0       0       0       0      25       0        0    0
##   47                     0      0       0       0       0      16       0        0    0
##   48                     0      0       0       0       0      21       0        0    0
##   49                     0      0       0       0       0      15       0        0    0
##   50                     0      0       0       0       0      12       0        0    0
##   51                     0      0       0       0       0       0      13        0    0
##   52                     0      0       0       0       0       0       7        0    0
##   53                     0      0       0       0       0       0       4        0    0
##   54                     0      0       0       0       0       0       6        0    0
##   55                     0      0       0       0       0       0       9        0    0
##   56                     0      0       0       0       0       0       7        0    0
##   57                     0      0       0       0       0       0       9        0    0
##   58                     0      0       0       0       0       0       6        0    0
##   59                     0      0       0       0       0       0       5        0    0
##   60                     0      0       0       0       0       0       4        0    0
##   61                     0      0       0       0       0       0       2        0    0
##   62                     0      0       0       0       0       0       1        0    0
##   63                     0      0       0       0       0       0       5        0    0
##   64                     0      0       0       0       0       0       1        0    0
##   65                     0      0       0       0       0       0       5        0    0
##   66                     0      0       0       0       0       0       3        0    0
##   67                     0      0       0       0       0       0       2        0    0
##   68                     0      0       0       0       0       0       1        0    0
##   69                     0      0       0       0       0       0       3        0    0
##   70                     0      0       0       0       0       0       1        0    0
##   72                     0      0       0       0       0       0       0        1    0
##   73                     0      0       0       0       0       0       0        3    0
##   76                     0      0       0       0       0       0       0        1    0
##   84                     0      0       0       0       0       0       0        1    0
##   <NA>                   0      0       0       0       0       0       0        0  107

Gán nhãn giá trị NA

Bạn có thể muốn gán nhãn cho các giá trị NA chẳng hạn như “Missing”. Bởi vì cột mới có kiểu Factor (số giá trị bị hạn chế), bạn không thể chỉ đơn giản thay đổi nó bằng replace_na(), vì giá trị này sẽ không được chấp nhận. Thay vào đó, hãy sử dụng hàm fct_explicit_na() từ package forcats như được giải thích trong chương Factors.

linelist <- linelist %>% 
  
  # cut() creates age_cat, automatically of class Factor      
  mutate(age_cat = cut(
    age_years,
    breaks = c(0, 5, 10, 15, 20, 30, 50, 70, 100),          
    right = FALSE,
    include.lowest = TRUE,        
    labels = c("0-4", "5-9", "10-14", "15-19", "20-29", "30-49", "50-69", "70-100")),
         
    # make missing values explicit
    age_cat = fct_explicit_na(
      age_cat,
      na_level = "Missing age")  # you can specify the label
  )    

# table to view counts
table(linelist$age_cat, useNA = "always")
## 
##         0-4         5-9       10-14       15-19       20-29       30-49       50-69      70-100 Missing age        <NA> 
##        1227        1223        1048         827        1216         848         105           7         107           0

Tạo nhanh điểm cắt và dán nhãn

Để nhanh chóng tạo điểm cắt và gắn nhãn vectơ, hãy thực hành như bên dưới. Xem chương R cơ bản về hàm seq()rep().

# Make break points from 0 to 90 by 5
age_seq = seq(from = 0, to = 90, by = 5)
age_seq

# Make labels for the above categories, assuming default cut() settings
age_labels = paste0(age_seq + 1, "-", age_seq + 5)
age_labels

# check that both vectors are the same length
length(age_seq) == length(age_labels)

Đọc thêm về hàm cut() trong trang Trợ giúp của hàm bằng cách nhập ?cut trong R console.

Chia khoảng phân vị

Theo cách hiểu thông thường, “phân vị” hoặc “phần trăm” thường đề cập đến một giá trị dưới ngưỡng tỷ lệ đang được đề cập. Ví dụ: phân vị thứ thứ 95 của biến độ tuổi trong linelist sẽ là các độ tuổi dưới 95% độ tuổi.

Tuy nhiên, trong cách nói thông thường, “tứ phân vị” và “thập phân vị” cũng có thể đề cập đến phân nhóm dữ liệu được chia đều thành 4 hoặc 10 nhóm (lưu ý sẽ có thêm một điểm cắt so với số nhóm).

Để nhận biết điểm cắt của khoảng phân vị, bạn có thể sử dụng hàm quantile() từ package stats trong base R. Bạn cung cấp một vectơ số (ví dụ: một cột trong bộ dữ liệu) và vectơ các giá trị xác suất số từ 0 đến 1. Các điểm cắt được trả về dưới dạng một vectơ số. Đọc thêm chi tiết các phương pháp thống kê bằng cách nhập ?quantile.

  • Nếu vectơ số đầu vào của bạn có bất kỳ giá trị nào bị thiếu, tốt nhất nên thiết lập na.rm = TRUE
  • Thiết lậpnames = FALSE để lấy một vectơ số chưa được đặt tên
quantile(linelist$age_years,               # specify numeric vector to work on
  probs = c(0, .25, .50, .75, .90, .95),   # specify the percentiles you want
  na.rm = TRUE)                            # ignore missing values 
##  0% 25% 50% 75% 90% 95% 
##   0   6  13  23  33  41

Bạn có thể sử dụng kết quả của quantile() làm điểm cắt trong age_categories() hoặc cut(). Dưới đây, chúng ta tạo một cột deciles mới bằng cách sử dụng hàm cut() trong đó các dấu ngắt được xác định bằng cách sử dụng hàm quantiles() trên cột age_years. Sau đó, chúng ta hiển thị kết quả bằng hàm tabyl() từ package janitor để bạn có thể xem tỷ lệ phần trăm (xem chương Bảng mô tả). Lưu ý rằng chúng không chính xác là 10% trong mỗi nhóm.

linelist %>%                                # begin with linelist
  mutate(deciles = cut(age_years,           # create new column decile as cut() on column age_years
    breaks = quantile(                      # define cut breaks using quantile()
      age_years,                               # operate on age_years
      probs = seq(0, 1, by = 0.1),             # 0.0 to 1.0 by 0.1
      na.rm = TRUE),                           # ignore missing values
    include.lowest = TRUE)) %>%             # for cut() include age 0
  janitor::tabyl(deciles)                   # pipe to table to display
##  deciles   n    percent valid_percent
##    [0,2] 748 0.11319613    0.11505922
##    (2,5] 721 0.10911017    0.11090601
##    (5,7] 497 0.07521186    0.07644978
##   (7,10] 698 0.10562954    0.10736810
##  (10,13] 635 0.09609564    0.09767728
##  (13,17] 755 0.11425545    0.11613598
##  (17,21] 578 0.08746973    0.08890940
##  (21,26] 625 0.09458232    0.09613906
##  (26,33] 596 0.09019370    0.09167820
##  (33,84] 648 0.09806295    0.09967697
##     <NA> 107 0.01619249            NA

Nhóm có khoảng đều

Một công cụ khác để tạo nhóm dạng số là hàm ntile() trong package dplyr, cố gắng chia dữ liệu của bạn thành n nhóm có khoảng cách đều - nhưng lưu ý rằng không giống như với quantile(), cùng một giá trị có thể xuất hiện trong nhiều nhóm. Cung cấp vectơ số và sau đó là số lượng nhóm. Các giá trị trong cột mới được tạo chỉ là nhóm “số” (ví dụ: 1 đến 10), không phải phạm vi giá trị như khi sử dụng hàm cut().

# make groups with ntile()
ntile_data <- linelist %>% 
  mutate(even_groups = ntile(age_years, 10))

# make table of counts and proportions by group
ntile_table <- ntile_data %>% 
  janitor::tabyl(even_groups)
  
# attach min/max values to demonstrate ranges
ntile_ranges <- ntile_data %>% 
  group_by(even_groups) %>% 
  summarise(
    min = min(age_years, na.rm=T),
    max = max(age_years, na.rm=T)
  )
## Warning in min(age_years, na.rm = T): no non-missing arguments to min; returning Inf
## Warning in max(age_years, na.rm = T): no non-missing arguments to max; returning -Inf
# combine and print - note that values are present in multiple groups
left_join(ntile_table, ntile_ranges, by = "even_groups")
##  even_groups   n    percent valid_percent min  max
##            1 651 0.09851695    0.10013844   0    2
##            2 650 0.09836562    0.09998462   2    5
##            3 650 0.09836562    0.09998462   5    7
##            4 650 0.09836562    0.09998462   7   10
##            5 650 0.09836562    0.09998462  10   13
##            6 650 0.09836562    0.09998462  13   17
##            7 650 0.09836562    0.09998462  17   21
##            8 650 0.09836562    0.09998462  21   26
##            9 650 0.09836562    0.09998462  26   33
##           10 650 0.09836562    0.09998462  33   84
##           NA 107 0.01619249            NA Inf -Inf

case_when()

Có thể sử dụng hàm case_when() từ dplyr để tạo biến danh mục từ cột số, tuy nhiên điều này được thực hiện dễ dàng hơn với hàm age_categories() từ epikit hoặc cut() vì chúng sẽ tự động tạo ra một biến factor có thứ tự.

Nếu sử dụng case_when(), vui lòng xem lại cách sử dụng thích hợp như đã mô tả trước đó trong mục Mã hóa lại giá trị. Cũng lưu ý rằng tất cả các giá trị bên tay phải phải thuộc cùng một kiểu. Do đó, nếu bạn muốn giá trị NA ở phía bên phải, bạn nên viết “Missing” hoặc sử dụng giá trị NA đặc biệt là NA_character_.

Thêm vào chuỗi làm sạch

Dưới đây, code để tạo hai cột phân loại tuổi được thêm vào chuỗi các bước làm sạch:

# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)
##################################################################################

# begin cleaning pipe chain
###########################
linelist <- linelist_raw %>%
    
    # standardize column name syntax
    janitor::clean_names() %>% 
    
    # manually re-name columns
           # NEW name             # OLD name
    rename(date_infection       = infection_date,
           date_hospitalisation = hosp_date,
           date_outcome         = date_of_outcome) %>% 
    
    # remove column
    select(-c(row_num, merged_header, x28)) %>% 
  
    # de-duplicate
    distinct() %>% 

    # add column
    mutate(bmi = wt_kg / (ht_cm/100)^2) %>%     

    # convert class of columns
    mutate(across(contains("date"), as.Date), 
           generation = as.numeric(generation),
           age        = as.numeric(age)) %>% 
    
    # add column: delay to hospitalisation
    mutate(days_onset_hosp = as.numeric(date_hospitalisation - date_onset)) %>% 
    
    # clean values of hospital column
    mutate(hospital = recode(hospital,
                      # OLD = NEW
                      "Mitylira Hopital"  = "Military Hospital",
                      "Mitylira Hospital" = "Military Hospital",
                      "Military Hopital"  = "Military Hospital",
                      "Port Hopital"      = "Port Hospital",
                      "Central Hopital"   = "Central Hospital",
                      "other"             = "Other",
                      "St. Marks Maternity Hopital (SMMH)" = "St. Mark's Maternity Hospital (SMMH)"
                      )) %>% 
    
    mutate(hospital = replace_na(hospital, "Missing")) %>% 

    # create age_years column (from age and age_unit)
    mutate(age_years = case_when(
          age_unit == "years" ~ age,
          age_unit == "months" ~ age/12,
          is.na(age_unit) ~ age,
          TRUE ~ NA_real_)) %>% 
  
    # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED
    ###################################################   
    mutate(
          # age categories: custom
          age_cat = epikit::age_categories(age_years, breakers = c(0, 5, 10, 15, 20, 30, 50, 70)),
        
          # age categories: 0 to 85 by 5s
          age_cat5 = epikit::age_categories(age_years, breakers = seq(0, 85, 5)))

8.10 Thêm hàng

Thêm từng hàng một

Việc thêm từng hàng một theo cách thủ công là rất nhàm chán nhưng có thể được thực hiện với hàm add_row() từ package dplyr. Hãy nhớ rằng mỗi cột chỉ được chứa các giá trị của một phân lớp (ký tự, số, lôgic, v.v.). Vì vậy, khi thêm một hàng cần chú ý tới điều này.

linelist <- linelist %>% 
  add_row(row_num = 666,
          case_id = "abc",
          generation = 4,
          `infection date` = as.Date("2020-10-10"),
          .before = 2)

Sử dụng .before.after. để cụ thể vị trí của hàng bạn muốn thêm. .before = 3 sẽ cho phép hàng mới được thêm trước hàng thứ 3 hiện tại. Mặc định thì hàng sẽ được thêm vào dòng cuối cùng. Các cột không được chỉ định sẽ được để trống (NA).

Số thứ tự hàng mới có thể trông lạ (“… 23”) nhưng số thứ tự hàng trong các hàng tồn tại trước đó đã thay đổi. Vì vậy, nếu sử dụng lệnh hai lần, hãy kiểm tra/thử việc chèn một cách cẩn thận.

Nếu một phân lớp bạn cung cấp không hoạt động, bạn sẽ thấy báo lỗi như sau:

Error: Can't combine ..1$infection date <date> and ..2$infection date <character>.

(khi chèn một hàng có giá trị ngày tháng, hãy nhớ đặt thông tin as.Date() ví dụ như as.Date("2020-10-10")).

Nối các hàng

Để kết hợp các bộ dữ liệu với nhau bằng cách liên kết các hàng của một bộ dữ liệu này với phần cuối bộ dữ liệu khác, bạn có thể sử dụng hàm bind_rows() từ dplyr. Điều này được giải thích chi tiết hơn trong chương Nối dữ liệu.

8.11 Lọc hàng

Bước làm sạch điển hình sau khi bạn đã làm sạch các cột và các giá trị được mã hóa lại đó là lọc bộ dữ liệu cho các hàng cụ thể bằng cách sử dụng hàm filter() trong package dplyr.

Trong hàm filter(), hãy cụ thể điều kiện logic phải là giá trị TRUE cho một hàng được giữ trong bộ dữ liệu. Dưới đây chúng tôi trình bày cách lọc hàng dựa trên điều kiện logic đơn giản và phức tạp.

Lọc đơn giản

Ví dụ đơn giản này định nghĩa lại bộ dữ liệu linelist, đã lọc các hàng để đáp ứng điều kiện logic. Chỉ những hàng mà câu lệnh logic trong dấu ngoặc đơn đánh giá là TRUE mới được giữ lại.

Trong ví dụ này, câu lệnh logic là gender == "f", được hỏi liệu giá trị trong cột gender có bằng “f” (phân biệt chữ hoa chữ thường) hay không.

Trước khi bộ lọc được áp dụng, số hàng trong linelistnrow(linelist).

linelist <- linelist %>% 
  filter(gender == "f")   # keep only rows where gender is equal to "f"

Sau khi bộ lọc được áp dụng, số hàng trong linelistlinelist %>% filter(gender == "f") %>% nrow().

Lọc bỏ các giá trị missing

Một điều khá phổ biến là bạn muốn lọc bỏ các hàng có giá trị missing. Hãy bỏ qua cách lọc cổ điển filter(!is.na(column) & !is.na(column)) và thay vào đó, hãy sử dụng hàm drop_na() trong package tidyr được xây dựng để tùy chỉnh cho mục đích này. Nếu chạy với dấu ngoặc trống, nó sẽ loại bỏ các hàng có bất kỳ giá trị nào bị missing. Ngoài ra, bạn có thể cung cấp tên của các cột cụ thể sẽ được đánh giá về mức độ missing hoặc sử dụng các hàm trợ giúp “tidyselect” như được mô tả ở trên.

linelist %>% 
  drop_na(case_id, age_years)  # drop rows with missing values for case_id or age_years

Xem chương về Dữ liệu Missing để biết nhiều kỹ thuật phân tích và quản lý dữ liệu missing.

Lọc bằng số thứ tự của hàng

Trong một data frame hoặc tibble, mỗi hàng thường sẽ có một “số thứ tự” (khi xem trong R Viewer) xuất hiện ở bên trái của cột đầu tiên. Bản thân nó không phải là một cột trong dữ liệu, nhưng nó có thể được sử dụng trong câu lệnh filter().

Để lọc dựa trên “số thứ tự của hàng”, bạn có thể sử dụng hàm dplyr row_number() với “dấu ngoặc đơn mở” như một phần của câu lệnh lọc logic. Thường thì bạn sẽ sử dụng toán tử %in% và một dãy số như một phần của câu lệnh logic đó, như được trình bày bên dưới. Để xem N hàng đầu tiên, bạn cũng có thể sử dụng hàm đặc biệt head() trong package dplyr.

# View first 100 rows
linelist %>% head(100)     # or use tail() to see the n last rows

# Show row 5 only
linelist %>% filter(row_number() == 5)

# View rows 2 through 20, and three specific columns
linelist %>% filter(row_number() %in% 2:20) %>% select(date_onset, outcome, age)

Bạn cũng có thể chuyển đổi số thứ tự hàng thành cột bằng cách sử dụng hàm tibble rownames_to_column() (không viết bất kỳ ký tự nào trong dấu ngoặc đơn).

Lọc phức tạp

Các câu lệnh logic phức tạp hơn có thể được xây dựng bằng cách sử dụng các toán tử ngoặc đơn ( ), HOẶC |, PHỦ ĐỊNH !, %in%, và VÀ &. Dưới đây là một ví dụ:

Lưu ý: Bạn có thể sử dụng ! trước một mệnh đề logic để phủ định nó. Ví dụ, !is.na(column) trả về giá trị TRUE nếu cột đó không bị thiếu giá trị. Tương tự như vậy !column %in% c("a", "b", "c") trả về kết quả là TRUE nếu giá trị cột không có trong vectơ.

Kiểm tra dữ liệu

Dưới đây là một dòng lệnh đơn giản để tạo biểu đồ các ngày bắt đầu. Chúng ta thấy rằng có một đợt bùng phát nhỏ thứ hai từ năm 2012-2013 cũng được bao gồm trong tập bộ liệu thô này. Trong các phân tích, chúng ta muốn xóa các bản ghi từ đợt bùng phát sớm.

hist(linelist$date_onset, breaks = 50)

Cách lọc và xử lý các giá trị số và ngày bị missing

Chúng ta có thể chỉ lọc theo date_onset đến những hàng sau tháng 6 năm 2013 không? Lưu ý! Việc áp dụng code filter(date_onset > as.Date("2013-06-01"))) sẽ xóa bất kỳ hàng nào trong đợt dịch sau đó có ngày bắt đầu bị thiếu!

NGUY HIỂM: Việc lọc với điều kiện lớn hơn (>) hoặc nhỏ hơn (<) một ngày hoặc số có thể loại bỏ bất kỳ hàng nào có giá trị bị thiếu (NA)! Điều này là do NA được coi là giá trị lớn hoặc nhỏ vô hạn.

(Xem chương Làm việc với ngày tháng để biết thêm thông tin về cách xử lý với ngày tháng và package lubridate)

Thiết kế bộ lọc

Lập một bảng chéo để đảm bảo rằng chúng ta chỉ loại bỏ các hàng cần loại bỏ:

table(Hospital  = linelist$hospital,                     # hospital name
      YearOnset = lubridate::year(linelist$date_onset),  # year of date_onset
      useNA     = "always")                              # show missing values
##                                       YearOnset
## Hospital                               2012 2013 2014 2015 <NA>
##   Central Hospital                        0    0  351   99   18
##   Hospital A                            229   46    0    0   15
##   Hospital B                            227   47    0    0   15
##   Military Hospital                       0    0  676  200   34
##   Missing                                 0    0 1117  318   77
##   Other                                   0    0  684  177   46
##   Port Hospital                           9    1 1372  347   75
##   St. Mark's Maternity Hospital (SMMH)    0    0  322   93   13
##   <NA>                                    0    0    0    0    0

Liệu chúng ta có thể lọc theo tiêu chí nào khác để loại bỏ đợt bùng phát đầu tiên (trong năm 2012 & 2013) khỏi bộ dữ liệu? Chúng ta thấy rằng:

  • Vụ dịch đầu tiên năm 2012 & 2013 xảy ra tại Hospital A, Hospital B, tại Port Hospital cũng có 10 trường hợp mắc.
  • Hospital A & B không có ca bệnh nào trong đợt dịch thứ hai, nhưng Port Hospital thì có.

Chúng ta muốn loại bỏ:

Chúng ta bắt đầu với linelist bằng câu lệnh nrow(linelist). Đây là toàn bộ câu lệnh lọc của chúng ta:

linelist <- linelist %>% 
  # keep rows where onset is after 1 June 2013 OR where onset is missing and it was a hospital OTHER than Hospital A or B
  filter(date_onset > as.Date("2013-06-01") | (is.na(date_onset) & !hospital %in% c("Hospital A", "Hospital B")))

nrow(linelist)
## [1] 6019

Khi chúng ta lập lại bảng chéo, chúng ta thấy rằng Hospital A & B bị loại bỏ hoàn toàn, và 10 trường hợp của Port Hospital từ năm 2012 & 2013 bị loại bỏ và tất cả các giá trị khác đều tương tự - đúng như ý chúng ta muốn.

table(Hospital  = linelist$hospital,                     # hospital name
      YearOnset = lubridate::year(linelist$date_onset),  # year of date_onset
      useNA     = "always")                              # show missing values
##                                       YearOnset
## Hospital                               2014 2015 <NA>
##   Central Hospital                      351   99   18
##   Military Hospital                     676  200   34
##   Missing                              1117  318   77
##   Other                                 684  177   46
##   Port Hospital                        1372  347   75
##   St. Mark's Maternity Hospital (SMMH)  322   93   13
##   <NA>                                    0    0    0

Nhiều biểu thức có thể được bao hàm trong một lệnh lọc (được phân tách bằng dấu phẩy) hoặc bạn luôn có thể viết chúng riêng biệt và nối tiếp nhau trong chuỗi các bước làm sạch để rõ ràng hơn.

Ghi chú: một số độc giả có thể nhận thấy rằng sẽ dễ dàng hơn nếu chỉ lọc theo date_hospitalisation vì nó hoàn chỉnh 100% mà không có giá trị nào bị thiếu. Đây là sự thật. Nhưng date_onset được sử dụng cho mục đích thể hiện sự phức tạp của việc lọc.

Độc lập

Việc lọc cũng có thể được thực hiện như một lệnh độc lập (không phải là một phần của chuỗi các bước làm sạch). Giống như các hàm dplyr khác, trong trường hợp này, đối số đầu tiên phải chính là bộ dữ liệu.

# dataframe <- filter(dataframe, condition(s) for rows to keep)

linelist <- filter(linelist, !is.na(case_id))

Bạn cũng có thể sử dụng base R để chia nhỏ dữ liệu bằng cách sử dụng dấu ngoặc đểm tham chiếu tới [hàng, cột] mà bạn muốn giữ lại.

# dataframe <- dataframe[row conditions, column conditions] (blank means keep all)

linelist <- linelist[!is.na(case_id), ]

Xem nhanh các bản ghi

Thường thì bạn muốn xem nhanh một vài bản ghi, chỉ cho một vài cột. Hàm View() trong base R sẽ hiển thị một khung dữ liệu để xem trong RStudio.

Xem linelist trong RStudio:

View(linelist)

Dưới đây là hai ví dụ về việc xem các ô cụ thể (hàng cụ thể và cột cụ thể):

Với hàm filter()select() trong dplyr:

Với hàm View(), chuyển tiếp bộ dữ liệu đến hàm filter() để giữ các hàng nhất định và sau đó select() để giữ các cột nhất định. Ví dụ, để xem xét ngày khởi phát và nhập viện của 3 trường hợp cụ thể:

View(linelist %>%
       filter(case_id %in% c("11f8ea", "76b97a", "47a5f5")) %>%
       select(date_onset, date_hospitalisation))

Bạn có thể đạt được điều tương tự với cú pháp trong base R, sử dụng dấu ngoặc [ ] cho tập hợp con bạn muốn xem.

View(linelist[linelist$case_id %in% c("11f8ea", "76b97a", "47a5f5"), c("date_onset", "date_hospitalisation")])

Thêm vào chuỗi làm sạch

# CLEANING 'PIPE' CHAIN (starts with raw data and pipes it through cleaning steps)
##################################################################################

# begin cleaning pipe chain
###########################
linelist <- linelist_raw %>%
    
    # standardize column name syntax
    janitor::clean_names() %>% 
    
    # manually re-name columns
           # NEW name             # OLD name
    rename(date_infection       = infection_date,
           date_hospitalisation = hosp_date,
           date_outcome         = date_of_outcome) %>% 
    
    # remove column
    select(-c(row_num, merged_header, x28)) %>% 
  
    # de-duplicate
    distinct() %>% 

    # add column
    mutate(bmi = wt_kg / (ht_cm/100)^2) %>%     

    # convert class of columns
    mutate(across(contains("date"), as.Date), 
           generation = as.numeric(generation),
           age        = as.numeric(age)) %>% 
    
    # add column: delay to hospitalisation
    mutate(days_onset_hosp = as.numeric(date_hospitalisation - date_onset)) %>% 
    
    # clean values of hospital column
    mutate(hospital = recode(hospital,
                      # OLD = NEW
                      "Mitylira Hopital"  = "Military Hospital",
                      "Mitylira Hospital" = "Military Hospital",
                      "Military Hopital"  = "Military Hospital",
                      "Port Hopital"      = "Port Hospital",
                      "Central Hopital"   = "Central Hospital",
                      "other"             = "Other",
                      "St. Marks Maternity Hopital (SMMH)" = "St. Mark's Maternity Hospital (SMMH)"
                      )) %>% 
    
    mutate(hospital = replace_na(hospital, "Missing")) %>% 

    # create age_years column (from age and age_unit)
    mutate(age_years = case_when(
          age_unit == "years" ~ age,
          age_unit == "months" ~ age/12,
          is.na(age_unit) ~ age,
          TRUE ~ NA_real_)) %>% 
  
    mutate(
          # age categories: custom
          age_cat = epikit::age_categories(age_years, breakers = c(0, 5, 10, 15, 20, 30, 50, 70)),
        
          # age categories: 0 to 85 by 5s
          age_cat5 = epikit::age_categories(age_years, breakers = seq(0, 85, 5))) %>% 
    
    # ABOVE ARE UPSTREAM CLEANING STEPS ALREADY DISCUSSED
    ###################################################
    filter(
          # keep only rows where case_id is not missing
          !is.na(case_id),  
          
          # also filter to keep only the second outbreak
          date_onset > as.Date("2013-06-01") | (is.na(date_onset) & !hospital %in% c("Hospital A", "Hospital B")))

8.12 Tính toán theo hàng

Nếu bạn muốn thực hiện một phép tính trong một hàng, bạn có thể sử dụng hàm rowwise() từ dplyr. Xem chi tiết tại row-wise calculations.
Ví dụ: code này sử dụng hàm rowwise() và sau đó tạo một cột mới là tổng số cột triệu chứng được chứa giá trị “có”, cho mỗi hàng trong linelist. Các cột được cụ thể trong hàm sum() theo tên trong vectơ c(). Hàm rowwise() về cơ bản là một hình thức group_by() đặc biệt, vì vậy tốt nhất bạn nên sử dụng ungroup() khi bạn hoàn tất (trong chương Nhóm dữ liệu).

linelist %>%
  rowwise() %>%
  mutate(num_symptoms = sum(c(fever, chills, cough, aches, vomit) == "yes")) %>% 
  ungroup() %>% 
  select(fever, chills, cough, aches, vomit, num_symptoms) # for display
## # A tibble: 5,888 x 6
##    fever chills cough aches vomit num_symptoms
##    <chr> <chr>  <chr> <chr> <chr>        <int>
##  1 no    no     yes   no    yes              2
##  2 <NA>  <NA>   <NA>  <NA>  <NA>            NA
##  3 <NA>  <NA>   <NA>  <NA>  <NA>            NA
##  4 no    no     no    no    no               0
##  5 no    no     yes   no    yes              2
##  6 no    no     yes   no    yes              2
##  7 <NA>  <NA>   <NA>  <NA>  <NA>            NA
##  8 no    no     yes   no    yes              2
##  9 no    no     yes   no    yes              2
## 10 no    no     yes   no    no               1
## # ... with 5,878 more rows

Khi bạn chỉ định cột để đánh giá, bạn có thể muốn sử dụng các hàm trợ giúp “tidyselect” như được mô tả trong mục select() của chương này. Bạn chỉ cần thực hiện một điều chỉnh (vì bạn đang không sử dụng chúng bên trong một hàm của dplyr như select() hoặc summarise()).

Đặt tiêu chí đặc tả cột trong hàm dplyrc_across(). Điều này là do hàm c_across (tài liệu) được thiết kế để hoạt động cụ thể với hàm rowwise(). Ví dụ, như đoạn code sau:

  • Áp dụng rowwise() nên bước tiếp theo (sum()) được áp dụng trong mỗi hàng (không tính tổng toàn bộ các cột)
  • Tạo cột mới num_NA_dates, được xác định cho mỗi hàng là số cột (có tên chứa “ngày tháng”) mà is.na() trả về giá trị TRUE (nghĩa là chúng bị thiếu dữ liệu)
  • ungroup() để loại bỏ ảnh hưởng của rowwise() cho các bước tiếp theo
linelist %>%
  rowwise() %>%
  mutate(num_NA_dates = sum(is.na(c_across(contains("date"))))) %>% 
  ungroup() %>% 
  select(num_NA_dates, contains("date")) # for display
## # A tibble: 5,888 x 5
##    num_NA_dates date_infection date_onset date_hospitalisation date_outcome
##           <int> <date>         <date>     <date>               <date>      
##  1            1 2014-05-08     2014-05-13 2014-05-15           NA          
##  2            1 NA             2014-05-13 2014-05-14           2014-05-18  
##  3            1 NA             2014-05-16 2014-05-18           2014-05-30  
##  4            1 2014-05-04     2014-05-18 2014-05-20           NA          
##  5            0 2014-05-18     2014-05-21 2014-05-22           2014-05-29  
##  6            0 2014-05-03     2014-05-22 2014-05-23           2014-05-24  
##  7            0 2014-05-22     2014-05-27 2014-05-29           2014-06-01  
##  8            0 2014-05-28     2014-06-02 2014-06-03           2014-06-07  
##  9            1 NA             2014-06-05 2014-06-06           2014-06-18  
## 10            1 NA             2014-06-05 2014-06-07           2014-06-09  
## # ... with 5,878 more rows

Bạn cũng có thể cung cấp các hàm khác, chẳng hạn như max() để lấy ngày mới nhất hoặc gần đây nhất cho mỗi hàng:

linelist %>%
  rowwise() %>%
  mutate(latest_date = max(c_across(contains("date")), na.rm=T)) %>% 
  ungroup() %>% 
  select(latest_date, contains("date"))  # for display
## # A tibble: 5,888 x 5
##    latest_date date_infection date_onset date_hospitalisation date_outcome
##    <date>      <date>         <date>     <date>               <date>      
##  1 2014-05-15  2014-05-08     2014-05-13 2014-05-15           NA          
##  2 2014-05-18  NA             2014-05-13 2014-05-14           2014-05-18  
##  3 2014-05-30  NA             2014-05-16 2014-05-18           2014-05-30  
##  4 2014-05-20  2014-05-04     2014-05-18 2014-05-20           NA          
##  5 2014-05-29  2014-05-18     2014-05-21 2014-05-22           2014-05-29  
##  6 2014-05-24  2014-05-03     2014-05-22 2014-05-23           2014-05-24  
##  7 2014-06-01  2014-05-22     2014-05-27 2014-05-29           2014-06-01  
##  8 2014-06-07  2014-05-28     2014-06-02 2014-06-03           2014-06-07  
##  9 2014-06-18  NA             2014-06-05 2014-06-06           2014-06-18  
## 10 2014-06-09  NA             2014-06-05 2014-06-07           2014-06-09  
## # ... with 5,878 more rows

8.13 Sắp xếp

Sử dụng hàm dplyr arrange() để sắp xếp thứ tự các hàng theo giá trị cột.

Đơn giản hãy liệt kê các cột theo thứ tự mà chúng cần được sắp xếp. Thêm .by_group = TRUE nếu bạn muốn việc sắp xếp xảy ra trước bất kỳ hành động nhóm dữ liệu nào được áp dụng trong dữ liệu (xem chương Nhóm dữ liệu).

Mặc định, cột sẽ được sắp xếp theo thứ tự “tăng dần” (áp dụng cho cột số và cả cột ký tự). Bạn có thể sắp xếp một biến theo thứ tự “giảm dần” bằng cách đặt tên biến trong hàm desc().

Việc sắp xếp dữ liệu với hàm arrange() đặc biệt hữu ích khi Trình bày bảng, sử dụng hàm slice() để lấy các hàng “trên cùng” trên mỗi nhóm hoặc thiết lập thứ tự danh mục theo thứ tự xuất hiện.

Ví dụ: để sắp xếp các hàng trong linelist theo hospital, sau đó theo date_onset thứ tự giảm dần, chúng ta sẽ thực hiện như sau:

linelist %>% 
   arrange(hospital, desc(date_onset))