R语言lm函数循环调用时subset参数识别异常求解
问题根因
这是R基础建模函数非标准求值机制导致的经典作用域坑:lm()对subset参数采用非标准求值,查找变量的路径默认只有两个优先级:
- 先找传入
data参数里的列 - 找不到就去全局环境找
它不会回溯到外层lapply/mapply的匿名函数执行环境,所以你在循环函数里定义的局部变量x、y,对lm的subset求值逻辑来说是完全不可见的。
对应两种失效写法的具体表现:
- 第一种
lapply写法没报错但所有模型结果一致:是因为你的全局环境里恰好存在一个名为x的历史对象,lm找不到循环内的x,就直接复用全局的x计算筛选条件,每次循环的子集根本没变化,输出结果自然完全相同。 - 第二种
mapply写法报object 'y' not found:是因为你的全局环境里没有名为y的对象,lm找了一圈没找到y,直接抛出错误。
你写的先调用subset()筛数据再传给lm的写法之所以能跑,是因为子集筛选的逻辑是在lapply的匿名函数内部执行的,这时候找x的路径是当前函数环境,能正常拿到每次循环的排除名单,筛完的数据直接传给lm,不需要lm自己处理subset的变量查找,自然不会出问题。
更规范的实现方式
推荐两种无环境依赖、可复现性强的写法,不需要依赖额外宏包,也不会踩非标准求值的坑:
写法1:提前筛完训练数据再建模(最稳妥)
直接在循环内部先完成数据子集筛选,把清洗好的训练数据传给lm,完全绕开lm的subset参数求值问题:
library(faraway) data(savings) form <- "sr ~ pop75 + dpi + ddpi + pop15" label_res <- list(pop75 = c("Ireland", "Japan"), dpi = c("Ireland", "Sweden", "United States"), ddpi = "Libya", pop15 = c("Japan", "Libya")) model_list <- lapply(label_res, function(exclude_names){ # 先在当前函数环境完成行筛选 train_data <- savings[!rownames(savings) %in% exclude_names, ] lm(form, data = train_data) })
写法2:提前计算好子集逻辑向量再传给subset
如果一定要用lm的subset参数,不要在参数位置写依赖局部变量的表达式,提前在当前函数环境算好和数据行数一致的逻辑向量,直接传值给subset即可,不需要lm再做变量查找:
model_list2 <- lapply(label_res, function(exclude_names){ # 提前算好逻辑向量 sub_logic <- !rownames(savings) %in% exclude_names lm(form, data = savings, subset = sub_logic) })
不要用把循环变量赋值到全局环境的野路子解决问题,代码换个运行环境、或者全局变量被意外修改就会出不可复现的错误。
内容的提问来源于stack exchange,提问作者Emil11
相关产品推荐
相关产品推荐

