【问题标题】:Group by similar results in R按 R 中的相似结果分组
【发布时间】:2019-12-13 12:58:58
【问题描述】:

我想对相似的结果(不是唯一的)进行分组,但我不知道该怎么做。

我的意思是,我有一个名为“名称”的列的 df,其结果类似:ARPO、ARPO S.L、ARPO、SL 等。

|---------------------|------------------|
|      name           |     address      |
|---------------------|------------------|
|       ARPO          |     street 1     |
|---------------------|------------------|
|       ARPO S.L      |     street 1     |
|---------------------|------------------|
|       ARPO, SL      |     street 1     |
|---------------------|------------------|
|       ARPO SL       |     street 1     |
|---------------------|------------------|
|       AAAA          |     street 2     |
|---------------------|------------------|
|       AAAAAb        |     street 2     |
|---------------------|------------------|
|       AAAAAB        |     street 2     |
|---------------------|------------------|

这个想法是建立一个像 0,8(或类似)的阈值来识别具有 80% 重合的结果。

然后使用 dplyr 库按 'similar_names' 对它们进行分组,以仅保留每个组的一个结果(行)。

library (dplyr)
groups <- df %>%
  group_by(similar_names) %>%
  summarise() %>%
  arrange(name)

我尝试了使用不同库的不同选项,例如:stringr、duplicated、adist 等...但我没有找到好的解决方案。

【问题讨论】:

标签: r string


【解决方案1】:

这里我有一个例子要输入:

df <- tibble::tribble(
  ~name,       ~number,       ~ind,
  "ARPO",      "405162",      5,
  "ARPO S.L.", "504653",      22,
  "ARPOS",     "900232",      1,
  "ARPO",      "504694",      12,
  "ARPO",      "400304",      42,
  "JJJJ",      "401605",      2,
  "JJJJ",      "900029",      31,
  "BBBBB",     "400090",      25,
  "BBBBB",     "403004",      33,
  "JJJJ",      "900222",      2,
  "BBBBB",     "403967",      11,
  "BBBB",      "400304",      52,
  "JJJJ",      "404308",      200,
  "ARPO",      "403898",      2,
  "ARPO",      "158159",      24,
  "BBBBBBB",   "700805",      2,
  "ARPO S.L.", "900245",      24,
  "JJJJ",      "501486",      2,
  "JJJJ",      "400215",      210,
  "JJJJ",      "504379",      26,
  "HARPO",     "900222",      400,
  "BBBBB",     "109700",      46,
  "ARPO",      "142173",      14,
  "BBBBB",     "400586",      22,
  "ARPO",      "401605",      322
)

我在这里找到了类似的解决方案:Group together levels with similar names R

x <- df$name

groups <- list()
i <- 1
while(length(x) > 0) {

  id <- agrep(x[1], x, ignore.case = TRUE, max.distance = 0.1)
  groups[[i]] <- x[id]
  x <- x[-id]
  i <- i + 1

}

因此,从那时起,您可以创建一个组变量:

df$group <- ""

for (j in 1:length(groups)){
  df$group <- ifelse(df$name %in% groups[[j]], paste0("group_",j), df$group)
}

也许您可以找到一个更简单的解决方案,但这可行!

【讨论】:

  • 谢谢诺莉亚! 'max.distance' 参数是阈值吗?我不太确定在运行循环之后如何创建组。最大距离为 0.1 时有 1340 个组。
【解决方案2】:

下面的函数使用agrepl 来获取相似的字符串,给定一个阈值thresh。并返回找到组中第一个位置的整数向量。

测试数据为NoeliaNC's answer中的数据。

library(dplyr)

similarGroups <- function(x, thresh = 0.8){
  grp <- integer(length(x))
  name <- x
  for(i in seq_along(name)){
    if(!is.na(name[i])){
      sim <- agrepl(x[i], x, ignore.case = TRUE, max.distance = 1 - thresh)
      k <- which(sim & !is.na(name))
      grp[k] <- i
      is.na(name) <- k
    }
  }
  grp
}

similarGroups(df[['name']])
# [1] 1 1 1 1 1 6 6 8 8 6 8 8 6 1 1 8 1 6 6 6 1 8 1 8 1

现在应用该函数对数据框进行分组。

df %>%
  mutate(group = name[similarGroups(name)]) %>%
  count(group)
## A tibble: 3 x 2
#  group     n
#  <chr> <int>
#1 ARPO     11
#2 BBBBB     7
#3 JJJJ      7

编辑

另一种方法是使用包stringdist 中的stringsim 函数。它具有多种距离/相似性度量,可以对其进行测试以查看哪一个可以提供更好的结果。

similarGroups2 <- function(x, thresh = 0.8, method = "soundex"){
  grp <- integer(length(x))
  name <- x
  x <- tolower(x)
  for(i in seq_along(name)){
    if(!is.na(name[i])){
      sim <- stringdist::stringsim(x[i], x, method = method)
      k <- which(sim > thresh & !is.na(name))
      grp[k] <- i
      is.na(name) <- k
    }
  }
  grp
}

df %>%
   mutate(group = name[similarGroups2(name, thresh = 0.7, method = "jw")]) %>%
   count(group)
## A tibble: 4 x 2
#  group             n
#  <chr>         <int>
#1 Antonio Gomez     3
#2 ARPO             11
#3 BBBBB             7
#4 JJJJ              7

新数据

df <- tibble::tribble(
  ~name,       ~number,       ~ind,
  'Antonio Gomez', 1234,       1,
  'Antonio Sanches', 5678,     2,
  'Antonio Ruiz',  9089,       3,
  "ARPO",      "405162",      5,
  "ARPO S.L.", "504653",      22,
  "ARPOS",     "900232",      1,
  "ARPO",      "504694",      12,
  "ARPO",      "400304",      42,
  "JJJJ",      "401605",      2,
  "JJJJ",      "900029",      31,
  "BBBBB",     "400090",      25,
  "BBBBB",     "403004",      33,
  "JJJJ",      "900222",      2,
  "BBBBB",     "403967",      11,
  "BBBB",      "400304",      52,
  "JJJJ",      "404308",      200,
  "ARPO",      "403898",      2,
  "ARPO",      "158159",      24,
  "BBBBBBB",   "700805",      2,
  "ARPO S.L.", "900245",      24,
  "JJJJ",      "501486",      2,
  "JJJJ",      "400215",      210,
  "JJJJ",      "504379",      26,
  "HARPO",     "900222",      400,
  "BBBBB",     "109700",      46,
  "ARPO",      "142173",      14,
  "BBBBB",     "400586",      22,
  "ARPO",      "401605",      322
)

【讨论】:

  • 非常感谢瑞!现在,如果我只想保留每组的一个结果,那么想法是继续使用 agrepl 以删除除每组一个结果之外的所有结果,对吗?我能问一下你的方法是什么吗?谢谢!
  • 在这种情况下,函数会失败,因为它按 100% 的可信度对具有相同名字的人进行分组:antonio、antonio sanchez、antonio ruiz、antonio gomez(名字是不真实的,因为权利保护)。你将如何处理这个问题?
  • Rui,在您的编辑中,您再次调用了 SimilarGroups 函数或者是输入错误,您的意思是 SimilarGroups2? df %>% mutate(group = name[similarGroups(name, thresh = 0.7, method = "jw")]) %>% count(group)
  • @kikusanchez 你是对的,对不起。将编辑,它是similarGroups2
猜你喜欢
  • 2018-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多