慢功能,如何在 R 中从中删除 for 循环
Slow function, how can I remove the for loop from it in R
我在 R 中有一个函数,可以将较小的向量与较大的向量进行比较,然后找到匹配项并使用该信息从较大的数据框中提取数据。
compare_masses <- function(mass_lst){
for (i in seq_along(mass_lst)) {
positions <- which(abs(AB_massLst_numeric - mass_lst[i]) < 0.02)
rows <- AB_lst[positions,]
match_df <- rbind(match_df, rows)
}
}
其中 mass_lst
是化合物质量的列表:
例如:mass_lst <- c(315, 243, 484, 121)
AB_massLst_numeric
是较大的质量列表:
例如:AB_massLst_numeric <- c(323, 474, 812, 375, 999, 271, 676, 232)
AB_lst
是一个更大的数据框,我使用位置向量从中提取数据。
match_df
是一个空数据框我做rbind
数据到.
问题是这个函数中有一个 for 循环,即使我使用它也需要很长时间
test <- sapply(mass_lst, compare_masses)
所以我的问题是如何使这个函数更快并可能删除 for 循环?我的数据在现实生活中比我提供的例子要大得多。我想不出一种不迭代来使这个功能起作用的方法。
尝试将其全部封装在一个调用中并使用 do.call
以便它同时执行所有 rbind
调用,而不是一次调用一个。
match_df <- do.call(rbind.data.frame, lapply(
mass_lst, function(x)
AB_lst[abs(AB_lst_numeric - x) < 0.02,]))
为了回应有关 do.call
与 dplyr::bind_rows
相比速度的评论,我创建了一个 AB_lst_numeric
,其 1k 值介于 0 和 1000 之间,对应的 AB_lst
data.frame
以及具有 100 个元素的 mass_lst
向量。以下是使用 rbenchmark
进行测试的结果,正如您所见,do.call
和 bind_rows
调用非常相似(与 110% 相比,bind_rows
的效率高出 36%与原始解决方案相比效率提升)。
benchmark(
match_df <- compare_masses(mass_lst),
match_df <- do.call(rbind.data.frame, lapply(
mass_lst, function(x)
AB_lst[abs(AB_lst_numeric - x) < 0.02,])),
match_df <- bind_rows(lapply(
mass_lst, function(x)
AB_lst[abs(AB_lst_numeric - x) < 0.02,])))
## 3 match_df <- bind_rows(lapply(mass_lst, function(x) AB_lst[abs(AB_lst_numeric - x) < 0.02, ]))
## 1 match_df <- compare_masses(mass_lst)
## 2 match_df <- do.call(rbind.data.frame, lapply(mass_lst, function(x) AB_lst[abs(AB_lst_numeric - x) < 0.02, ]))
## replications elapsed relative user.self sys.self user.child sys.child
## 3 100 1.453 1.000 1.387 0.059 0 0
## 1 100 3.050 2.099 2.983 0.051 0 0
## 2 100 1.974 1.359 1.905 0.060 0 0
这应该是一个向量化的解决方案。使用发布的 compare_masses 功能。它比这里的其他解决方案快得多。
写一个匿名函数来向量化。进行与循环中相同的比较。
pos = Vectorize(FUN = function(y) {abs(AB_massLst_numeric-y) < 0.02}, vectorize.args = "y")
找到你要子集的索引,这一步代替do.call(rbind,...)
或bind_rows
。这一步应该很快,因为它只是对大小为 length(AB_massLst_numeric) x length(mass_lst)
的矩阵进行逻辑比较。需要这一步,因为我无法让 vectorize
函数与 which
很好地协同工作。
i = unlist(apply(X = matrix(sample(c(T,F), 100, r = T), nrow = 10), MARGIN = 2, FUN = which))
子集和存储
AB_lst[i,]
编辑:使用发布的compare_masses功能。它比这里的其他解决方案快得多。
Unit: microseconds
expr min lq mean median uq max neval cld
Vectorize 318.595 327.280 358.9813 355.112 386.892 413.739 10 b
do.call 1418.473 1510.853 1569.7161 1578.954 1635.606 1744.173 10 d
bind_rows 744.570 801.420 813.9346 815.435 836.161 871.297 10 c
compare_masses 135.808 138.176 158.0344 158.508 169.365 197.395 10 a
更大的测试数据集
Unit: nanoseconds
expr min lq mean median uq max neval cld
Vectorize 239242 292341 342314.079 324714 359455 3480844 1000 a
compare_masses 395 1975 3674.669 3554 4738 19346 1000 a
do.call 16570424 18223007 21092022.254 20921183 22194176 159718470 1000 c
bind_rows 13423572 14869680 17027330.356 17008639 18061341 116983885 1000 b
使用 R 的向量循环功能。首先构建长度为 N*m 的 positions
向量,其中 N 是 AB_lst
中的行数,m 是 length(mass_lst)
。然后 select 使用此向量从数据框中提取行。
请参阅下面完整的可运行示例。
positions <- c()
compare_masses <- function(mass_lst){
for (i in seq_along(mass_lst)) {
positions <- c(positions, which(abs(AB_massLst_numeric - mass_lst[i]) < 0.02))
}
return(AB_lst[positions,])
}
mass_lst <- c(375, 243, 676, 121)
AB_massLst_numeric <- c(323, 474, 812, 375, 999, 271, 676, 232, 676)
AB_lst <- data.frame(x=1,y=AB_massLst_numeric)
match_df <- AB_lst[c(),]
compare_masses(mass_lst)
您可以循环查找所需的行索引,然后 select 基于该数据的行:
set.seed(1)
DF <- data.frame(x=runif(1e2), y=sample(letters, 1e2, rep=T))
LIST <- list(0, 0.2, 0.4, 0.5)
DF[unlist(lapply(LIST, function(y) which(abs(DF$x - y) < .02))), ]
对于我们的虚拟数据,这会产生:
x y
24 0.01017122 b
70 0.01065314 d
5 0.19193779 e
40 0.21181133 l
65 0.21488963 q
80 0.20122201 q
16 0.39572663 e
23 0.41434742 x
30 0.41330587 t
67 0.40899105 p
73 0.40808877 x
78 0.49894035 o
79 0.49745918 o
请注意我们选择的值确实在目标的 0.02 范围内。
我在 R 中有一个函数,可以将较小的向量与较大的向量进行比较,然后找到匹配项并使用该信息从较大的数据框中提取数据。
compare_masses <- function(mass_lst){
for (i in seq_along(mass_lst)) {
positions <- which(abs(AB_massLst_numeric - mass_lst[i]) < 0.02)
rows <- AB_lst[positions,]
match_df <- rbind(match_df, rows)
}
}
其中 mass_lst
是化合物质量的列表:
例如:mass_lst <- c(315, 243, 484, 121)
AB_massLst_numeric
是较大的质量列表:
例如:AB_massLst_numeric <- c(323, 474, 812, 375, 999, 271, 676, 232)
AB_lst
是一个更大的数据框,我使用位置向量从中提取数据。
match_df
是一个空数据框我做rbind
数据到.
问题是这个函数中有一个 for 循环,即使我使用它也需要很长时间
test <- sapply(mass_lst, compare_masses)
所以我的问题是如何使这个函数更快并可能删除 for 循环?我的数据在现实生活中比我提供的例子要大得多。我想不出一种不迭代来使这个功能起作用的方法。
尝试将其全部封装在一个调用中并使用 do.call
以便它同时执行所有 rbind
调用,而不是一次调用一个。
match_df <- do.call(rbind.data.frame, lapply(
mass_lst, function(x)
AB_lst[abs(AB_lst_numeric - x) < 0.02,]))
为了回应有关 do.call
与 dplyr::bind_rows
相比速度的评论,我创建了一个 AB_lst_numeric
,其 1k 值介于 0 和 1000 之间,对应的 AB_lst
data.frame
以及具有 100 个元素的 mass_lst
向量。以下是使用 rbenchmark
进行测试的结果,正如您所见,do.call
和 bind_rows
调用非常相似(与 110% 相比,bind_rows
的效率高出 36%与原始解决方案相比效率提升)。
benchmark(
match_df <- compare_masses(mass_lst),
match_df <- do.call(rbind.data.frame, lapply(
mass_lst, function(x)
AB_lst[abs(AB_lst_numeric - x) < 0.02,])),
match_df <- bind_rows(lapply(
mass_lst, function(x)
AB_lst[abs(AB_lst_numeric - x) < 0.02,])))
## 3 match_df <- bind_rows(lapply(mass_lst, function(x) AB_lst[abs(AB_lst_numeric - x) < 0.02, ]))
## 1 match_df <- compare_masses(mass_lst)
## 2 match_df <- do.call(rbind.data.frame, lapply(mass_lst, function(x) AB_lst[abs(AB_lst_numeric - x) < 0.02, ]))
## replications elapsed relative user.self sys.self user.child sys.child
## 3 100 1.453 1.000 1.387 0.059 0 0
## 1 100 3.050 2.099 2.983 0.051 0 0
## 2 100 1.974 1.359 1.905 0.060 0 0
这应该是一个向量化的解决方案。使用发布的 compare_masses 功能。它比这里的其他解决方案快得多。
写一个匿名函数来向量化。进行与循环中相同的比较。
pos = Vectorize(FUN = function(y) {abs(AB_massLst_numeric-y) < 0.02}, vectorize.args = "y")
找到你要子集的索引,这一步代替do.call(rbind,...)
或bind_rows
。这一步应该很快,因为它只是对大小为 length(AB_massLst_numeric) x length(mass_lst)
的矩阵进行逻辑比较。需要这一步,因为我无法让 vectorize
函数与 which
很好地协同工作。
i = unlist(apply(X = matrix(sample(c(T,F), 100, r = T), nrow = 10), MARGIN = 2, FUN = which))
子集和存储
AB_lst[i,]
编辑:使用发布的compare_masses功能。它比这里的其他解决方案快得多。
Unit: microseconds
expr min lq mean median uq max neval cld
Vectorize 318.595 327.280 358.9813 355.112 386.892 413.739 10 b
do.call 1418.473 1510.853 1569.7161 1578.954 1635.606 1744.173 10 d
bind_rows 744.570 801.420 813.9346 815.435 836.161 871.297 10 c
compare_masses 135.808 138.176 158.0344 158.508 169.365 197.395 10 a
更大的测试数据集
Unit: nanoseconds
expr min lq mean median uq max neval cld
Vectorize 239242 292341 342314.079 324714 359455 3480844 1000 a
compare_masses 395 1975 3674.669 3554 4738 19346 1000 a
do.call 16570424 18223007 21092022.254 20921183 22194176 159718470 1000 c
bind_rows 13423572 14869680 17027330.356 17008639 18061341 116983885 1000 b
使用 R 的向量循环功能。首先构建长度为 N*m 的 positions
向量,其中 N 是 AB_lst
中的行数,m 是 length(mass_lst)
。然后 select 使用此向量从数据框中提取行。
请参阅下面完整的可运行示例。
positions <- c()
compare_masses <- function(mass_lst){
for (i in seq_along(mass_lst)) {
positions <- c(positions, which(abs(AB_massLst_numeric - mass_lst[i]) < 0.02))
}
return(AB_lst[positions,])
}
mass_lst <- c(375, 243, 676, 121)
AB_massLst_numeric <- c(323, 474, 812, 375, 999, 271, 676, 232, 676)
AB_lst <- data.frame(x=1,y=AB_massLst_numeric)
match_df <- AB_lst[c(),]
compare_masses(mass_lst)
您可以循环查找所需的行索引,然后 select 基于该数据的行:
set.seed(1)
DF <- data.frame(x=runif(1e2), y=sample(letters, 1e2, rep=T))
LIST <- list(0, 0.2, 0.4, 0.5)
DF[unlist(lapply(LIST, function(y) which(abs(DF$x - y) < .02))), ]
对于我们的虚拟数据,这会产生:
x y
24 0.01017122 b
70 0.01065314 d
5 0.19193779 e
40 0.21181133 l
65 0.21488963 q
80 0.20122201 q
16 0.39572663 e
23 0.41434742 x
30 0.41330587 t
67 0.40899105 p
73 0.40808877 x
78 0.49894035 o
79 0.49745918 o
请注意我们选择的值确实在目标的 0.02 范围内。