r - 将函数应用于矩阵的每一行而不使用 R 中的 lapply 函数
问题描述
我有一个包含多行的输入数据框。对于每一行,我想应用一个函数。输入数据框有 1,000,000+ 行。如何加快零件使用速度lapply
?我想避免以有效的方式将函数应用到每一行数据帧并返回数据帧列表中的应用函数系列,因为这些方法在我的情况下似乎很慢。
这是一个具有简单功能的可重现示例:
library(tictoc) # enable use of tic() and toc() to record time taken for test to compute
func <- function(coord, a, b, c){
X1 <- as.vector(coord[1])
Y1 <- as.vector(coord[2])
X2 <- as.vector(coord[3])
Y2 <- as.vector(coord[4])
if(c == 0) {
res1 <- mean(c((X1 - a) : (X1 - 1), (Y1 + 1) : (Y1 + 40)))
res2 <- mean(c((X2 - a) : (X2 - 1), (Y2 + 1) : (Y2 + 40)))
res <- matrix(c(res1, res2), ncol=2, nrow=1)
} else {
res1 <- mean(c((X1 - a) : (X1 - 1), (Y1 + 1) : (Y1 + 40)))*b
res2 <- mean(c((X2 - a) : (X2 - 1), (Y2 + 1) : (Y2 + 40)))*b
res <- matrix(c(res1, res2), ncol=2, nrow=1)
}
return(res)
}
## Apply the function
set.seed(1)
n = 10000000
tab <- as.matrix(data.frame(x1 = sample(1:100, n, replace = T), y1 = sample(1:100, n, replace = T), x2 = sample(1:100, n, replace = T), y2 = sample(1:100, n, replace = T)))
tic("test 1")
test <- do.call("rbind", lapply(split(tab, 1:nrow(tab)),
function(x) func(coord = x,
a = 40,
b = 5,
c = 1)))
toc()
## test 1: 453.76 sec elapsed
解决方案
这似乎是一个重构向量化计算的好机会,R 可以更快地解决这个问题。(TL;DR:这使它快了大约 1000 倍。)
看起来这里的任务是对两个整数范围进行加权平均,其中范围的书挡因行而异(基于 X1、X2、Y1 和 Y2),但每行中的序列长度相同. 这很有帮助,因为这意味着我们可以使用代数来简化计算。
对于 a = 40 的简单情况,第一个序列将从 x1-40 到 x-1,从 y+1 到 y1+40。平均值将是这两项的总和除以 80。总和将是 40*X1 + 40*Y1 + (-40:-1) 的总和 + (1:40) 的总和,最后两项抵消. 因此,您可以简单地输出每对列的平均值,乘以 b。
library(dplyr)
b = 5
quick_test <- tab_tbl %>%
as_data_frame() %>%
mutate(V1 = (x1+y1)/2 * b,
V2 = (x2+y2)/2 * b)
使用 n = 1E6(OP 的 10%),OP 函数需要 73 秒。上面的函数需要 0.08 秒并且具有相同的输出。
对于 的情况a != 40
,它需要更多的代数。V1
这里以加权平均值结束,我们将序列(x1-a):(x1-1)
和序列相加(y1+1):(y1+40)
,全部除以a+40
(因为序列中有a
项,x1
序列中有 40 个项y1
。我们实际上不需要把这个序列相加;我们可以使用代数将其转换为更短的计算:https ://en.wikipedia.org/wiki/Arithmetic_progression
sum of (x1-a):(x1-1)
= x1*a + sum of (-a:-1)
= x1*a + a*(-a + -1)/2
=x1*a - (a*a + a)/2
这一切意味着我们可以使用以下方法完全复制任何正面的代码a
:
a = 50
b = 5
tictoc::tic("test 2b")
quick_test2 <- quick_test <- tab %>%
as_data_frame() %>%
mutate(V1 = (a*x1 - (a*a + a)/2 + 40*y1 + 820)/(a+40)*b,
V2 = (a*x2 - (a*a + a)/2 + 40*y2 + 820)/(a+40)*b)
tictoc::toc()
这大约快 1000 倍。在 n = 1E6、a = 41、b = 5、c = 1 的情况下,OP 解决方案在我的 2012 笔记本电脑上花费了 154 秒,而quick_test2
上面的解决方案花费了 0.23 秒并得到了相同的结果。
(小附录,如果 c == 0,您可以添加一个测试来设置 b = 1,然后您已经处理了 if-else 条件。)
推荐阅读
- css - Sublime Text Markmon 包,用 github markdown 风格渲染?
- firebase - 如何通过 Google Analytics 管理控制台为 ionic 创建移动应用属性
- c# - 如何在 C# 中的应用程序中运行 Windows IoT 应用程序
- node.js - 在 AWS 上托管 SQLITE 数据库文件
- itunes - 是否可以使用 iTunes API 播放歌曲
- batch-rename - 使用从 EXIF 中提取的日期进行重命名的脚本
- php - 我是否需要过滤/清理/验证用于过滤实体的 GET 变量
- r - R中的正则表达式模式删除
- mysql - 在mysql中以大写命名表名
- android - OkHttp Android 流已重置:HTTP_1_1_REQUIRED