for 循环内部 sapply to return 字符串匹配

for loop inside sapply to return string match

我写这行代码是为了在一个数据框中工作,returns一个不区分大小写的新列与字符串列表的元素匹配。

但是,结果列仅适用于列表的第一个元素,'seed' 在这种情况下,但不适用于其他匹配项。不知道for循环哪里错了

这是您可能想要检查结果的示例数据框。

input.strings <- c('seed', 'fertilizer', 'fertiliser', 'loan', 'interest', 'feed', 'insurance')

polic = data.frame(policy_label=c('seed supply','energy subsidy','fertilizer distribution','loan guarantee','Interest waiver','feed purchase'))

polic$policy_class <- sapply(polic$policy_label, function(x){
                        for (i in input.strings){
                           if (grepl(i, tolower(x))){
                           return(i)
                      }
                      else{
                       return("others")
                      }
                     }
                    }) 

基础 R 替代方案

这是一种使用 sapply(并且没有 for 循环)的更快 more-direct 方法,依赖于 grepl 可以在 [=17 上向量化这一事实=]. (它没有在 pattern= 上矢量化,要求长度为 1,这就是我们根本需要 sapply 的原因之一。)

matches <- sapply(input.strings, grepl, x = polic$policy_label)
matches
#       seed fertilizer fertiliser  loan interest  feed insurance
# [1,]  TRUE      FALSE      FALSE FALSE    FALSE FALSE     FALSE
# [2,] FALSE      FALSE      FALSE FALSE    FALSE FALSE     FALSE
# [3,] FALSE       TRUE      FALSE FALSE    FALSE FALSE     FALSE
# [4,] FALSE      FALSE      FALSE  TRUE    FALSE FALSE     FALSE
# [5,] FALSE      FALSE      FALSE FALSE    FALSE FALSE     FALSE
# [6,] FALSE      FALSE      FALSE FALSE    FALSE  TRUE     FALSE

因为我们想要将 "others" 分配给所有没有匹配的东西(并且因为我们将需要至少一个 TRUE in

matches <- cbind(matches, others = rowSums(matches) == 0)
matches
#       seed fertilizer fertiliser  loan interest  feed insurance others
# [1,]  TRUE      FALSE      FALSE FALSE    FALSE FALSE     FALSE  FALSE
# [2,] FALSE      FALSE      FALSE FALSE    FALSE FALSE     FALSE   TRUE
# [3,] FALSE       TRUE      FALSE FALSE    FALSE FALSE     FALSE  FALSE
# [4,] FALSE      FALSE      FALSE  TRUE    FALSE FALSE     FALSE  FALSE
# [5,] FALSE      FALSE      FALSE FALSE    FALSE FALSE     FALSE   TRUE
# [6,] FALSE      FALSE      FALSE FALSE    FALSE  TRUE     FALSE  FALSE

从这里,我们可以找到与真实值关联的名称并将它们分配(可选 ,-collapsed)到 polic:

polic$policy_class <- apply(matches, 1, function(z) toString(colnames(matches)[z]))
polic
#              policy_label policy_class
# 1             seed supply         seed
# 2          energy subsidy       others
# 3 fertilizer distribution   fertilizer
# 4          loan guarantee         loan
# 5         Interest waiver       others
# 6           feed purchase         feed

仅供参考,我使用 toString 的原因是因为我不想假设永远不会超过一场比赛;也就是说,如果两个 input.strings 由于某种原因匹配一个 policy_label,则 toString 会将它们组合成一个字符串,例如 "seed, feed" 用于 multi-match 策略。

fuzzyjoin 备选方案

如果您熟悉 merges/joins (and What's the difference between INNER JOIN, LEFT JOIN, RIGHT JOIN and FULL JOIN?),那么这应该看起来很熟悉。如果不是,以这种方式连接数据的概念可以转变为 data-munging/cleaning。

library(fuzzyjoin)
out <- regex_left_join(
  polic, data.frame(policy_class = input.strings),
  by = c("policy_label" = "policy_class"))
out
#              policy_label policy_class
# 1             seed supply         seed
# 2          energy subsidy         <NA>
# 3 fertilizer distribution   fertilizer
# 4          loan guarantee         loan
# 5         Interest waiver         <NA>
# 6           feed purchase         feed

### clean up the NAs for "others"
out$policy_class[is.na(out$policy_class)] <- "others"

与上面的 base-R 变体相比,当多个 input.strings 匹配一个 policy_label 时,这里(还没有!)没有 safe-guard 来处理;当发生这种情况时,具有匹配项的行将被复制,因此您会看到(例如)seed supply 和该行上的所有其他列两次。这可以很容易地通过一些努力来缓解。