sta*_*oob 3 sql r data-manipulation duplicates fuzzy-logic
我有一个在 R 中看起来像这样的数据集:
address = c("882 4N Road River NY, NY 12345", "882 - River Road NY, ZIP 12345", "123 Fake Road Boston Drive Boston", "123 Fake - Rd Boston 56789")
name = c("ABC Center Building", "Cent. Bldg ABC", "BD Home 25 New", "Boarding Direct 25")
my_data = data.frame(address, name)
address name
1 882 4N Road River NY, NY 12345 ABC Center Building
2 882 - River Road NY, ZIP 12345 Cent. Bldg ABC
3 123 Fake Road Boston Drive Boston BD Home 25 New
4 123 Fake - Rd Boston 56789 Boarding Direct 25
Run Code Online (Sandbox Code Playgroud)
查看此数据,很明显前两行是相同的,后两行也是相同的。但是,如果您尝试直接删除重复项,标准函数(例如“ distinct()”)将声明此数据集中没有重复项,因为所有行都有一些唯一元素。
我一直在尝试研究 R 中能够根据“模糊条件”删除重复行的不同方法。
根据此处提供的答案(查找接近重复记录的技术),我遇到了这种称为“记录链接”的方法。我在这里遇到了这个特定的教程(https://cran.r-project.org/web/packages/RecordLinkage/vignettes/WeightBased.pdf),它可能能够执行类似的任务,但我不确定这是否旨在解决我正在解决的问题。
有人可以帮我确认这个记录链接教程是否确实与我正在解决的问题相关 - 如果是这样,有人可以告诉我如何使用它吗?
例如,我想根据名称和地址删除重复项 - 并且只剩下两行(即 row1/row2 中的一行和 row3/row4 中的一行 - 选择哪一行并不重要)。
作为另一个例子 - 假设我想尝试这个并且仅基于“地址”列去重复:这也可能吗?
有人可以告诉我这是如何工作的吗?
谢谢你!
注意:我听说过一些关于使用 SQL JOINS 和 FUZZY JOINS 的选项(例如https://cran.r-project.org/web/packages/fuzzyjoin/readme/README.html) - 但我不确定这个选项是否也合适。
对于这样的任务,我喜欢使用分而治之的策略,因为在比较大量字符串或更长的字符串时,您很快就会遇到内存问题。
\nlibrary(tidyverse)\nlibrary(quanteda)\nlibrary(quanteda.textstats)\nlibrary(stringdist)\nRun Code Online (Sandbox Code Playgroud)\n我添加了一个 ID 列,并将姓名和地址合并为全文以进行比较。
\nmy_data2 <- my_data|>\n mutate(ID = factor(row_number()),\n fulltext = paste(name, address))\nRun Code Online (Sandbox Code Playgroud)\n相似性的方法quanteda是在比较两个字符串中哪些标记相同之前将字符串划分为单词/标记。与字符串距离相比,这是非常有效的:
duplicates <- my_data2 |> \n # a bunch of wrangling to create the quanteda dfm object\n corpus(docid_field = "ID",\n text_field = "fulltext") |> \n tokens() |> \n dfm() |> \n # calculate similarity using cosine (other methods are available)\n textstat_simil(method = "cosine") |> \n as_tibble() |>\n # attaching the original documents back to the output \n left_join(my_data2, by = c("document1" = "ID")) |> \n left_join(my_data2, by = c("document2" = "ID"), suffix = c("", "_comparison"))\n\nduplicates |> \n select(cosine, \n address, address_comparison, \n name, name_comparison)\n#> # A tibble: 5 \xc3\x97 5\n#> cosine address address_comparison name name_\xe2\x80\xa6\xc2\xb9\n#> <dbl> <chr> <chr> <chr> <chr> \n#> 1 0.641 882 4N Road River NY, NY 12345 882 - River Road NY, Z\xe2\x80\xa6 ABC \xe2\x80\xa6 Cent. \xe2\x80\xa6\n#> 2 0.0801 882 4N Road River NY, NY 12345 123 Fake Road Boston D\xe2\x80\xa6 ABC \xe2\x80\xa6 BD Hom\xe2\x80\xa6\n#> 3 0.0833 882 - River Road NY, ZIP 12345 123 Fake Road Boston D\xe2\x80\xa6 Cent\xe2\x80\xa6 BD Hom\xe2\x80\xa6\n#> 4 0.0962 882 - River Road NY, ZIP 12345 123 Fake - Rd Boston 5\xe2\x80\xa6 Cent\xe2\x80\xa6 Boardi\xe2\x80\xa6\n#> 5 0.481 123 Fake Road Boston Drive Boston 123 Fake - Rd Boston 5\xe2\x80\xa6 BD H\xe2\x80\xa6 Boardi\xe2\x80\xa6\n#> # \xe2\x80\xa6 with abbreviated variable name \xc2\xb9\xe2\x80\x8bname_comparison\nRun Code Online (Sandbox Code Playgroud)\n可以看到,第一个和第二个以及第三个和第四个条目的相似度相当高,分别为 0.641 和 0.481。在大多数情况下,这种比较已经足以识别重复项。然而,它完全忽略了词序。典型的例子是“狗咬人”和“人咬狗”,表面上相似度达到100%,但含义却完全不同。查看您的数据集以确定标记的顺序是否起作用。如果您认为如此,请继续阅读。
\nstringdist 中实现的字符串相似度是距离的标准化版本。就距离而言,您比较的文本长度不起作用。然而,两个字母不同的 4 个字母的字符串非常不同,而两个 100 个字母的字符串则不然。你的例子看起来这可能不是一个大问题,但总的来说,我更喜欢相似性。
\n然而,字符串相似度和距离的问题在于它们的计算成本非常高。即使是 100 条简短的文字也会很快占用您的全部记忆。因此,您可以做的是过滤上面的结果,并仅计算看起来已经重复的候选者的字符串相似度:
\nduplicates_stringsim <- duplicates |> \n filter(cosine > 0.4) |> \n mutate(stringsim = stringsim(fulltext, fulltext_comparison, method = "lv"))\n\nduplicates_stringsim |> \n select(cosine, stringsim,\n address, address_comparison, \n name, name_comparison)\n#> # A tibble: 2 \xc3\x97 6\n#> cosine stringsim address address_com\xe2\x80\xa6\xc2\xb9 name name_\xe2\x80\xa6\xc2\xb2\n#> <dbl> <dbl> <chr> <chr> <chr> <chr> \n#> 1 0.641 0.48 882 4N Road River NY, NY 12345 882 - River \xe2\x80\xa6 ABC \xe2\x80\xa6 Cent. \xe2\x80\xa6\n#> 2 0.481 0.354 123 Fake Road Boston Drive Boston 123 Fake - R\xe2\x80\xa6 BD H\xe2\x80\xa6 Boardi\xe2\x80\xa6\n#> # \xe2\x80\xa6 with abbreviated variable names \xc2\xb9\xe2\x80\x8baddress_comparison, \xc2\xb2\xe2\x80\x8bname_comparison\nRun Code Online (Sandbox Code Playgroud)\n为了进行比较,我们已经消除的其他三个比较的 stringim 分别是 0.2、0.208 和 0.133。尽管较小,但字符串的相似性证实了第一阶段的结果。
\n现在最后一步是从原始 data.frame 中删除重复项。为此,我使用另一个过滤器,从duplicates_stringsim 对象中提取ID,然后从数据中删除这些重复项。
\ndup_ids <- duplicates_stringsim |> \n filter(stringsim > 0.3) |> \n pull(document2)\n\n\nmy_data2 |> \n filter(!ID %in% dup_ids)\n#> address name ID\n#> 1 882 4N Road River NY, NY 12345 ABC Center Building 1\n#> 2 123 Fake Road Boston Drive Boston BD Home 25 New 3\n#> fulltext\n#> 1 ABC Center Building 882 4N Road River NY, NY 12345\n#> 2 BD Home 25 New 123 Fake Road Boston Drive Boston\nRun Code Online (Sandbox Code Playgroud)\n创建于 2022 年 11 月 16 日,使用reprex v2.0.2
\n请注意,我根据您对示例的要求选择了截止值。您必须针对您的数据集以及可能的所有新项目对这些进行微调。
\n