在 R 中 - 最快的方式成对比较字符串的相似性
In R - fastest way pairwise comparing character strings on similarity
我正在寻找一种方法来加速以下方法。任何指针都非常受欢迎。瓶颈在哪里?
假设我有以下 data.frame
:
df <- data.frame(names=c("A ADAM", "S BEAN", "A APPLE", "J BOND", "J BOND"),
v1=c("Test_a", "Test_b", "Test_a", "Test_b", "Test_b"),
v2=c("Test_c", "Test_c", "Test_d", "Test_d", "Test_d"))
我想比较 df
中每一对行的 JaroWinkler 相似性。
在其他人 (see this post) 的帮助下,我已经能够构建此代码:
#columns to compare
testCols <- c("names", "v1", "v2")
#compare pairs
RowCompare= function(x){
comp <- NULL
pairs <- t(combn(nrow(x),2))
for(i in 1:nrow(pairs)){
row_a <- pairs[i,1]
row_b <- pairs[i,2]
a_tests <- x[row_a,testCols]
b_tests <- x[row_b,testCols]
comp <- rbind(comp, c(row_a, row_b, TestsCompare(a_tests, b_tests)))
}
colnames(comp) <- c("row_a","row_b","names_j","v1_j","v2_j")
return(comp)
}
#define TestsCompare
TestsCompare=function(x,y){
names_j <- stringdist(x$names, y$names, method = "jw")
v1_j <-stringdist(x$v1, y$v1, method = "jw")
v2_j <-stringdist(x$v2, y$v2, method = "jw")
c(names_j,v1_j, v2_j)
}
这会生成正确的输出:
output = as.data.frame(RowCompare(df))
> output
row_a row_b names_j v1_j v2_j
1 1 2 0.4444444 0.1111111 0.0000000
2 1 3 0.3571429 0.0000000 0.1111111
3 1 4 0.4444444 0.1111111 0.1111111
4 1 5 0.4444444 0.1111111 0.1111111
5 2 3 0.4603175 0.1111111 0.1111111
6 2 4 0.3333333 0.0000000 0.1111111
7 2 5 0.3333333 0.0000000 0.1111111
8 3 4 0.5634921 0.1111111 0.0000000
9 3 5 0.5634921 0.1111111 0.0000000
10 4 5 0.0000000 0.0000000 0.0000000
然而,我的真实 data.frame 有 800 万次观察,我进行了 17 次比较。 运行 这段代码需要几天...
我正在寻找加快此过程的方法:
- 我应该使用矩阵而不是 data.frames 吗?
- 如何并行化这个过程?
- 向量化?
如果遍历要检查的变量,可以使用 stringdist::stringdistmatrix
为每个变量创建一个距离矩阵。使用 lapply
或 purrr::map
的形式将 return 距离矩阵列表(每列一个),您可以依次迭代到 cal broom::tidy
,它将将它们变成格式良好的 data.frames。如果你使用 purrr::map_df
并使用它的 .id
参数,结果将被强制转换成一个大的 data.frame,并且每个列表元素的名称将被添加为一个新列,因此你可以保留他们直。生成的 data.frame 将是长格式,因此如果您希望它与上面的结果匹配,请使用 tidyr::spread
重塑。
如果,正如您在评论中提到的,您想对不同的变量使用不同的方法,请与 map2
或 Map
并行迭代。
一共
library(tidyverse)
map2(df, c('soundex', 'jw', 'jw'), ~stringdist::stringdistmatrix(.x, method = .y)) %>%
map_df(broom::tidy, .id = 'var') %>%
spread(var, distance)
## item1 item2 names v1 v2
## 1 2 1 1 0.1111111 0.0000000
## 2 3 1 1 0.0000000 0.1111111
## 3 3 2 1 0.1111111 0.1111111
## 4 4 1 1 0.1111111 0.1111111
## 5 4 2 1 0.0000000 0.1111111
## 6 4 3 1 0.1111111 0.0000000
## 7 5 1 1 0.1111111 0.1111111
## 8 5 2 1 0.0000000 0.1111111
## 9 5 3 1 0.1111111 0.0000000
## 10 5 4 0 0.0000000 0.0000000
请注意,虽然 choose(5, 2)
returns 10 个观测值,choose(8000000, 2)
returns 3.2e+13 (32 万亿 ) 观察,因此出于实际目的,即使这比您现有的代码工作得更快(并且 stringdistmatrix
在可能的情况下进行一些并行化),除非您只处理子集,否则数据将变得非常大。
我正在寻找一种方法来加速以下方法。任何指针都非常受欢迎。瓶颈在哪里?
假设我有以下 data.frame
:
df <- data.frame(names=c("A ADAM", "S BEAN", "A APPLE", "J BOND", "J BOND"),
v1=c("Test_a", "Test_b", "Test_a", "Test_b", "Test_b"),
v2=c("Test_c", "Test_c", "Test_d", "Test_d", "Test_d"))
我想比较 df
中每一对行的 JaroWinkler 相似性。
在其他人 (see this post) 的帮助下,我已经能够构建此代码:
#columns to compare
testCols <- c("names", "v1", "v2")
#compare pairs
RowCompare= function(x){
comp <- NULL
pairs <- t(combn(nrow(x),2))
for(i in 1:nrow(pairs)){
row_a <- pairs[i,1]
row_b <- pairs[i,2]
a_tests <- x[row_a,testCols]
b_tests <- x[row_b,testCols]
comp <- rbind(comp, c(row_a, row_b, TestsCompare(a_tests, b_tests)))
}
colnames(comp) <- c("row_a","row_b","names_j","v1_j","v2_j")
return(comp)
}
#define TestsCompare
TestsCompare=function(x,y){
names_j <- stringdist(x$names, y$names, method = "jw")
v1_j <-stringdist(x$v1, y$v1, method = "jw")
v2_j <-stringdist(x$v2, y$v2, method = "jw")
c(names_j,v1_j, v2_j)
}
这会生成正确的输出:
output = as.data.frame(RowCompare(df))
> output
row_a row_b names_j v1_j v2_j
1 1 2 0.4444444 0.1111111 0.0000000
2 1 3 0.3571429 0.0000000 0.1111111
3 1 4 0.4444444 0.1111111 0.1111111
4 1 5 0.4444444 0.1111111 0.1111111
5 2 3 0.4603175 0.1111111 0.1111111
6 2 4 0.3333333 0.0000000 0.1111111
7 2 5 0.3333333 0.0000000 0.1111111
8 3 4 0.5634921 0.1111111 0.0000000
9 3 5 0.5634921 0.1111111 0.0000000
10 4 5 0.0000000 0.0000000 0.0000000
然而,我的真实 data.frame 有 800 万次观察,我进行了 17 次比较。 运行 这段代码需要几天...
我正在寻找加快此过程的方法:
- 我应该使用矩阵而不是 data.frames 吗?
- 如何并行化这个过程?
- 向量化?
如果遍历要检查的变量,可以使用 stringdist::stringdistmatrix
为每个变量创建一个距离矩阵。使用 lapply
或 purrr::map
的形式将 return 距离矩阵列表(每列一个),您可以依次迭代到 cal broom::tidy
,它将将它们变成格式良好的 data.frames。如果你使用 purrr::map_df
并使用它的 .id
参数,结果将被强制转换成一个大的 data.frame,并且每个列表元素的名称将被添加为一个新列,因此你可以保留他们直。生成的 data.frame 将是长格式,因此如果您希望它与上面的结果匹配,请使用 tidyr::spread
重塑。
如果,正如您在评论中提到的,您想对不同的变量使用不同的方法,请与 map2
或 Map
并行迭代。
一共
library(tidyverse)
map2(df, c('soundex', 'jw', 'jw'), ~stringdist::stringdistmatrix(.x, method = .y)) %>%
map_df(broom::tidy, .id = 'var') %>%
spread(var, distance)
## item1 item2 names v1 v2
## 1 2 1 1 0.1111111 0.0000000
## 2 3 1 1 0.0000000 0.1111111
## 3 3 2 1 0.1111111 0.1111111
## 4 4 1 1 0.1111111 0.1111111
## 5 4 2 1 0.0000000 0.1111111
## 6 4 3 1 0.1111111 0.0000000
## 7 5 1 1 0.1111111 0.1111111
## 8 5 2 1 0.0000000 0.1111111
## 9 5 3 1 0.1111111 0.0000000
## 10 5 4 0 0.0000000 0.0000000
请注意,虽然 choose(5, 2)
returns 10 个观测值,choose(8000000, 2)
returns 3.2e+13 (32 万亿 ) 观察,因此出于实际目的,即使这比您现有的代码工作得更快(并且 stringdistmatrix
在可能的情况下进行一些并行化),除非您只处理子集,否则数据将变得非常大。