【问题标题】:Avoid failing when a factor has new levels in test set当一个因素在测试集中有新的水平时,避免失败
【发布时间】:2017-10-13 06:40:45
【问题描述】:

我有一个数据集,我按以下方式将其拆分为训练和测试子集:

train_ind <- sample(seq_len(nrow(dataset)), size=(2/3)*nrow(dataset))
train <- dataset[train_ind]
test <- dataset[-train_ind]

然后,我用它来训练一个 glm:

glm.res <- glm(response ~ ., data=dataset, subset=train_ind, family = binomial(link=logit)) 

最后,我用它来预测我的测试集:

preds <- predict(glm.res, test, type="response")

根据示例,这会失败并出现错误:

model.frame.default 中的错误(条款,新数据,na.action = na.action, xlev = 对象$xlevels) : 因子有新的水平

请注意,该值出现在完整数据集上,但显然不在训练集上。我想做的是让预测函数忽略这些新因素。即使它对因子执行了二值化,我也不明白为什么它可以假设新值(因此,不是线性模型中的变量)只是 0,这将产生正确的行为。

有没有办法做到这一点?

【问题讨论】:

  • 不公平地说,模型假设新值应该被视为“0”是正确的行为。例如。假设您有一个预测变量“眼睛颜色”,并且您的模型是根据仅包含“棕色”和“蓝色”的数据进行训练的。如果此变量使用 brown = 0 和 blue = 1 编码,并且您的测试数据现在包含有绿眼睛的人,则根据您的建议将此行视为“0”将假定他们有棕色眼睛。更一般地说,让模型对它在训练期间从未接触过的事物进行预测是没有意义的,这就是你得到错误的原因
  • 我的问题中有一个隐含的假设(诚然,我应该澄清一下)值 0 表示“缺失”或“空”值。另请注意,我不是在问如何强制模型(即 glm 函数)处理它以前从未见过的数据,而是我作为程序员如何解决 R 中可能在现实中发生的问题场景。
  • 我坚持我原来的帖子。我认为在 R 中用零编码缺失值是没有意义的——它们要么被视为数字变量的“真”零(一个坏主意),要么被视为它们自己的因子水平。这里可能的例外是,如果您想将所有缺失值建模为它们自己的类别。
  • @jruf003 我同意你的看法。我的评论只是为了强调,尽管存在这种不一致,但我的问题的目的仍然有效。我在问这个问题时对值 0 做了一些隐含的假设,这对于我当时正在从事的任何项目都是有意义的(不幸的是,我不记得了!)。可能是我的意思是“N/A”而不是 0,或者在我的特定用例中,数值零是 N/As 的一个很好的猜测:例如,0 是当您的数据为居中和缩放,这对于 N/As 来说或多或少是一种明智的处理方式(即使不是最理想的)。

标签: r glm


【解决方案1】:

我从以下数据生成过程开始(一个二元响应变量、一个数值自变量和 3 个分类自变量):

set.seed(1)
n <- 500
y <- factor(rbinom(n, size=1, p=0.7))
x1 <- rnorm(n)
x2 <- cut(runif(n), breaks=seq(0,1,0.2))
x3 <- cut(runif(n), breaks=seq(0,1,0.25))
x4 <- cut(runif(n), breaks=seq(0,1,0.1))
df <- data.frame(y, x1, x2, x3, x4)

在这里,我构建训练和测试集的方式是在测试集中拥有一些类别协变量(x2x3),而类别比训练集中的类别多:

idx <- which(df$x2!="(0.6,0.8]" & df$x3!="(0,0.25]")
train_ind <- sample(idx, size=(2/3)*length(idx))
train <- df[train_ind,]
train$x2 <- droplevels(train$x2)
train$x3 <- droplevels(train$x3)
test <- df[-train_ind,]

table(train$x2)
(0,0.2] (0.2,0.4] (0.4,0.6]   (0.8,1] 
     55        40        53        49 

table(test$x2)
(0,0.2] (0.2,0.4] (0.4,0.6] (0.6,0.8]   (0.8,1] 
     58        48        45        90        62 

table(train$x3)
(0.25,0.5] (0.5,0.75]   (0.75,1] 
        66         61         70 

table(test$x3)
(0,0.25] (0.25,0.5] (0.5,0.75]   (0.75,1] 
     131         63         47         62 

当然,predict 会产生上面由@Setzer22 描述的消息错误:

glm.res <- glm(y ~ ., data=train, family = binomial(link=logit)) 
preds <- predict(glm.res, test, type="response")

model.frame.default 中的错误(条款,新数据,na.action = na.action, xlev = object$xlevels) : 因子 x2 有新的水平 (0.6,0.8]

这是一种(不优雅的)删除train 行的方法,这些行在协变量中有新级别:

dropcats <- function(k) {
   xtst <- test[,k]
   xtrn <- train[,k]
   cmp.tst.trn <- (unique(xtst) %in% unique(xtrn))
   if (is.factor(xtst) & any(!cmp.tst.trn)) {
      cat.tst <- unique(xtst)
      apply(test[,k]==matrix(rep(cat.tst[cmp.tst.trn],each=nrow(test)),
                      nrow=nrow(test)),1,any)
   } else {
      rep(TRUE,nrow(test))
   }
}   
filt <- apply(sapply(2:ncol(df),dropcats),1,all)
subset.test <- test[filt,]

在测试集x2x3 的子集subset.test 中没有新类别:

table(subset.test[,"x2"])
  (0,0.2] (0.2,0.4] (0.4,0.6] (0.6,0.8]   (0.8,1] 
       26        25        20         0        28

table(subset.test[,"x3"])
  (0,0.25] (0.25,0.5] (0.5,0.75]   (0.75,1] 
         0         29         29         41 

现在predict 工作得很好:

preds <- predict(glm.res, subset(test,filt), type="response")
head(preds)

       30        39        41        49        55        56 
0.7732564 0.8361226 0.7576259 0.5589563 0.8965357 0.8058025

希望对你有帮助。

【讨论】:

  • 感谢您的回答!我发现在 R 中似乎没有简单的方法来做到这一点令人沮丧。在我看来,实现应该涵盖的基本边缘情况,并且解决方案很简单。有什么我没有考虑到的吗?为什么它不能忽略任何新值?
猜你喜欢
  • 1970-01-01
  • 2020-08-11
  • 2021-02-21
  • 2020-09-29
  • 2021-10-31
  • 2017-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多