使用索引计算RMSE时生成NA值的问题(波士顿房价数据集)
波士顿房价数据集RMSE计算出现NA值的问题解决
问题重现
使用MASS包的波士顿房价数据集,通过索引方式存储5次交叉验证的RMSE时,结果出现NA值:
library(MASS) library(glmnet) for (i in 1:5){ idx <- sample(seq(1, 3), size = nrow(MASS::Boston), replace = TRUE, prob = c(.6, .2, .2)) train <- MASS::Boston[idx == 1,] test <- MASS::Boston[idx == 2,] validation <- MASS::Boston[idx == 3,] elastic.test.RMSE <- 0 elastic.test.pred <- 0 y <- train$medv x <- data.matrix(train %>% dplyr::select(-medv)) elastic.model <- glmnet(x, y, alpha = 0.5) elastic.cv <- cv.glmnet(x, y, alpha = 0.5) best.elastic.lambda <- elastic.cv$lambda.min best.elastic.model <- glmnet(x, y, alpha = 0, lambda = best.elastic.lambda) elastic.test.pred <- predict(best.elastic.model, s = best.elastic.lambda, newx = data.matrix(test %>% dplyr::select(-medv))) elastic.test.RMSE[i] <- Metrics::rmse(actual = test$medv, predicted = elastic.test.pred) }
运行后输出:
[1] 0.000000 NA NA NA 4.019411
但改用数据框存储RMSE时,结果完全正常:
elastic.test.RMSE.df <- data.frame(elastic.test.RMSE) library(MASS) library(glmnet) for (i in 1:5){ idx <- sample(seq(1, 3), size = nrow(MASS::Boston), replace = TRUE, prob = c(.6, .2, .2)) train <- MASS::Boston[idx == 1,] test <- MASS::Boston[idx == 2,] validation <- MASS::Boston[idx == 3,] elastic.test.RMSE <- 0 elastic.test.pred <- 0 y <- train$medv x <- data.matrix(train %>% dplyr::select(-medv)) elastic.model <- glmnet(x, y, alpha = 0.5) elastic.cv <- cv.glmnet(x, y, alpha = 0.5) best.elastic.lambda <- elastic.cv$lambda.min best.elastic.model <- glmnet(x, y, alpha = 0, lambda = best.elastic.lambda) elastic.test.pred <- predict(best.elastic.model, s = best.elastic.lambda, newx = data.matrix(test %>% dplyr::select(-medv))) elastic.test.RMSE <- Metrics::rmse(actual = test$medv, predicted = elastic.test.pred) elastic.test.RMSE.df <- rbind(elastic.test.RMSE.df, elastic.test.RMSE) }
运行后输出:
> elastic.test.RMSE.df elastic.test.RMSE 1 5.213519 2 4.806393 3 5.412275 4 5.749699 5 5.192845 6 4.229541
问题根源
出现NA的核心原因有两个:
- 向量初始化位置错误:在循环内部每次执行
elastic.test.RMSE <- 0,会将之前存储的向量重置为单个数值0。当后续赋值elastic.test.RMSE[i]时,R会自动扩展向量,但之前迭代的结果会丢失,且如果某次计算返回NA,就会直接保留在向量中。 - 测试集可能为空:使用
sample随机划分时,存在概率性的idx==2样本数为0的情况,此时test是空数据框。Metrics::rmse在实际值和预测值都为空的情况下会返回NA。
另外,原代码中还有一个参数不一致的小问题:交叉验证用的是alpha=0.5(弹性网),但训练最优模型时写成了alpha=0(岭回归),这会导致模型类型不匹配,也可能影响结果稳定性。
修正后的代码
针对上述问题,调整代码如下:
library(MASS) library(glmnet) library(dplyr) library(Metrics) # 循环外初始化长度为5的数值向量 elastic.test.RMSE <- numeric(5) for (i in 1:5){ # 循环直到获得非空的测试集 idx <- NULL while(is.null(idx) || sum(idx == 2) == 0){ idx <- sample(seq(1, 3), size = nrow(MASS::Boston), replace = TRUE, prob = c(.6, .2, .2)) } train <- MASS::Boston[idx == 1,] test <- MASS::Boston[idx == 2,] validation <- MASS::Boston[idx == 3,] y <- train$medv x <- data.matrix(train %>% select(-medv)) # 直接交叉验证,无需提前训练基础模型 elastic.cv <- cv.glmnet(x, y, alpha = 0.5) best.elastic.lambda <- elastic.cv$lambda.min # 保持alpha参数与交叉验证一致 best.elastic.model <- glmnet(x, y, alpha = 0.5, lambda = best.elastic.lambda) elastic.test.pred <- predict(best.elastic.model, s = best.elastic.lambda, newx = data.matrix(test %>% select(-medv))) elastic.test.RMSE[i] <- rmse(actual = test$medv, predicted = elastic.test.pred) } # 查看结果 elastic.test.RMSE
关键修改说明
- 向量初始化移至循环外:预先创建长度为5的数值向量,避免每次迭代重置覆盖之前的计算结果。
- 确保测试集非空:通过
while循环重新划分数据集,直到得到包含样本的测试集,彻底避免空数据集导致的NA。 - 统一模型参数:将
best.elastic.model的alpha参数修正为0.5,与交叉验证的设置保持一致,保证模型类型匹配。
内容的提问来源于stack exchange,提问作者Russ Conte
相关产品推荐
相关产品推荐

