【问题标题】:Partial string matching in R and trim the charactersR中的部分字符串匹配并修剪字符
【发布时间】:2018-12-07 20:49:28
【问题描述】:

这是一个数据框和一个向量。

df1  <-  tibble(var1 = c("abcd", "efgh", "ijkl", "mnopqr", "qrst"))
vec <-  c("ab", "mnop", "ijk")

现在,对于 var1 中与 vec 中的值最接近(我想匹配前 n 个字符)的所有值,在 var1 中只保留 upto vec 的前 3 个字符,这样所需的解决方案是:

df2 <- tibble(var1 = c("ab", "efgh", "ijk", "mno", "qrst"))

由于“abcd”在 vec 中与“ab”最接近,我们只保留最多 3 个“ab”字符,在这种情况下,在 df2 中为 2,但“efgh”在 vec 中不存在,所以我们保留它是 df2 中的“efgh”等等。

我可以使用 dplyr、stringr、fuzzyjoin、agrep 或 blurwuzzyr 来完成此任务吗?感谢 Psidom,您可能希望在 https://stackoverflow.com/a/51053674/6762788 的以下建议的基础上进行构建。

df1 %>% 
    mutate(var1 = ifelse(var1 %in% vec, substr(var1, 1, 3), var1))

【问题讨论】:

  • 如果值 'tmnop' 在 var1 中,它会返回 'mno' 还是什么都不返回?如果 'nope' 在 var1 中,它会返回 nop 还是什么都不返回?
  • 我想匹配前 n 个字符。所以 tmnop 和 nope 应该像 efgh 一样返回 tmnop 和 nope。

标签: r string-matching fuzzy-search agrep fuzzyjoin


【解决方案1】:

这是一个两步解决方案。首先,使用前 n 个字符进行模糊匹配和替换的函数。它运行agrepl 以将输入模式与提供的向量匹配,如果匹配,则保持第一个n 字符。如果没有匹配,则返回NA。这旨在通过lapply 应用于模式向量,因此第二个函数意味着Reduce 将其转换为一个向量。 reducer 接收两个长度相同的向量,并用第二个的非缺失值替换第一个的所有实例,其中第二个不是NA

这一切都包含在几个调用中,并根据需要返回向量。

fuzzy_match_and_replace = function(pattern, vector, n = 3){
  n = min(c(n,nchar(pattern)))
  match = agrepl(pattern,vector)
  pattern_first_n = substr(pattern,1,n)
  vector_first_n = substr(vector,1,n)
  output = rep(NA,length(vector))
  output[match & pattern_first_n == vector_first_n] = pattern_first_n
  return(output)
}

reducer = function(a,b){
  a[!is.na(b)] = b[!is.na(b)]
  return(a)
}


df1  <-  data.frame(var1 = c("abcd", "efgh", "ijkl", "mnopqr", "qrst"), stringsAsFactors = FALSE)
vec <-  c("ab", "mnop", "ijk")
Reduce(reducer,lapply(vec,fuzzy_match_and_replace,vector=df1$var1),init=df1$var1)
#> [1] "ab"   "efgh" "ijk"  "mno"  "qrst"

如果您希望它在变异步骤中工作,您可以使用如下包装器

wrapper = function(pattern, vector, n = 3){
  Reduce(reducer,lapply(pattern,fuzzy_match_and_replace,vector=vector,n=n),init=vector)
}

更新

这是一个更简单的函数(1 步),它利用 Onyambu 的答案中的 adist,但不依赖 max.col,而是使用 vapply 遍历矩阵来识别匹配并进行替换。

fuzzy_match_and_replace = function(pattern, vector, n = 3, ...){
  matches = adist(pattern,vector,partial=T,...) == 0
  replace = vapply(apply(matches,2,which)
                  ,function(x){
                    if(length(x) > 0) return(substr(pattern,1,n)[x]) else return(NA_character_)
                   }
                  ,FUN.VALUE = c(""))
  vector[!is.na(replace)] = replace[!is.na(replace)]
  return(vector)
}

library(dplyr)
df1  <-  tibble(var1 = c("abcd", "efgh", "ijkl", "mnopqr", "qrst","mnopr"))
vec <-  c("ab", "mnop", "ijk")

df1%>%
  mutate(var1=fuzzy_match_and_replace(vec,var1))
#> # A tibble: 6 x 1
#>   var1 
#>   <chr>
#> 1 ab   
#> 2 efgh 
#> 3 ijk  
#> 4 mno  
#> 5 qrst 
#> 6 mno

【讨论】:

  • 感谢您的详细解释并创建了如此复杂的功能来解决此问题!
  • 太棒了!你也可以让它不区分大小写吗?
  • 在第二个函数中,您可以通过... 将参数传递给adist,因此只需添加ignore.case=TRUE
  • 这使它更加灵活。
  • 我将无法重现我的真实数据,但上述方法给了我这个错误:列 var1 的长度必须为 2(组大小)或 1,而不是 4。任何想法可能是问题所在,如何解决?
【解决方案2】:
df1 <- tibble(var1 = c("abcd", "efgh", "ijkl", "mnopqr", "qrst","mnopr"))

a = which(adist(vec,df1$var1,partial = T,ignore.case = T)==0,T)

df1%>%
  mutate(var1=replace(var1,a[,2],substr(vec[a[,1]],1,3)))
# A tibble: 6 x 1
  var1 
  <chr>
1 ab   
2 efgh 
3 ijk  
4 mno  
5 qrst 
6 mno  

【讨论】:

  • 这真的很有趣!你也可以让它不区分大小写和一个不同的变量,比如 var2 吗?所以,AB 应该匹配 ab 等等。
  • @Geet 在函数adist(...,ignore.case=TRUE) 中包含ignore.case=TRUE 甚至在grep 函数中,您可以包含ignore.case=TRUE 这将使其不区分大小写
  • 此解决方案适用于给定的示例,但当 df1 中有更多匹配项时会崩溃,例如df1 &lt;- tibble(var1 = c("abcd", "efgh", "ijkl", "mnopqr", "qrst","mnopr")) - 在这种情况下,输出保持 mnopqr 不变
  • @Onyambu:我将无法重现我的真实数据,但上述方法给了我这个错误:列 var1 的长度必须为 2(组大小)或 1,而不是 1166955。任何知道可能是什么问题以及如何解决?
  • @Geet 您的数据已分组。您需要将其取消组合。即df1%&gt;%ungroup()%&gt;%mutate(...)
猜你喜欢
  • 1970-01-01
  • 2014-07-20
  • 2021-11-19
  • 1970-01-01
  • 1970-01-01
  • 2016-10-18
  • 1970-01-01
  • 1970-01-01
  • 2014-08-07
相关资源
最近更新 更多