Прежде чем приступить к поиску дубликатов, важно сначала получить / собрать хорошие данные.
Вы упомянули имя, фамилию, адрес электронной почты и номер телефона.Имена хороши, так как они обычно не меняются в отличие от адресов электронной почты и телефонных номеров.Фамилии могут меняться в результате брака / развода.Поэтому всегда полезно иметь другие переменные, не зависящие от времени, такие как «дата рождения» или «место рождения».
Даже при наличии хороших данных всегда будет проблема, соответствующая имени, фамилии идата рождения в большой базе данных клиентов.
Как вы указали в своих комментариях, матрица расстояний между строками 100 000 и более клиентов требует времени и вызывает проблемы с памятью.
Обходное решение заключается в сортировке данных.и разбить его на куски.Создайте матрицу расстояний между строками на каждом маленьком кусочке, соберите несколько вероятных совпадений и соберите все вместе.Существуют разные подходы к тому, как это сделать, и я просто покажу, как это работает в принципе, и, возможно, вы можете расширить это.
Я скачал некоторые фальшивые данные из 1000 записей.К сожалению, он не содержит дубликатов, но для отображения основного рабочего процесса он не содержит реальных дубликатов.
Подход состоит из следующих шагов:
- Создание поля имени на основе последнегои имя.
- Расположите его в порядке возрастания (AZ).
- Разбейте его на группы по 50 клиентов (это для моего примера данных с 1000 строк, на самом деле работает группы из 500 следуетне будет проблем с точки зрения скорости и памяти).
- Создайте вложенный тиббл для работы с
purrr::map
. - Примените настроенную функцию
stringdistmatrix
, которая работает в конвейере dplyr
и дает вероятные совпадения имен клиентов в качестве выходных данных. - Снимите единичные результаты, чтобы получить полный список возможных совпадений.
Идея разбивки данных заключается в том, что вы делаетене нужна матрица расстояний между 100 000 клиентов.Большинство имен настолько различны, что вам даже не нужно вычислять расстояние до строки.Сортировка имен и работа с небольшими подмножествами подобны сужению поиска.
Конечно, это всего лишь один из способов разбить данные.Он неполный, поскольку он пропускает, например, всех клиентов с опечаткой в первой букве фамилии.Однако вы можете повторить этот подход для других переменных, таких как дата рождения, количество символов в имени и т. Д. В идеале вы делаете разные разбивки и собираете все вместе в конце.
Я скачал фиктивную дату через www.mockaroo.com.Я пытался поставить его здесь с помощью dput, но это было долго.Поэтому я просто показываю вам заголовок () моих данных, и вы можете создавать свои собственные поддельные данные или использовать реальные данные клиентов.
Одна заметка, касающаяся моей настроенной версии stringdistmatrix
, которую я назвал str_dist_mtx
.При работе с реальными данными вы должны корректировать размер группы (в примере она довольно мала, n = 50).И вы должны отрегулировать расстояние до строки string_dist
, до которого вы хотите рассматривать два разных имени в качестве возможных совпадений.Я взял 6
, чтобы хотя бы получить некоторые результаты, но я не работаю с данными, которые имеют реальные дубликаты.Поэтому в реальном приложении я бы выбрал 1
или 2
, чтобы охватить самые основные опечатки.
# the head() of my data
test_data <- structure(list(first_name = c("Gabriel", "Roscoe", "Will", "Francyne",
"Giorgi", "Dulcinea"), last_name = c("Jeandeau", "Chmiel", "Tuckwell",
"Vaggers", "Fairnie", "Tommis"), date_of_birth = structure(c(9161,
4150, 2557, 9437, -884, -4489), class = "Date")), row.names = c(NA,
-6L), class = c("tbl_df", "tbl", "data.frame"))
Ниже приведен код, который я использовал.
library(dplyr)
library(tidyr)
library(ggplot2)
library(purrr)
library(stringdist)
# customized stringdistmatrix function
str_dist_mtx <- function(df, x, string_dist, n) {
temp_mtx = stringdistmatrix(df[[x]],df[[x]])
temp_tbl = tibble(name1 = rep(df[[x]], each = n),
name2 = rep(df[[x]], times = n),
str_dist = as.vector(temp_mtx)) %>%
filter(str_dist > 0 & str_dist < string_dist)
temp_tbl[!duplicated(data.frame(t(apply(temp_tbl,1,sort)))),]
}
# dplyr pipe doing the job
test_data2 <- test_data %>%
mutate(name = paste0(last_name, first_name)) %>%
arrange(name) %>%
mutate(slice_id = row_number(),
slice_id = cut_width(slice_id, 50, center = 25)) %>%
nest(-slice_id) %>%
mutate(str_mtx = map(data,
~ str_dist_mtx(., "name", string_dist = 6, n = 50))) %>%
select(str_mtx) %>%
unnest()