15  Дедупликация

На данной странице рассматриваются следующие приемы дедупликации:

  1. Выявление и удаление дублирующихся строк
  2. “Срез” строк, чтобы сохранить только определенные строки (например, минимум или максимум) из каждой группы строк
  3. “Комбинирование” значений из нескольких строк в одну строку

15.1 Подготовка

Загрузка пакетов

Данный фрагмент кода показывает загрузку пакетов, необходимых для анализа. В данном руководстве мы фокусируемся на использовании p_load() из пакета pacman, которая устанавливает пакет, если необходимо, и загружает его для использования. Вы можете также загрузить установленные пакеты с помощью library() из базового R. См. страницу Основы R для получения дополнительной информации о пакетах R.

pacman::p_load(
  tidyverse,   # функции дедупликации, группирования и среза
  janitor,     # функция обзора дубликатов
  stringr)      # для поисков по последовательности, может быть использован в "комбинированных" значениях

Импорт данных

Для демонстрации мы будем использовать пример набора данных, который создан с помощью кода R ниже.

Данные - записи разговоров по телефону в контексте COVID-19, которые включают в себя беседы с контактами и случаями. Столбцы включают recordID (сгенерированный компьютером), personID (идентификатор человека), name (имя), date (дату) беседы, time (время) беседы, purpose (цель) беседы (интервью как со случаем или как с контактом), а также symptoms_ever (симптомы) (были ли у человека когда-либо симптомы).

Вот код для создания набора данных 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"))

Вот датафрейм

Используйте ячейки фильтра наверху, чтобы рассмотреть беседы с каждым человеком.

Несколько аспектов, на которые нужно обратить внимание, при просмотре данных:

  • Первые две записи являются 100% дубликатами, включая дублирующийся recordID (наверное, ошибка компьютера!)
  • Вторые две строки являются дубликатами по всем столбцам, кроме recordID
  • У нескольких человек было несколько телефонных бесед в разные даты и время, в качестве контактов и/или случаев
  • При каждой беседе человека спрашивали, были ли у него когда-либо симптомы, и некоторая такая информация отсутствует.

Вот краткое резюме по людям и целям бесед с ними с помощью функции tabyl() из 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 Дедупликация

Данный раздел описывает, как проводить обзор и удалять дублирующиеся строки в датафрейме. В ней также рассказывается, как работать с дублирующимися элементами вектора.

Рассмотрение дублирующихся строк

Чтобы быстро рассмотреть строки, имеющие дубликаты, вы можете использовать get_dupes() из пакета janitor. По умолчанию будут рассмотрены все столбцы, когда будут оцениваться дубликаты - полученные в результате выполнения функции строки будут являться 100% дубликатами, принимая во внимание значения во всех столбцах.

В датафрейме obs первые две строки являются 100% дубликатами - у них совпадают значения в каждом столбце (включая столбец recordID, который должен быть уникальным - наверное, возникла ошибка компьютера). Полученный в результате датафрейм автоматически включает новый столбец dupe_count с правой стороны, показывающий количество строк с такой комбинацией дублирующихся значений.

# 100% дубликаты по всем столбцам
obs %>% 
  janitor::get_dupes()

См. оригинальные данные

Однако если мы решим игнорировать recordID, 3я и 4я строки также являются дубликатами друг друга. То есть, в них совпадают значения во всех столбцах, кроме recordID. Вы можете уточнить несколько столбцов, которые нужно игнорировать в функции, используя знак -.

# Дубликаты без учета столбца recordID
obs %>% 
  janitor::get_dupes(-recordID)         # если несколько столбцов, оберните их в c()

Вы можете также в положительном ключе указать те столбцы, которые надо рассмотреть. Ниже нам выданы только те строки, у которых совпадают значения в столбцах name (имя) и purpose (цель). Обратите внимание, у “amrish” теперь количество дубликатов dupe_count равно 3, что отражает три беседы с “контактом”.

*Прокрутите влево, чтобы увидеть остальные строки**

# дубликаты ТОЛЬКО на основе столбца имени и цели
obs %>% 
  janitor::get_dupes(name, purpose)

См. оригинальные данные.

Дополнительную информацию можно получить через ?get_dupes или в этой онлайн справке

Сохранение только уникальных строк

Чтобы сохранить только уникальные строки датафрейма, используйте distinct() из dplyr (как показано на странице Вычистка данных и ключевые функции). Дублирующиеся строки удаляются таким образом, что сохраняется только первая из таких строк. По умолчанию, “первая” означает строка с более высоким номером строки rownumber (порядок строк сверху вниз). Остаются только уникальные строки.

В примере ниже мы выполняем distinct() таким образом, чтобы столбец recordID был исключен из рассмотрения - таким образом, удаляются две дублирующихся строки. Первая строка (строка “adam”) была 100% дубликатом и была удалена. Также строка 3 (строка “amrish”) была дубликатом в каждом столбце, кроме recordID (который не принимается во внимание) и также удаляется. В наборе данных obs n теперь стала nrow(obs)-2, а не nrow(obs) строк).

Прокрутите влево, чтобы увидеть весь датафрейм

# добавляется в цепочку канала (например, вычистки данных)
obs %>% 
  distinct(across(-recordID), # сокращает датафрейм только до уникальных строк (сохраняет первый из дубликатов)
           .keep_all = TRUE) 

# если вне канала, то нужно в качестве первого аргумента включить данные 
# distinct(obs)

ВНИМАНИЕ: Если вы используете distinct() для группированных данных, функция будет применена к каждой группе.

Дедупликация на основе конкретных столбцов

Вы можете также уточнить столбцы, которые станут основанием для дедупликации. Таким образом, дедупликация применяется только к строкам, которые являются дубликатами по конкретным столбцам. Если вы не установите .keep_all = TRUE, все не упомянутые столбцы будут выкинуты.

В примере ниже дедупликация применяется только к тем строкам, которые имеют идентичные значения в столбцах name (имя) и purpose (цель). Таким образом, по “brian” у нас только 2 строки вместо 3 - первая “беседа с контактом” и единственная “беседа со случаем”. Чтобы сохранить последнюю беседу с Брайаном для каждой цели, см. вкладку Срез внутри групп.

Прокрутите влево, чтобы увидеть весь датафрейм

# добавляем к цепочке канала (например, вычистки данных)
obs %>% 
  distinct(name, purpose, .keep_all = TRUE) %>%  # сохраняет строки, уникальные по имени и цели, сохраняет все столбцы
  arrange(name)                                  # упорядочиваем для более удобного просмотра

См. оригинальные данные.

Дедупликация элементов в векторе

Функция duplicated() из базового R оценит вектор (столбец) и выдаст логический вектор той же длины (TRUE/FALSE-ИСТИНА/ЛОЖЬ). Первый раз, когда появится значение, она выдаст FALSE (не дубликат), а в последующие разы это значение будет появляться, она выдаст TRUE (ИСТИНА). Обратите внимание, что NA рассматривается как любое другое значение.

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

Чтобы выдать только дублированные элементы, вы можете использовать квадратные скобки для определения подмножества из оригинального вектора:

x[duplicated(x)]
[1]  1 NA  4  4  1  2

Чтобы выдать только уникальные элементы, используйте unique() из базового R. Чтобы удалить NA из выходных данных, поставьте na.omit() внутрь unique().

unique(x)           # альтернативно, используйте x[!duplicated(x)]
[1]  1  2 NA  4  5
unique(na.omit(x))  # удаляет NA 
[1] 1 2 4 5

Использование базового R

Чтобы выдать дублирующиеся строки

В базовом R вы можете также посмотреть, какие строки являются 100% дубликатами в датафрейме df с помощью команды duplicated(df) (выдает логический вектор строк).

Таким образом, вы можете также использовать базовую команду подмножества [ ] для датафрейма, чтобы увидеть дублирующиеся строки с помощью df[duplicated(df),] (не забудьте запятую, что означает, что вы хотите увидеть все столбцы!).

Чтобы выдать уникальные строки

См. заметки выше. Чтобы увидеть уникальные строки, вы добавляете логическое отрицание ! перед функцией duplicated():
df[!duplicated(df),]

Чтобы выдать строки, которые являются дубликатами только определенных столбцов

Выберите подмножество df, которое находится внутри скобок в duplicated(), чтобы эта функция работала только применительно к некоторым столбцам df.

Чтобы уточнить столбцы, задайте номера или имена столбцов после запятой (помните, все это находится внутри функции duplicated()).

Убедитесь, что вы сохранили запятую , также и за пределами функции duplicated()!

Например, чтобы оценить только столбцы со 2 по 5 на предмет дубликатов: df[!duplicated(df[, 2:5]),]
Чтобы оценить только столбцы name и purpose на предмет дубликатов: df[!duplicated(df[, c("name", "purpose)]),]

15.3 Срез

Чтобы сделать “срез” датафрейма, чтобы применить фильтр к строкам по номеру/позиции строки. Это становится особенно полезно, если у вас по несколько строк на функциональную группу (например, на человека) и вам нужно сохранить только некоторые из них.

В базовом виде, функция slice() принимает числа и выдает строки в этих позициях. Если предоставляются положительные числа, выдаются только эти строки. Если отрицательные числа - эти строки не выдаются. Числа должны быть либо все положительными, либо все отрицательными.

obs %>% slice(4)  # выдает 4ю строку
  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))  # выдает строки 2 и 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))  # выдает строки с 2 по 4

См. оригинальные данные.

Существует несколько вариаций: Для них нужно задавать столбец и количество строк, которые необходимо выдать (в n =).

  • slice_min() и slice_max() сохраняет только строку(и) с минимальными или максимальными значениями по указанному столбцу. Также работает, чтобы выдать “минимум” и “максимум” упорядоченных факторов.
  • slice_head() и slice_tail() - сохраняет только первую или последнюю строку(и).
  • slice_sample() - сохраняет только случайную выборку строк.
obs %>% slice_max(encounter, n = 1)  # выдает строки с самым большим количеством бесед (encounter)
  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

Используйте аргументы n = или prop =, чтобы указать число или долю строк, которые нужно сохранить. Если вы используете эту функцию не в рамках цепочки канала, сначала укажите аргумент данных (data) (например, slice(data, n = 2)). Для получения дополнительной информации, см. ?slice.

Другие аргументы:

.order_by = используется в slice_min() и slice_max(), это столбец, по которому идет упорядочивание до среза.
with_ties = по умолчанию TRUE (ИСТИНА), что означает, что равные значения сохраняются.
.preserve = по умолчанию FALSE (ЛОЖЬ). Если TRUE (ИСТИНА), тогда структура группирования пересчитывается после среза.
weight_by = опциональный, числовой столбец, по которому идет взвешивание (чем больше число, тем больше вероятность попасть в выборку). Также replace = чтобы указать, делается ли выборка с/без замещения.

СОВЕТ: При использовании slice_max() и slice_min(), убедитесь, что вы указали/написали n = (например, n = 2, а не просто 2). В противном случае может возникнуть ошибка Error:is not empty.

ПРИМЕЧАНИЕ: Вы можете столкнуться с функцией top_n(), вместо которой были введены функции slice.

Срез с группами

Функции slice_*() могут быть очень полезны, если будут применены к группированному датафрейму, поскольку операция среза проводится отдельно для каждой группы. Используйте функцию group_by() в сочетании со slice(), чтобы сгруппировать данные и сделать срез из каждой группы.

Это полезно для дедупликации, если у вас по несколько строк на человека, но вы хотите сохранить только одну из них. Сначала вы используете group_by() с ключевыми столбцами, которые относятся к одному и тому же человеку, а затем используйте функцию среза по столбцу, который отличается среди группированных строк.

В примере ниже, чтобы сохранить только самую последнюю беседу с каждым человеком, мы группируем строки по имени name, а затем используем slice_max() с n = 1 по столбцу дата date. Будьте внимательны! Чтобы применить такую функцию, как slice_max() к датам, столбец даты должен быть в классе Дата.

По умолчанию, “равенство” (например, та же дата в этом сценарии) сохраняются, и мы все равно получим по нескольку строк для некоторых людей (например, adam). Чтобы этого избежать, мы устанавливаем аргумент with_ties = FALSE. Мы получим только по одной строке на человека.

ВНИМАНИЕ: При использовании arrange(), уточните .by_group = TRUE, чтобы данные были упорядочены внутри каждой группы.

ВНИМАНИЕ: Если with_ties = FALSE, будет сохраняться первая строка равенства. Это может вводить в заблуждение. Обратите внимание, что у Mariah было две беседы в последний день (6 Jan), и была сохранена первая из них (более ранняя). Нам скорее всего интересна более поздняя беседа в этот день. Чтобы решить проблему равенства дат, см. следующий пример.

obs %>% 
  group_by(name) %>%       # группируем строки по 'name'
  slice_max(date,          # сохраняем строку в группе с максимальным значением даты 
            n = 1,         # сохраняем только один верхний ряд 
            with_ties = F) # если есть равенство (даты), берем первую строку

Например, выше мы можем увидеть, что у участника Amrish была сохранена только строка от 5 января, а у Brian только строка от 7 января. См. оригинальные данные.

Решение проблемы “равенства”

Для решения проблемы “равенства” можно выполнить ряд утверждений для среза. В данном случае, если с человеком было несколько бесед в самую последнюю дату, сохраняется беседа с самым последним временем (lubridate::hm() используется для конвертации текстового времени в сортируемый класс времени).
Обратите внимание, что сейчас сохраняется одна строка для “Mariah” от 6 янвакря, и это беседа 3 в 08:32, а не беседа 2 в 07:25.

# Пример нескольких утверждений в slice, чтобы решить проблему равенства
obs %>%
  group_by(name) %>%
  
  # ПЕРВОЕ - срез по последней дате
  slice_max(date, n = 1, with_ties = TRUE) %>% 
  
  # ВТОРОЕ - если есть равенство, выбрать строку с более поздним временем; равенство запрещено
  slice_max(lubridate::hm(time), n = 1, with_ties = FALSE)

В примере выше можно также сделать срез по номеру беседы encounter, но для примера мы показали срез по дате и времени date и time.

СОВЕТ: Чтобы использовать slice_max() или slice_min() для текстового столбца, преобразуйте его с помощью mutate в класс упорядоченных факторов!

См. оригинальные данные.

Сохранить все, но пометить

Если вы хотите сохранить все записи, но отметить лишь некоторые для анализа, рассмотрите использование двухэтапного подхода, используя уникальный идентификационный номер/номер беседы:

  1. Сократите/срежьте оригинальный датафрейм до двух строк для анализа. Сохраните этот сокращенный датафрейм.
  2. В оригинальном датафрейме отметьте соответствующие строки с помощью case_when(), основываясь на том, присутствует ли их уникальный идентификатор записи (в данном случае recordID) в сокращенном датафрейме.
# 1. Определите датафрейм строк для сохранения для анализа
obs_keep <- obs %>%
  group_by(name) %>%
  slice_max(encounter, n = 1, with_ties = FALSE) # сохраняем только последнюю беседу для каждого человека


# 2. Делаем пометки в оригинальном датафрейме
obs_marked <- obs %>%

  # создаем новый столбец дублирующихся записей dup_record
  mutate(dup_record = case_when(
    
    # если запись есть в датафрейме obs_keep
    recordID %in% obs_keep$recordID ~ "For analysis", 
    
    # все остальные случаи отмечаются как игнорировать "Ignore" для целей анализа
    TRUE                            ~ "Ignore"))

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

См. оригинальные данные.

Расчет полноты строк

Создайте столбец, который содержит метрику полноты строки (не-отсутствия). Это может быть полезным при определении того, какие строки будут более приоритетными по сравнению с другими при дедупликации/срезе.

В данном примере, “ключевые” столбцы, по которым вы будете измерять полноту, сохраняются как вектор имен столбцов.

Затем создается новый столбец key_completeness с помощью mutate(). Новое значение в каждой строке определяется как рассчитанная дробь: количество неотсутствующих значений в этой строке среди ключевых столбцов, разделенное на количество ключевых столбцов.

Это требует функции rowSums() из базового R. Также используется ., что внутри канала относится к датафрейму на тот момент канала (в данном случае используется подмножество с квадратными скобками []).

*Прокрутите вправо, чтобы увидеть больше строк**

# создаем столбец "полноты ключевых переменных"
# это *доля* от столбцов, указанных как ключевые "key_cols", в которых значения не отсутствуют

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

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

См. оригинальные данные.

15.4 Комбинирование значений

В данном разделе описано:

  1. Как “комбинировать” значения из нескольких строк в одну строку с некоторыми вариациями
  2. Как только у вас есть “комбинированные” значения, как переписать/приоритезировать значения в каждой ячейке

Здесь используется пример набора данных из раздела Подготовка.

Комбинирование значений в одну строку

Пример кода ниже использует group_by() и summarise(), чтобы сгруппировать строки по лицам, а затем вставить вместе все уникальные значения в группированных строках. Таким образом, вы получаете одну сводную строку на человека. Несколько замечаний:

  • Ко всем новым столбцам добавляется суффикс (в данном случае “_roll”)
  • Если вы хотите показать только уникальные значения в клетке, тогда оберните na.omit() в unique()
  • na.omit() удаляет значения NA, но если вам это нежелательно, его можно удалить с помощью paste0(.x)
# "Комбинируем" значения в одну строку на группу (на один "personID") 
cases_rolled <- obs %>% 
  
  # создаем группы по имени
  group_by(personID) %>% 
  
  # упорядочиваем строки внутри каждой группы (например, по дате)
  arrange(date, .by_group = TRUE) %>% 
  
  # для каждого столбца вставляем вместе все значения в группированных строках, отделенные ";"
  summarise(
    across(everything(),                           # применяем ко всем столбцам
           ~paste0(na.omit(.x), collapse = "; "))) # определяется функция, которая комбинирует значения, которые не являются NA

Результатом будет одна строка на группу (ID) с записями, упорядоченными по дате и вставленными вместе. Прокрутите влево, чтобы увидеть больше строк

См. оригинальные данные.

Данная вариация показывает только уникальные значения:

# Вариация - показать только уникальные значения 
cases_rolled <- obs %>% 
  group_by(personID) %>% 
  arrange(date, .by_group = TRUE) %>% 
  summarise(
    across(everything(),                                   # применяем ко всем столбцам
           ~paste0(unique(na.omit(.x)), collapse = "; "))) # определяется функция, которая комбинирует уникальные значения, которые не являются NA

Данная вариация добавляет суффикс к каждому столбцу.
В данном случае “_roll” указывает на то, что столбец скомбинирован:

# Вариация - к именам столбцов добавляется суффикс 
cases_rolled <- obs %>% 
  group_by(personID) %>% 
  arrange(date, .by_group = TRUE) %>% 
  summarise(
    across(everything(),                
           list(roll = ~paste0(na.omit(.x), collapse = "; ")))) # _roll добавляется к именам столбцов

Перезапись значений/иерархия

Если вы затем хотите оценить все комбинированные значения и сохранить только конкретное значение (например, “наилучшее” или “максимальное” значение), вы можете использовать mutate() по нужным столбцам, чтобы выполнить case_when(), которая использует str_detect() из пакета stringr для последовательного поиска последовательностей символов, и перезаписать содержимое ячейки.

# ЧИСТЫЕ СЛУЧАИ
#############
cases_clean <- cases_rolled %>% 
    
    # чистые переменные Yes-No-Unknown (Да-Нет-Неизвестно): замещает текст "самым высоким" значением, присутствующим в последовательности
    mutate(across(c(contains("symptoms_ever")),                     # работает для указанных столбцов (Y/N/U)
             list(mod = ~case_when(                                 # добавляет суффикс "_mod" к новым столбцам; выполняет case_when()
               
               str_detect(.x, "Yes")       ~ "Yes",                 # если обнаружено "Yes", тогда значение ячейки конвертируется на Yes (да)
               str_detect(.x, "No")        ~ "No",                  # затем, если обнаружено "No", тогда значение ячейки консертируется на No (нет)
               str_detect(.x, "Unknown")   ~ "Unknown",             # затем, если обнаружено "Unknown", тогда значение ячейки конвертируется на Unknown (неизвестно)
               TRUE                        ~ as.character(.x)))),   # затем, в остальных случаях сохраняется как есть
      .keep = "unused")                                             # старые столбцы удаляются, остаются только столбцы _mod

Теперь вы видите в столбце symptoms_ever, что если человек КОГДА-ЛИБО ответил да на симптомы (“Yes”), то отобразится только “Yes”.

См. оригинальные данные.

15.5 Вероятностная дедупликация

Иногда вам может потребоваться выявить “вероятные” дубликаты на основе похожести (например, расстояния последовательности) по нескольким столбцам, таким как имя, возраст, пол, дата рождения и т.п. Вы можете применить алгоритмы поиска вероятностных совпадений, чтобы выявить вероятные дубликаты.

См. страницу [Соединение данных], где объясняется этот метод. В разделе по вероятностным соответствиям есть пример применения этих алгоритмов для сравнения датафрейма с самим собой, таким образом проводя вероятностную дедупликацию.

15.6 Ресурсы

Большая часть информации на этой странице взята из следующих онлайн ресурсов и виньеток:

datanovia

справка по dplyr tidyverse

виньетка cran janitor