【问题标题】:vectorized text mining over multiple columns多列向量化文本挖掘
【发布时间】:2018-01-08 16:31:08
【问题描述】:

我有一些代码想要矢量化,但我不确定如何。以下代码提供了一些示例数据,由名称和地址组成。

name <- c("holiday inn", "geico", "zgf", "morton phillips")
address <- c("400 lafayette pl tupelo ms", "227 geico plaza chevy chase md", 
         "811 quincy st washington dc", "1911 1st st rockville md")

source1 <- data.frame(name, address)

name <- c("williams sonoma", "mamas bbq", "davis polk", "hop a long diner",
      "joes crag shack", "mike lowry place", "holiday inn", "zummer")

name2 <- c(NA, NA, NA, NA, NA, NA, "hi express", "zummer gunsul frasca")
address <- c("2 reads way new castle de", "248 w 4th st newark de",
         "1100 21st st nw washington dc", "1804 w 5th st wilmington de",
         "1208 kenwood parkway holdridge nb", "4203 ocean drive miami fl",
         "400 lafayette pl tupelo ms", "811 quincy st washington dc")
source2 <- data.frame(name, name2, address) 

此块通过 R 的原生 adist 函数计算两列文本之间的 levenshtein 区别,然后应用 min 函数。

dist.name<- adist(source1$name,source2$name, partial = TRUE, ignore.case = TRUE)
dist.address <- adist(source1$address, source2$address, partial = TRUE, ignore.case = TRUE)

min.name<-apply(dist.name, 2, min)
min.address <- apply(dist.address, 2, min)

我想做以下事情:

  1. 根据最小 levenshtein 距离匹配 source1$namesource2$name
  2. 如果 1 的结果产生 NA,则使用 levenshtein 距离根据 source1$addresssource2$address 进行匹配。我尝试使用 for 循环,它适用于 1 但不适用于 2。这是我用来尝试合并两者的代码:

    match.s1.s2<-NULL  
    for(i in 1:nrow(dist.name)){
      for(j in 1:nrow(dist.address)){
    if(is.na(match(min.name[i], dist.name[i, ]))) {
    s2.i <- match(min.address[j], dist.address[j,])
    s1.i <- i
    match.s1.s2 <- match.s1.s2<-rbind(data.frame(s2.i=s2.i,s1.i=s1.i,s2name=source2[s2.i,]$name, 
                                             s1name=source1[s1.i,]$name, adist=min.name[j], 
                                             s1.i.address = source1[s1.i,]$address,
                                             s2.i.address = source2[s2.i,]$address),match.s1.s2)
    
    } else {
      s2.i<-match(min.name[i],dist.name[i,])
      s1.i<-i
      match.s1.s2<-rbind(data.frame(s2.i=s2.i,s1.i=s1.i,s2name=source2[s2.i,]$name, s1name=source1[s1.i,]$name, 
                                adist=min.name[i], s1.i.address = source1[s1.i,]$address,
                                s2.i.address = source2[s2.i,]$address),match.s1.s2)
        }
    
      }
    
    }
    

我的问题是它很慢,最终会生成一个太大的数据框。最终结果,数据框match.s1.s2 应该与 source1 具有相同的行数。任何建议或帮助将不胜感激。谢谢。

【问题讨论】:

  • address.full?
  • @42- 刚刚编辑过的代码。应该只是最初定义的address

标签: r for-loop vectorization tm levenshtein-distance


【解决方案1】:

使用归一化分数(0 到 1 之间)会更有效。这样,您可以使用矢量化的ifelse 仅更改地址对应分数的NA。对于非标准化分数,您必须更改整行。试试这个方法:

dist.mat.nm <- adist(source1$name, source2$name, partial = TRUE, ignore.case = TRUE)
dist.mat.ad <- adist(source1$address, source2$address, partial = TRUE, ignore.case = TRUE)

#If you use non-normalized distances
dist.mat <- dist.mat.nm
for(i in 1:nrow(dist.mat)){
  if(is.na(dist.mat[i, ])) dist.mat[i, ] <- dist.mat.ad[i, ]
}

#If you use normalized distances
dist.mat <- ifelse(is.na(dist.mat.nm), dist.mat.ad, dist.mat.nm)

which.match <- function(x, nm) return(nm[which(x == min(x))[1]])

matches <- apply(dist.mat, 1, which.match, nm = source2$name)

这可能会提高性能并解决您的问题。如果您愿意更改为标准化距离(而不是 levenshtein),我会推荐 Jaro-Winkler's。

【讨论】:

  • 谢谢,这很直观。但是它也没有考虑地址,这就是问题所在。
  • 编辑它以考虑地址。
  • 1.是的,实际上代码中的which(x == min(x)) 会返回匹配项的位置。我将它与附加的[1] 一起使用,因此如果有平局,它只会返回第一个匹配项。然后我将它包装在 nm[] 中以返回值而不是位置。如果你想要两者,在roder中你可以使用:function(x, nm) return(nm[which(x == min(x))[1]]),它返回位置,然后添加一个带有source1$matched_name &lt;- sorce2$name[matches]的“matched_name”列。这样你的函数性能会更好。
  • 2.非标准化距离随字符串长度的变化而变化。这样,您无法从地址矩阵中获取单个值并插入以与名称矩阵进行比较。如果对它们进行规范化,名称和地址矩阵将以相同的方式变化,从而允许您比较 min 调用中的任何行值组合。
  • 是的,对不起。正确的代码是:function(x, nm) return(which(x == min(x))[1])。如果您为此函数调用apply 并将其分配给matches,那么您将拥有索引。使用您运行的索引nm.matches &lt;- source2$names[matches]
猜你喜欢
  • 1970-01-01
  • 2019-05-05
  • 2013-02-09
  • 1970-01-01
  • 1970-01-01
  • 2018-06-05
  • 2011-02-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多