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

Chương này đề cập đến các kỹ thuật loại bỏ trùng lặp sau:

  1. Xác định và loại bỏ các hàng trùng lặp
  2. “Cắt” một số hàng để chỉ giữ lại những hàng nhất định (ví dụ: tối thiểu hoặc tối đa) từ mỗi nhóm hàng
  3. “Rolling-up” hoặc kết hợp các giá trị từ nhiều hàng thành một hàng

15.1 Chuẩn bị

Gọi package

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

pacman::p_load(
  tidyverse,   # deduplication, grouping, and slicing functions
  janitor,     # function for reviewing duplicates
  stringr)      # for string searches, can be used in "rolling-up" values

Nhập dữ liệu

Để minh họa, chúng tôi sẽ sử dụng một bộ dữ liệu mẫu được tạo bằng code R bên dưới.

Dữ liệu là các bản ghi về những cuộc gọi truy vết COVID-19, bao gồm những cuộc gọi truy vết với các liên hệ và với các trường hợp. Các cột bao gồm recordID (mã bản ghi) (do máy tính tạo), personID (mã người truy vết), name (tên), date (ngày) truy vết, time (thời gian) truy vết, purpose (mục đích) truy vết (phỏng vấn một trường hợp hoặc một liên hệ) và symptoms_ever (liệu người được truy vết đã từng được báo cáo có triệu chứng hay không).

Đây là code để tạo bộ dữ liệu obs:

obs <- data.frame(
  recordID  = c(1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18),
  personID  = c(1,1,2,2,3,2,4,5,6,7,2,1,3,3,4,5,5,7,8),
  name      = c("adam", "adam", "amrish", "amrish", "mariah", "amrish", "nikhil", "brian", "smita", "raquel", "amrish",
                "adam", "mariah", "mariah", "nikhil", "brian", "brian", "raquel", "natalie"),
  date      = c("1/1/2020", "1/1/2020", "2/1/2020", "2/1/2020", "5/1/2020", "5/1/2020", "5/1/2020", "5/1/2020", "5/1/2020","5/1/2020", "2/1/2020",
                "5/1/2020", "6/1/2020", "6/1/2020", "6/1/2020", "6/1/2020", "7/1/2020", "7/1/2020", "7/1/2020"),
  time      = c("09:00", "09:00", "14:20", "14:20", "12:00", "16:10", "13:01", "15:20", "14:20", "12:30", "10:24",
                "09:40", "07:25", "08:32", "15:36", "15:31", "07:59", "11:13", "17:12"),
  encounter = c(1,1,1,1,1,3,1,1,1,1,2,
                2,2,3,2,2,3,2,1),
  purpose   = c("contact", "contact", "contact", "contact", "case", "case", "contact", "contact", "contact", "contact", "contact",
                "case", "contact", "contact", "contact", "contact", "case", "contact", "case"),
  symptoms_ever = c(NA, NA, "No", "No", "No", "Yes", "Yes", "No", "Yes", NA, "Yes",
                    "No", "No", "No", "Yes", "Yes", "No","No", "No")) %>% 
  mutate(date = as.Date(date, format = "%d/%m/%Y"))

Đây là data frame

Sử dụng bộ lọc của các box ở trên cùng để xem lại các cuộc gọi truy vết theo từng người.

Một số điều cần lưu ý khi bạn xem xét dữ liệu:

  • Hai bản ghi đầu tiên hoàn toàn 100% là trùng lặp, bao gồm cả recordID (hẳn là một lỗi máy tính!)
  • Hai hàng thứ hai trùng lặp trong tất cả các cột ngoại trừ recordID
  • Một vài cá nhân có nhiều cuộc gọi truy vết, vào những ngày, giờ khác nhau, được tính là những liên hệ và/hoặc những trường hợp
  • Ở mỗi cuộc gọi truy vết, người được truy vết được hỏi đã từng mắc các triệu chứng chưa và một số thông tin này bị missing.

Và đây là bản tóm tắt nhanh về những người truy vết và mục đích các cuộc gọi truy vết của họ, sử dụng hàm tabyl() từ janitor:

obs %>% 
  tabyl(name, purpose)
##     name case contact
##     adam    1       2
##   amrish    1       3
##    brian    1       2
##   mariah    1       2
##  natalie    1       0
##   nikhil    0       2
##   raquel    0       2
##    smita    0       1

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

Phần này mô tả cách xem xét và loại bỏ các hàng trùng lặp trong data frame. Đồng thời cũng chỉ ra cách xử lý các phần tử trùng lặp trong một vectơ.

Kiểm tra hàng trùng lặp

Để nhanh chóng xem lại các hàng có trùng lặp, bạn có thể sử dụng get_dupes() từ package janitor. Theo mặc định, tất cả các cột được xem xét khi đánh giá trùng lặp - các hàng được hàm trả về là trùng lặp 100% nếu xét đến các giá trị trong tất cả các cột.

Trong data frame obs, hai hàng đầu tiên trùng lặp 100% - chúng có cùng giá trị trong tất cả các cột (bao gồm cả cột recordID, vốn được cho là duy nhất - hẳn là một số trục trặc máy tính). Data frame được trả về tự động bao gồm một cột mới dupe_count ở phía bên phải, hiển thị số hàng có sự kết hợp của các giá trị trùng lặp đó.

# 100% duplicates across all columns
obs %>% 
  janitor::get_dupes()

Xem dữ liệu gốc

Tuy nhiên, nếu chúng ta chọn bỏ qua recordID, thì hàng thứ 3 và thứ 4 cũng là bản trùng lặp của nhau. Nghĩa là chúng có cùng giá trị trong tất cả các cột ngoại trừ recordID. Bạn có thể xác định các cột cụ thể bị bỏ qua trong hàm bằng ký hiệu - (dấu trừ).

# Duplicates when column recordID is not considered
obs %>% 
  janitor::get_dupes(-recordID)         # if multiple columns, wrap them in c()

Bạn cũng có thể xác định rõ các cột cần xem xét. Dưới đây, chỉ các hàng có cùng giá trị trong cột namepurpose được trả về. Lưu ý rằng “amrish” hiện có dupe_count bằng 3 cho thấy ba cuộc gọi truy vết “liên hệ” của anh ta.

Cuộn sang trái để xem thêm hàng

# duplicates based on name and purpose columns ONLY
obs %>% 
  janitor::get_dupes(name, purpose)

Xem dữ liệu gốc.

Xem ?get_dupes để biết thêm chi tiết hoặc xem tài liệu tham khảo trực tuyến này

Chỉ giữ lại các hàng duy nhất

Để chỉ giữ lại các hàng duy nhất của một data frame, hãy sử dụng distinct() từ dplyr (đã được giải thích trong chương Làm sạch số liệu và các hàm quan trọng). Các hàng trùng lặp sẽ bị loại bỏ sao cho chỉ giữ lại hàng đầu tiên trong số các hàng đó. Theo mặc định, hàng “đầu tiên” có nghĩa là hàng có rownumber cao nhất (thứ tự của các hàng từ trên-xuống-dưới). Chỉ các hàng duy nhất được giữ lại.

Trong ví dụ dưới đây, chúng tôi chạy lệnh distinct() sao cho cột recordID bị loại trừ khỏi việc xem xét - do đó hai hàng trùng lặp sẽ bị loại bỏ. Hàng đầu tiên (đối với “adam”) bị trùng lặp 100% và đã bị loại bỏ. Bên cạnh đó, hàng 3 (cho “amrish”) là một bản bị trùng lặp trong tất cả các cột ngoại trừ recordID (đang không được xét) và do đó cũng bị loại bỏ. Bộ dữ liệu obs bây giờ có số dòng là nrow(obs)-2, không phải là nrow(obs)).

Cuộn sang trái để xem toàn bộ data frame

# added to a chain of pipes (e.g. data cleaning)
obs %>% 
  distinct(across(-recordID), # reduces data frame to only unique rows (keeps first one of any duplicates)
           .keep_all = TRUE) 

# if outside pipes, include the data as first argument 
# distinct(obs)

CHÚ Ý: Nếu sử dụng distinct() trên nhóm dữ liệu, hàm sẽ áp dụng cho từng nhóm.

Loại bỏ trùng lặp dựa trên cột cụ thể

Bạn cũng có thể xác định các cột cơ sở để loại bỏ trùng lặp. Theo cách này, loại bỏ trùng lặp chỉ áp dụng cho các hàng trùng lặp trong các cột được chỉ định. Trừ khi bạn đặt .keep_all = TRUE, tất cả các cột không được đề cập sẽ bị loại bỏ.

Trong ví dụ dưới đây, loại bỏ trùng lặp chỉ áp dụng cho các hàng có giá trị giống nhau trong các cột namepurpose. Do đó, “brian” chỉ có 2 hàng thay vì 3 - cuộc gọi truy vết “liên hệ” đầu tiên và cuộc gọi truy vết “trường hợp” duy nhất của anh ta. Để điều chỉnh sao cho cuộc gọi truy vết mới nhất của brian đối với từng mục đích được lưu giữ, hãy xem mục Cắt theo nhóm.

Cuộn sang trái để xem toàn bộ data frame

# added to a chain of pipes (e.g. data cleaning)
obs %>% 
  distinct(name, purpose, .keep_all = TRUE) %>%  # keep rows unique by name and purpose, retain all columns
  arrange(name)                                  # arrange for easier viewing

Xem dữ liệu gốc.

Loại bỏ phần tử trùng lặp trong một vectơ

Hàm duplicated() từ base R sẽ đánh giá một vectơ (cột) và trả về một vectơ logic có cùng độ dài (TRUE/FALSE). Lần đầu tiên một giá trị xuất hiện, nó sẽ trả về giá trị FALSE (không phải là một bản trùng lặp) và những lần tiếp theo giá trị đó xuất hiện, nó sẽ trả về giá trị TRUE. Lưu ý giá trị NA được xử lý giống như bất kỳ giá trị nào khác.

x <- c(1, 1, 2, NA, NA, 4, 5, 4, 4, 1, 2)
duplicated(x)
##  [1] FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE  TRUE  TRUE  TRUE

Để chỉ trả về các phần tử bị trùng lặp, bạn có thể sử dụng dấu ngoặc để đặt vectơ gốc:

## [1]  1 NA  4  4  1  2

Để chỉ trả về các phần tử duy nhất, hãy sử dụng hàm unique() từ base R. Để loại bỏ các NA khỏi kết quả đầu ra, hãy lồng na.omit() trong unique().

unique(x)           # alternatively, use x[!duplicated(x)]
## [1]  1  2 NA  4  5
unique(na.omit(x))  # remove NAs 
## [1] 1 2 4 5

Sử dụng base R

Để trả về các hàng trùng lặp

Trong base R, bạn cũng có thể xem những hàng nào là trùng lặp 100% trong data frame df bằng lệnh duplicated(df) (trả về một vectơ logic của các hàng).

Do đó, bạn cũng có thể sử dụng tập con cơ sở [ ] trên data frame để xem các hàng trùng lặp với df[duplicated(df),] (đừng quên dấu phẩy, điều đó có nghĩa là bạn muốn xem tất cả các cột!).

Để trả về các hàng duy nhất

Xem các ghi chú ở trên. Để xem các hàng duy nhất, bạn thêm dấu phủ định logic ! ở trước hàm duplicated():
df[!duplicated(df),]

Để trả về các hàng trùng lặp chỉ với một số cột nhất định

Đặt df nằm trong dấu ngoặc đơn hàm duplicated(), hàm này sẽ chỉ hoạt động trên một số cột nhất định của df.

Để xác định các cột, hãy cung cấp số thứ tự hoặc tên cột sau dấu phẩy (nhớ rằng đặt tất cả những thông tin này trong hàm duplicated()).

Hãy chắc chắn đặt dấu phẩy , bên ngoài và sau hàm duplicated()!

Ví dụ: Để chỉ đánh giá các cột từ 2 đến 5 cho các bản trùng lặp: df[!duplicated(df[, 2:5]),]
Để chỉ đánh giá các cột namepurpose cho các bản trùng lặp: df[!duplicated(df[, c("name", "purpose)]),]

15.3 Cắt dòng

“Cắt” một data frame nhằm áp dụng bộ lọc trên các hàng theo số/vị trí hàng. Điều này trở nên đặc biệt hữu ích nếu bạn có nhiều hàng cho mỗi nhóm chức năng (ví dụ: mỗi “người”) và bạn chỉ muốn giữ một hoặc một số hàng trong số đó.

Hàm slice() cơ bản chấp nhận các số và trả về các hàng ở những vị trí đó. Nếu các số được cung cấp là số dương, chỉ có chúng được trả về. Nếu là số âm, những hàng đó không được trả về. Tất cả các số đều phải là số dương hoặc đều là số âm.

obs %>% slice(4)  # return the 4th row
##   recordID personID   name       date  time encounter purpose symptoms_ever
## 1        3        2 amrish 2020-01-02 14:20         1 contact            No
obs %>% slice(c(2,4))  # return rows 2 and 4
##   recordID personID   name       date  time encounter purpose symptoms_ever
## 1        1        1   adam 2020-01-01 09:00         1 contact          <NA>
## 2        3        2 amrish 2020-01-02 14:20         1 contact            No
#obs %>% slice(c(2:4))  # return rows 2 through 4

Xem dữ liệu gốc.

Có một số biến thể: Chúng phải được cung cấp với một cột và một số hàng để trả về (tới n =).

  • slice_min()slice_max() chỉ giữ (các) hàng có (các) giá trị nhỏ nhất hoặc lớn nhất của cột được chỉ định. Hàm này cũng hoạt động để trả về “tối thiểu” và “tối đa” của các biến factor có thứ tự.
  • slice_head()slice_tail() - chỉ giữ (các) hàng đầu tiên hoặc cuối cùng.
  • slice_sample() - chỉ giữ một mẫu ngẫu nhiên của các hàng.
obs %>% slice_max(encounter, n = 1)  # return rows with the largest encounter number
##   recordID personID   name       date  time encounter purpose symptoms_ever
## 1        5        2 amrish 2020-01-05 16:10         3    case           Yes
## 2       13        3 mariah 2020-01-06 08:32         3 contact            No
## 3       16        5  brian 2020-01-07 07:59         3    case            No

Sử dụng đối số n = hoặc prop = để xác định số lượng hoặc tỷ lệ hàng cần giữ. Nếu không sử dụng hàm trong chuỗi pipe, trước tiên hãy cung cấp đối số dữ liệu (ví dụ: slice(data, n = 2)). Xem ?slice để biết thêm thông tin.

Các đối số khác:

.order_by = được sử dụng trong slice_min()slice_max(), sắp xếp thứ tự theo một cột trước khi cắt.
with_ties = TRUE theo mặc định, nghĩa là các ràng buộc bị giữ lại.
.preserve = FALSE theo mặc định. Nếu TRUE thì cấu trúc nhóm được tính toán lại sau khi cắt.
weight_by = Tùy chọn, cột dữ liệu kiểu số được tính theo trọng số (số lớn hơn có nhiều khả năng được lấy mẫu hơn). Đồng thời replace = cho việc lấy mẫu liệu có được thực hiện mà có/không có sự thay thế.

MẸO: Khi sử dụng slice_max()slice_min(), hãy chắc chắn bạn cụ thể/ghi n = (ví dụ: n = 2, không chỉ 2). Nếu không, bạn có thể gặp lỗi Error:is not empty.

LƯU Ý: Bạn có thể gặp hàm top_n(), hàm mà đã bị thay thế bởi các hàm slice.

Cắt theo nhóm

Các hàm slice_*() có thể rất hữu ích nếu được áp dụng cho một data frame được nhóm lại vì thao tác cắt được thực hiện trên từng nhóm riêng biệt. Sử dụng hàm group_by() kết hợp với slice() nhằm nhóm dữ liệu để lấy một lát cắt từ mỗi nhóm.

Điều này rất hữu ích cho việc loại bỏ trùng lặp nếu bạn có nhiều hàng cho mỗi người nhưng chỉ muốn giữ một trong số chúng. Trước tiên, bạn sử dụng group_by() với các cột chính mà giống nhau cho mỗi người, sau đó sử dụng một hàm slice trên một cột, điều này sẽ khác nhau giữa các hàng được nhóm.

Trong ví dụ dưới đây, để chỉ giữ lại cuộc gọi truy vết mới nhất theo mỗi người, chúng ta nhóm các hàng theo cột name và sau đó sử dụng slice_max() với n = 1 trên cột date. Hãy lưu ý! Để áp dụng một hàm như slice_max() vào dates (ngày tháng), cột ngày tháng phải thuộc nhóm Date.

Theo mặc định, “các ràng buộc” (ví dụ: cùng ngày trong trường hợp này) được giữ lại và chúng tôi sẽ vẫn nhận được nhiều hàng cho một số người (ví dụ: adam). Để tránh tình huống này, chúng tôi đặt with_ties = FALSE. Chúng tôi sẽ chỉ nhận lại một hàng cho mỗi người.

CHÚ Ý: Nếu sử dụng arrange(), xác định .by_group = TRUE để sắp xếp dữ liệu trong mỗi nhóm.

THẬN TRỌNG: Nếu with_ties = FALSE, hàng đầu tiên có ràng buộc được giữ lại. Điều này có thể là sự nhầm lẫn. Hãy xem trường hợp của Mariah, cô ấy có hai cuộc gọi truy vết vào ngày gần nhất (ngày 06/01) và cuộc gọi truy vết đầu tiên (sớm nhất) được giữ lại. Rất có thể, chúng ta muốn giữ lại cuộc gọi truy vết muộn hơn của cô ấy vào ngày đó. Xem cách “phá vỡ” những ràng buộc này trong ví dụ tiếp theo.

obs %>% 
  group_by(name) %>%       # group the rows by 'name'
  slice_max(date,          # keep row per group with maximum date value 
            n = 1,         # keep only the single highest row 
            with_ties = F) # if there's a tie (of date), take the first row

Ví dụ ở trên, chúng ta có thể thấy rằng chỉ hàng của Amrish vào ngày 05/01 được giữ lại và chỉ hàng của Brian vào ngày 07/01 được giữ lại. Xem dữ lịệu gốc.

Phá vỡ “ràng buộc”

Nhiều câu lệnh cắt (slice) có thể được chạy để “phá vỡ các ràng buộc”. Trong trường hợp này, nếu một người có nhiều cuộc gọi truy vết vào ngày gần nhất, cuộc gọi truy vết với thời gian gần nhất sẽ được giữ lại (lubridate::hm() được sử dụng để chuyển đổi ký tự thời gian thành một lớp thời gian có thể sắp xếp).
Lưu ý rằng bây giờ, một hàng được giữ cho “Mariah” vào ngày 06/01 là cuộc gọi truy vết thứ 3 từ 08:32, không phải cuộc gọi truy vết thứ 2 lúc 07:25.

# Example of multiple slice statements to "break ties"
obs %>%
  group_by(name) %>%
  
  # FIRST - slice by latest date
  slice_max(date, n = 1, with_ties = TRUE) %>% 
  
  # SECOND - if there is a tie, select row with latest time; ties prohibited
  slice_max(lubridate::hm(time), n = 1, with_ties = FALSE)

Trong ví dụ trên, cũng có thể cắt theo số lần encounter, nhưng chúng tôi đã hiển thị cách cắt theo datetime cho mục đích ví dụ.

MẸO: Để sử dụng slice_max() hoặc slice_min() trên một cột “kí tự”, hãy biến đối nó thành một lớp yếu tố được sắp xếp theo thứ tự!

Xem dữ liệu gốc.

Giữ lại tất cả nhưng đánh dấu các dòng

Nếu bạn muốn giữ lại tất cả các bản ghi nhưng chỉ đánh dấu một số để phân tích, hãy cân nhắc cách tiếp cận hai bước sử dụng một số recordID/encounter duy nhất:

  1. Giảm/cắt data frame gốc thành chỉ bao gồm các hàng cho phân tích. Lưu/giữ lại data frame đã được thu gọn này.
  2. Trong data frame gốc, hãy đánh dấu các hàng là thích hợp với case_when(), dựa trên việc liệu mã định danh duy nhất của những bản ghi này (recordID trong ví dụ này) có trong data frame thu gọn hay không.
# 1. Define data frame of rows to keep for analysis
obs_keep <- obs %>%
  group_by(name) %>%
  slice_max(encounter, n = 1, with_ties = FALSE) # keep only latest encounter per person


# 2. Mark original data frame
obs_marked <- obs %>%

  # make new dup_record column
  mutate(dup_record = case_when(
    
    # if record is in obs_keep data frame
    recordID %in% obs_keep$recordID ~ "For analysis", 
    
    # all else marked as "Ignore" for analysis purposes
    TRUE                            ~ "Ignore"))

# print
obs_marked
##    recordID personID    name       date  time encounter purpose symptoms_ever   dup_record
## 1         1        1    adam 2020-01-01 09:00         1 contact          <NA>       Ignore
## 2         1        1    adam 2020-01-01 09:00         1 contact          <NA>       Ignore
## 3         2        2  amrish 2020-01-02 14:20         1 contact            No       Ignore
## 4         3        2  amrish 2020-01-02 14:20         1 contact            No       Ignore
## 5         4        3  mariah 2020-01-05 12:00         1    case            No       Ignore
## 6         5        2  amrish 2020-01-05 16:10         3    case           Yes For analysis
## 7         6        4  nikhil 2020-01-05 13:01         1 contact           Yes       Ignore
## 8         7        5   brian 2020-01-05 15:20         1 contact            No       Ignore
## 9         8        6   smita 2020-01-05 14:20         1 contact           Yes For analysis
## 10        9        7  raquel 2020-01-05 12:30         1 contact          <NA>       Ignore
## 11       10        2  amrish 2020-01-02 10:24         2 contact           Yes       Ignore
## 12       11        1    adam 2020-01-05 09:40         2    case            No For analysis
## 13       12        3  mariah 2020-01-06 07:25         2 contact            No       Ignore
## 14       13        3  mariah 2020-01-06 08:32         3 contact            No For analysis
## 15       14        4  nikhil 2020-01-06 15:36         2 contact           Yes For analysis
## 16       15        5   brian 2020-01-06 15:31         2 contact           Yes       Ignore
## 17       16        5   brian 2020-01-07 07:59         3    case            No For analysis
## 18       17        7  raquel 2020-01-07 11:13         2 contact            No For analysis
## 19       18        8 natalie 2020-01-07 17:12         1    case            No For analysis

Xem dữ liệu gốc.

Tính toán độ hoàn chỉnh của hàng

Tạo một cột chứa số liệu về độ hoàn chỉnh (không bị thiếu) của hàng. Điều này có thể hữu ích khi quyết định ưu tiên hàng nào hơn hàng nào khi loại bỏ trùng lặp/cắt.

Trong ví dụ này, các cột “chính” mà bạn muốn đo lường mức độ hoàn chỉnh được lưu trong một vectơ tên cột.

Sau đó, cột mới key_completeness được tạo bằng hàm mutate(). Giá trị mới của mỗi hàng được xác định dưới dạng phân số được tính toán bằng: số giá trị không bị thiếu trong hàng đó trong số các cột chính, chia cho số cột chính.

Điều này cần thêm hàm rowSums() từ base R. Data frame sử dụng được đại diện bởi dấu ., tức là tham chiếu đến data frame nguồn trong một chuỗi pipe (trong trường hợp này, nó sẽ được tách thành tập con với dấu ngoặc vuông []).

Cuộn sang phải để xem các hàng khác

# create a "key variable completeness" column
# this is a *proportion* of the columns designated as "key_cols" that have non-missing values

key_cols = c("personID", "name", "symptoms_ever")

obs %>% 
  mutate(key_completeness = rowSums(!is.na(.[,key_cols]))/length(key_cols)) 

Xem dữ liệu gốc.

15.4 Gộp các giá trị

Phần này miêu tả:

  1. Cách “gộp (roll-up)” các giá trị từ nhiều hàng chỉ thành một hàng, với một số biến thể
  2. Khi bạn có các giá trị “đã được gộp”, cách để ghi đè/ưu tiên các giá trị trong mỗi ô

Tab này sử dụng bộ dữ liệu mẫu từ tab Chuẩn bị.

Gộp các giá trị thành một hàng

Code ví dụ bên dưới sử dụng group_by()summarise() để nhóm các hàng theo từng người, rồi dán (paste) tất cả các giá trị duy nhất trong các hàng được nhóm lại với nhau. Do đó, bạn nhận được một hàng tóm tắt cho mỗi người. Một số lưu ý:

  • Một hậu tố được thêm vào tất cả các cột mới (“_roll” trong ví dụ này)
  • Nếu bạn chỉ muốn hiển thị các giá trị duy nhất trên mỗi ô, thì hãy đặt na.omit() trong unique()
  • na.omit() loại bỏ các giá trị NA, nhưng nếu đây là điều không mong muốn, nó có thể bị loại bỏ bởi paste0(.x)
# "Roll-up" values into one row per group (per "personID") 
cases_rolled <- obs %>% 
  
  # create groups by name
  group_by(personID) %>% 
  
  # order the rows within each group (e.g. by date)
  arrange(date, .by_group = TRUE) %>% 
  
  # For each column, paste together all values within the grouped rows, separated by ";"
  summarise(
    across(everything(),                           # apply to all columns
           ~paste0(na.omit(.x), collapse = "; "))) # function is defined which combines non-NA values

Kết quả là một hàng cho mỗi nhóm (ID), với các mục đã nhập được sắp xếp theo ngày và được dán cùng nhau. Cuộn sang trái để xem các hàng khác

Xem dữ liệu gốc.

Biến thể này chỉ hiển thị các giá trị duy nhất:

# Variation - show unique values only 
cases_rolled <- obs %>% 
  group_by(personID) %>% 
  arrange(date, .by_group = TRUE) %>% 
  summarise(
    across(everything(),                                   # apply to all columns
           ~paste0(unique(na.omit(.x)), collapse = "; "))) # function is defined which combines unique non-NA values

Biến thể này thêm hậu tố vào mỗi cột.
Trong trường hợp này, “_roll” được dùng để biểu thị rằng nó đã được gộp:

# Variation - suffix added to column names 
cases_rolled <- obs %>% 
  group_by(personID) %>% 
  arrange(date, .by_group = TRUE) %>% 
  summarise(
    across(everything(),                
           list(roll = ~paste0(na.omit(.x), collapse = "; ")))) # _roll is appended to column names

Ghi đè các giá trị/hệ thống phân cấp

Sau đó, nếu bạn muốn đánh giá tất cả các giá trị đã gộp và chỉ giữ một giá trị cụ thể (ví dụ: giá trị “tốt nhất (best)” hoặc “tối đa (maximum)”), bạn có thể sử dụng hàm mutate() trên các cột mong muốn, để triển khai các điều kiện rẽ nhánh với case_when()str_detect() từ package stringr để tìm kiếm tuần tự các mẫu chuỗi và ghi đè nội dung ô.

# CLEAN CASES
#############
cases_clean <- cases_rolled %>% 
    
    # clean Yes-No-Unknown vars: replace text with "highest" value present in the string
    mutate(across(c(contains("symptoms_ever")),                     # operates on specified columns (Y/N/U)
             list(mod = ~case_when(                                 # adds suffix "_mod" to new cols; implements case_when()
               
               str_detect(.x, "Yes")       ~ "Yes",                 # if "Yes" is detected, then cell value converts to yes
               str_detect(.x, "No")        ~ "No",                  # then, if "No" is detected, then cell value converts to no
               str_detect(.x, "Unknown")   ~ "Unknown",             # then, if "Unknown" is detected, then cell value converts to Unknown
               TRUE                        ~ as.character(.x)))),   # then, if anything else if it kept as is
      .keep = "unused")                                             # old columns removed, leaving only _mod columns

Bây giờ bạn có thể thấy trong cột symptoms_ever rằng nếu người đó TỪNG trả lời “Yes” với các triệu chứng, thì chỉ “Yes” được hiển thị.

Xem dữ liệu gốc.

15.5 Loại bỏ trùng lặp theo xác suất

Đôi khi, bạn có thể muốn xác định các bản trùng lặp “có khả năng xảy ra” dựa trên sự tương đồng (ví dụ: chuỗi “distance (khoảng cách)”) trên một số cột như name (tên), age (tuổi), sex (giới tính), date of birth (ngày sinh), v.v. Bạn có thể áp dụng thuật toán so khớp theo xác suất để xác định các bản trùng lặp có khả năng xảy ra.

Xem chương Nối dữ liệu để được giải thích về phương pháp này. Mục So sánh theo xác suất chứa một ví dụ về việc áp dụng các thuật toán này để so sánh một data frame với chính nó, do đó thực hiện loại bỏ trùng lặp theo xác suất.

15.6 Tài nguyên học liệu

Phần lớn thông tin trong chương này được điều chỉnh từ các tài nguyên và vignette trực tuyến sau:

datanovia

dplyr tidyverse reference

cran janitor vignette