为何apply比for循环慢?矩阵逐行拟合glm的高效实现咨询
为什么For循环比Apply在逐行拟合GLM时更快?
你遇到的这个情况其实挺常见的——不少R用户会默认apply家族函数比手动写for循环高效,但在逐行拟合GLM这类场景下,反而for循环的速度更占优。咱们先从你提供的模拟代码说起,再拆解背后的原因,最后聊聊这类场景下的正确编码方式。
模拟数据与性能对比
首先是你用来测试的模拟数据代码:
# 生成模拟数据 response_mat <- matrix(0, 10000, 100) response_mat <- apply(response_mat, c(1,2), function(x) sample(c(0,1),1)) predictor <- rnorm(100)
For循环实现
ptm <- proc.time() for (i in 1:nrow(response_mat)){ model <- glm(response_mat[i,]~predictor, family="binomial") } proc.time() - ptm
实际运行时,这段代码的耗时通常会明显低于apply版本。
Apply实现
glm_function <- function(x){model <- glm(x~predictor, family="binomial")} ptm <- proc.time() apply(response_mat, 1, glm_function) proc.time() - ptm
为什么For循环更快?
核心原因在于apply并没有真正的“向量化加速”,反而多了额外的开销:
- 函数调用的额外成本:apply会把每行数据作为参数传递给你定义的
glm_function,每一次迭代都多了一层函数调用的开销;而for循环里直接调用glm,省去了这层包装的成本。 - 底层的额外处理:apply本质上是R语言层面的语法糖,底层依然是循环,但它会额外做数据类型转换、结果收集等操作——比如它会把矩阵的每一行转换成单独的向量,再传递给函数,这些步骤都会消耗时间。
- 内存管理差异:apply会自动把所有拟合出的GLM模型收集到一个列表中返回,这个过程中需要不断分配内存来存储模型对象;而你的for循环代码里每次都覆盖
model变量,内存压力更小,垃圾回收的频率也更低。
此类场景下的正确编码方式
既然for循环本身已经更高效,我们可以在此基础上进一步优化,或者选择更底层的函数来提速:
1. 预先分配结果容器(如果需要保存输出)
如果你的目标不是只拟合模型,而是要提取系数、预测值等结果,预先分配好存储结果的容器(比如矩阵)可以避免循环中频繁的内存分配,进一步提升速度:
# 预先分配存储系数的矩阵(截距 + predictor系数) coefs <- matrix(NA, nrow = nrow(response_mat), ncol = 2) ptm <- proc.time() for (i in 1:nrow(response_mat)){ model <- glm(response_mat[i,]~predictor, family="binomial") coefs[i,] <- coef(model) } proc.time() - ptm
2. 使用底层的glm.fit代替glm
glm函数包含了很多额外的参数检查、模型诊断信息生成等步骤,如果不需要这些功能,直接调用底层的glm.fit可以大幅提速——它跳过了上层的包装逻辑,只做核心的拟合计算:
# 预先构造设计矩阵(只需做一次) X <- model.matrix(~predictor) coefs <- matrix(NA, nrow = nrow(response_mat), ncol = 2) ptm <- proc.time() for (i in 1:nrow(response_mat)){ y <- response_mat[i,] fit <- glm.fit(X, y, family = binomial()) coefs[i,] <- fit$coefficients } proc.time() - ptm
3. 若偏好函数式风格,可尝试purrr::map
如果你更喜欢函数式编程的写法,可以用purrr包的map函数,它的函数调用开销比base R的apply略小,但速度依然不如手动优化的for循环:
library(purrr) ptm <- proc.time() models <- map(seq_len(nrow(response_mat)), ~glm(response_mat[.,]~predictor, family="binomial")) proc.time() - ptm
内容的提问来源于stack exchange,提问作者Esteban
相关产品推荐
相关产品推荐

