变量我没有使用出现了“Factor has new levels”错误。

42
考虑一个简单的数据集,分为训练集和测试集:
dat <- data.frame(x=1:5, y=c("a", "b", "c", "d", "e"), z=c(0, 0, 1, 0, 1))
train <- dat[1:4,]
train
#   x y z
# 1 1 a 0
# 2 2 b 0
# 3 3 c 1
# 4 4 d 0
test <- dat[5,]
test
#   x y z
# 5 5 e 1

当我训练一个逻辑回归模型来预测使用x的z,并获得测试集的预测结果时,一切都很顺利。
mod <- glm(z~x, data=train, family="binomial")
predict(mod, newdata=test, type="response")
#         5 
# 0.5546394 

然而,对于一个外观相似的逻辑回归模型,却出现了“因子具有新水平”错误。
mod2 <- glm(z~.-y, data=train, family="binomial")
predict(mod2, newdata=test, type="response")
# Error in model.frame.default(Terms, newdata, na.action = na.action, xlev = object$xlevels) : 
#   factor y has new level e

自从我从模型方程中删除了y,我对看到这个错误信息感到惊讶。在我的应用程序中,dat非常宽,所以z~.-y是最方便的模型规范。我能想到的最简单的解决办法是从我的数据框中删除y变量,然后使用z~.语法训练模型,但我希望能够在不删除列的情况下使用原始数据集的方法。

在我的情况下,我的代码中有一个错误导致模型不稳定。我增加了正确分类实例的权重并减少了错误分类实例的权重。实际上应该反过来... - felixmp
我同意这是令人困惑的行为。model.frame 似乎无法忽略变量。 - undefined
即使test$y显示为一个具有5个水平的因子,但predict在某种程度上并不考虑这一点。 - undefined
https://bugs.r-project.org/show_bug.cgi?id=18619 - undefined
4个回答

49

你可以尝试在模型对象中更新mod2$xlevels[["y"]]

mod2 <- glm(z~.-y, data=train, family="binomial")
mod2$xlevels[["y"]] <- union(mod2$xlevels[["y"]], levels(test$y))

predict(mod2, newdata=test, type="response")
#        5 
#0.5546394 

另一个选择是从训练数据中排除(但不删除)"y"

mod2 <- glm(z~., data=train[,!colnames(train) %in% c("y")], family="binomial")
predict(mod2, newdata=test, type="response")
#        5 
#0.5546394 

1
这两个选项都很好 - 谢谢!帖子中描述的行为几乎看起来像是一个 bug(我不明白为什么我需要从第二个模型规范中删除 y),但这些都是明智的解决方法。 - josliber
3
如果你在glm上运行debug,你可以看到它是如何创建模型项mt <- attr(mf, "terms")的。我认为y被当作模型中的一部分来处理,因为当你使用z~.-y这个公式时,它会扩展为z ~ (x + y) - y,所以从技术上讲,y是模型中的一部分,但我没有其他的见解(只是一个变通方法 :)) - matt_k
使用dplyr:train %>% select(-y) - undefined

3
我们可以将@matt_k的优秀解决方案推广到高维数据,其中训练和测试集中有多个具有不同级别的因素,例如以下内容:
dat2
#   x y1 y2 z
# 1 1  a  A 0
# 2 2  b  B 0
# 3 3  c  C 1
# 4 4  d  D 0
# 5 5  e  E 1

当我们像以前一样将数据集分成测试集和训练集时,

train <- dat2[1:4, ]
test <- dat2[5, ]

y1y2test级别与train不同时,我们会得到错误。

mod <- glm(z ~ ., data=train, family="binomial")
predict(mod, newdata=test, type="response")
# Error in model.frame.default(Terms, newdata, na.action = na.action, xlev = object$xlevels) : 
#   factor y1 has new level e

对于高维数据,纠正每个失败因素都非常无聊,因此我们可能希望循环处理它们。

不管怎样,坏的因素要么是"factor"类,要么是"character"类(就像我们的情况一样)。由于它们将被包含在'xlevels'中,我们使用一个小助手来识别它们,

is.prone <- function(x) is.factor(x) | is.character(x)

并将其放入Map中。

id <- sapply(dat2, is.prone)
mod$xlevels <- Map(union, mod$xlevels, lapply(dat2[id], unique))

那么它应该能够正常工作。
predict(mod, newdata=test, type="response")
#            5 
# 5.826215e-11 
# Warning message:
# In predict.lm(object, newdata, se.fit, scale = 1, type = if (type ==  :
#   prediction from a rank-deficient fit may be misleading

dat2 <- structure(list(x = 1:5, y1 = c("a", "b", "c", "d", "e"), y2 = c("a", 
"b", "c", "d", "e"), z = c(0, 0, 1, 0, 1)), class = "data.frame", row.names = c(NA, 
-5L))

0

我对这个问题困惑了很长时间。然而,有一个简单的解决方案。其中一个变量“流量类型”有20个因素,对于一个因素即17,只有一行数据。因此,这一行数据可能存在于训练数据或测试数据中。在我的情况下,它存在于测试数据中,因此出现了错误 - 因素“流量类型”具有新的17级别,因为在训练数据中没有17级别的行。我从数据集中删除了这一行,模型运行得非常好。


嗨Bhavna - 是的,如果测试集中有一个因子的新级别,而您在模型中使用了该因子,则可能会出现此错误,并且删除该观察值是合理的处理方式。在这个问题中,我特别询问了一个我没有在模型中使用但恰好存在于我的数据框中的因子。在这种情况下,我们不应该从测试集中删除观察值,而matt_k提供了一些不错的方法。 - josliber
并不是真正的解决方案... 你不能仅仅从一个集合中删除所有未知因素。 - felixmp

0
如果您正在使用tidymodels框架,recipes有一种方法可以通过更改变量的“角色”来排除建模中的变量:
现在我们可以为这个配方添加角色。我们可以使用update_role()函数告诉recipes,flighttime_hour是具有自定义角色的变量,我们称之为"ID"(角色可以是任何字符值)。虽然我们的公式包含了训练集中除arr_delay之外的所有变量作为预测变量,但这告诉配方保留这两个变量,但不将它们用作结果或预测变量。
flights_rec <- 
  recipe(arr_delay ~ ., data = train_data) %>% 
  update_role(flight, time_hour, new_role = "ID") 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接