首页 > 解决方案 > 是否有一种有效的策略来对客户数据进行模糊连接以识别 R 中的单个客户 ID?

问题描述

我希望对我的客户数据执行“模糊重复数据删除”,以便为每个客户派生一个唯一 ID,其中在原始数据中可能为同一客户输入了多个 ID。

我在 R 中有一个数据框,其中包含客户列表。每个客户都有一个 ID、名字、姓氏、电子邮件和电话号码。

许多客户已多次进入不同的购买项目。有时旧记录(相同的 ID)会被重复使用,有时会发出新记录和新 ID。

在我不能依赖所有字段的精确匹配的情况下,删除这些数据的适当策略是什么 - 例如,名字拼写错误或仅给出初始名称,但其他字段可能匹配。

我目前对连接到姓氏的名字进行 dplyr left join,然后使用电话和电子邮件作为验证检查,但这可能会丢失一些记录。放松匹配规则(所有相同的姓氏)会导致数据框过大。

(目前没有代码 - 这更多是对一般编码策略和方法的要求。

是否有任何包可以有效地处理这些匹配?)

标签: rjoindplyr

解决方案


在开始查找重复项之前,首先获取/收集好的数据很重要。

您提到了名字、姓氏、电子邮件和电话号码。名字很好,因为它们通常不会像电子邮件地址和电话号码那样改变。姓氏可以通过结婚/离婚而改变。因此,最好有其他时间不变的变量,例如“出生日期”或“出生地点”。

即使有良好的数据,在大型客户数据库中匹配名字、姓氏和出生日期也总会遇到挑战。

正如您在评论中指出的那样,100,000 多个客户的字符串距离矩阵需要时间并导致内存问题。

这里的一项工作是对数据进行排序并将其分成几部分。在每个小块上创建一个字符串距离矩阵,获取一些可能的匹配项并将所有内容重新组合在一起。关于如何做到这一点有不同的方法,我将展示它在原则上是如何工作的,也许你可以对此进行扩展。

我下载了 1000 条记录的一些虚假数据。不幸的是,它不包含重复项,但为了显示基本工作流程,它并没有真正的重复项。

该方法采取以下步骤:

  1. 根据姓氏和名字创建名称字段。
  2. 按升序排列 (AZ)。
  3. 将其分解为 50 个客户组(这是我的示例数据,有 1,000 行,实际上运行 500 个组在速度和内存方面应该没有问题)。
  4. 创建一个嵌套的 tibble 以使用purrr::map.
  5. 应用stringdistmatrix在管道中工作的自定义函数,dplyr并提供客户名称之间可能的匹配作为输出。
  6. 取消嵌套单个结果以获得潜在匹配的完整列表。

分解数据背后的想法是,您不需要所有 100,000 个客户的字符串距离矩阵。大多数名称是如此不同,以至于您甚至不需要计算字符串距离。对名称进行排序并处理小子集就像缩小搜索范围。

当然,这只是分解数据的一种方法。它是不完整的,因为它遗漏了例如姓氏首字母有错字的所有客户。但是,您可以将这种方法复制到其他变量,例如出生日期、名称中的字符数等。理想情况下,您可以进行不同的分解并最终将所有内容拼凑在一起。

我通过 www.mockaroo.com 下载了一些假约会。我试图用 dput 把它放在这里,但它太长了。所以我只是向你展示我的数据的 head(),你可以创建自己的假数据或使用真实的客户数据。

关于我stringdistmatrix命名的自定义版本的一个注释str_dist_mtx。在处理真实数据时,您应该调整组的大小(在示例中它相当小 n = 50)。并且您应该调整字符串距离string_dist,直到您想将两个不同的名称视为潜在匹配。我6至少得到了一些结果,但我没有使用真正重复的数据。所以在一个真正的应用程序中,我会选择12覆盖最基本的错别字。

# 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() 

推荐阅读