r - 是否有一种有效的策略来对客户数据进行模糊连接以识别 R 中的单个客户 ID?
问题描述
我希望对我的客户数据执行“模糊重复数据删除”,以便为每个客户派生一个唯一 ID,其中在原始数据中可能为同一客户输入了多个 ID。
我在 R 中有一个数据框,其中包含客户列表。每个客户都有一个 ID、名字、姓氏、电子邮件和电话号码。
许多客户已多次进入不同的购买项目。有时旧记录(相同的 ID)会被重复使用,有时会发出新记录和新 ID。
在我不能依赖所有字段的精确匹配的情况下,删除这些数据的适当策略是什么 - 例如,名字拼写错误或仅给出初始名称,但其他字段可能匹配。
我目前对连接到姓氏的名字进行 dplyr left join,然后使用电话和电子邮件作为验证检查,但这可能会丢失一些记录。放松匹配规则(所有相同的姓氏)会导致数据框过大。
(目前没有代码 - 这更多是对一般编码策略和方法的要求。
是否有任何包可以有效地处理这些匹配?)
解决方案
在开始查找重复项之前,首先获取/收集好的数据很重要。
您提到了名字、姓氏、电子邮件和电话号码。名字很好,因为它们通常不会像电子邮件地址和电话号码那样改变。姓氏可以通过结婚/离婚而改变。因此,最好有其他时间不变的变量,例如“出生日期”或“出生地点”。
即使有良好的数据,在大型客户数据库中匹配名字、姓氏和出生日期也总会遇到挑战。
正如您在评论中指出的那样,100,000 多个客户的字符串距离矩阵需要时间并导致内存问题。
这里的一项工作是对数据进行排序并将其分成几部分。在每个小块上创建一个字符串距离矩阵,获取一些可能的匹配项并将所有内容重新组合在一起。关于如何做到这一点有不同的方法,我将展示它在原则上是如何工作的,也许你可以对此进行扩展。
我下载了 1000 条记录的一些虚假数据。不幸的是,它不包含重复项,但为了显示基本工作流程,它并没有真正的重复项。
该方法采取以下步骤:
- 根据姓氏和名字创建名称字段。
- 按升序排列 (AZ)。
- 将其分解为 50 个客户组(这是我的示例数据,有 1,000 行,实际上运行 500 个组在速度和内存方面应该没有问题)。
- 创建一个嵌套的 tibble 以使用
purrr::map
. - 应用
stringdistmatrix
在管道中工作的自定义函数,dplyr
并提供客户名称之间可能的匹配作为输出。 - 取消嵌套单个结果以获得潜在匹配的完整列表。
分解数据背后的想法是,您不需要所有 100,000 个客户的字符串距离矩阵。大多数名称是如此不同,以至于您甚至不需要计算字符串距离。对名称进行排序并处理小子集就像缩小搜索范围。
当然,这只是分解数据的一种方法。它是不完整的,因为它遗漏了例如姓氏首字母有错字的所有客户。但是,您可以将这种方法复制到其他变量,例如出生日期、名称中的字符数等。理想情况下,您可以进行不同的分解并最终将所有内容拼凑在一起。
我通过 www.mockaroo.com 下载了一些假约会。我试图用 dput 把它放在这里,但它太长了。所以我只是向你展示我的数据的 head(),你可以创建自己的假数据或使用真实的客户数据。
关于我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()
推荐阅读
- python - spotipy spotify python api 获取艺术家的所有版本
- json - Python3 Json位置数据提取
- next.js - Nextjs:在卸载组件时删除元素
- python - Selenium/Safari 的下一页
- c# - 在 Xamarin 中的 TabbedPages 之间进行通信
- r - 根据单独过滤器的条件过滤管道中的值
- ruby-on-rails - 收到“NoMethodError:nil:NilClass 的未定义方法‘上传’”错误
- python - 在 Python 中高效地迭代 3.311031748 E+12 组合
- java - 从java中读取文件后将数据值分组
- r - 为数据框编写函数