在R中如何对多个分类变量进行独热编码

42

我正在处理一个预测问题,在R中构建决策树,我的数据有几个分类变量,我想在训练集和测试集中一致的进行独热编码。我已经成功的在训练集上使用如下代码完成了独热编码:

temps <- X_train
tt <- subset(temps, select = -output)
oh <- data.frame(model.matrix(~ . -1, tt), CLASS = temps$output)

但我找不到在测试集上应用相同编码的方法,我该怎么做?

但是我无法找到一种在我的测试集上应用相同编码的方法,我该怎么做?


1
通过编码,您是指创建虚拟变量吗? - Esteban PS
以相同的方式做。有什么不同吗? - Gregor Thomas
是的,我的意思是创建虚拟变量:对于每个分类变量,我需要创建与变量中不同类别相同数量的虚拟变量。 - xeco
@Gregor 不同的是,一些类别可能出现在测试集中而不在训练集中,虚拟变量的顺序也很重要,并且两个集合的顺序不同。 - xeco
1
@xeco 我建议你在R中寻找vtreat包。 - Rushabh Patel
5个回答

48
我建议使用caret包中的dummyVars函数。
library(caret)

customers <- data.frame(
  id=c(10, 20, 30, 40, 50),
  gender=c('male', 'female', 'female', 'male', 'female'),
  mood=c('happy', 'sad', 'happy', 'sad','happy'),
  outcome=c(1, 1, 0, 0, 0))
customers
id gender  mood outcome
1 10   male happy       1
2 20 female   sad       1
3 30 female happy       0
4 40   male   sad       0
5 50 female happy       0


# dummify the data
dmy <- dummyVars(" ~ .", data = customers)
trsf <- data.frame(predict(dmy, newdata = customers))
trsf
id gender.female gender.male mood.happy mood.sad outcome
1 10             0           1          1        0       1
2 20             1           0          0        1       1
3 30             1           0          1        0       0
4 40             0           1          0        1       0
5 50             1           0          1        0       0

示例 来源

您将相同的步骤应用于训练集和验证集。


11
我发现使用caret方法(带有dummyVars)比使用mltools包中的one_hot()函数快大约73%。使用microbenchmark包和iris数据集,caret方法需要0.025毫秒才能完成,而one_hot()方法需要0.095毫秒才能完成。 - Dale Kube
1
@DaleKube,你的基准测试中是否包含了 data.frame(predict(dmy, newdata = customers))?显然仅有 dummyVars 是无法给出实际虚拟变量的。 - robertspierre
3
如果您有一个包含不同变量的数据框,并且只想对其中一些进行独热编码,您需要使用类似于 dummyVars(" ~ VARIABLE1 + VARIABLE2", data = customers) 的方法。 - robertspierre
1
@raffamaiden 是的,我包括了 predict() 调用和转换为 data.frame。 - Dale Kube
dummyVars(以及其他库的函数)应默认删除一个虚拟变量以避免共线性:https://rdrr.io/cran/caret/man/dummyVars.html - undefined
显示剩余2条评论

31

这里有一个简单的解决方案,可以使用无需任何包来进行类别的独热编码。

解决方案

model.matrix(~0+category)

需要将您的分类变量转换为因子(factor)。在训练和测试数据中,因子水平必须相同,请使用levels(train$category)levels(test$category)进行检查。如果某些水平在测试集中不存在也没有关系。

示例

这是使用鸢尾花数据集的示例。

data(iris)
#Split into train and test sets.
train <- sample(1:nrow(iris),100)
test <- -1*train

iris[test,]

    Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
34           5.5         4.2          1.4         0.2    setosa
106          7.6         3.0          6.6         2.1 virginica
112          6.4         2.7          5.3         1.9 virginica
127          6.2         2.8          4.8         1.8 virginica
132          7.9         3.8          6.4         2.0 virginica

model.matrix()会为因子的每个水平创建一列,即使它在数据中不存在。零表示它不是该级别,一表示它是。添加零指定您不想要拦截器或参考水平,并且等效于-1。

oh_train <- model.matrix(~0+iris[train,'Species'])
oh_test <- model.matrix(~0+iris[test,'Species'])

#Renaming the columns to be more concise.
attr(oh_test, "dimnames")[[2]] <- levels(iris$Species)


  setosa versicolor virginica
1      1          0         0
2      0          0         1
3      0          0         1
4      0          0         1
5      0          0         1

顺便说一句,通常最好在训练和测试数据中包含所有类别。但这不关我的事。


这对于一个变量来说效果很好,但如果有多个分类变量,则不会覆盖每个类别。 - Gregor Thomas
2
请参见 https://dev59.com/8bvoa4cB1Zd3GeqP1lNY,返回已翻译的文本:true。 - Aydin K.

22

代码

library(data.table)
library(mltools)
customers_1h <- one_hot(as.data.table(customers))

结果

> customers_1h
id gender_female gender_male mood_happy mood_sad outcome
1: 10             0           1          1        0       1
2: 20             1           0          0        1       1
3: 30             1           0          1        0       0
4: 40             0           1          0        1       0
5: 50             1           0          1        0       0

数据

customers <- data.frame(
  id=c(10, 20, 30, 40, 50),
  gender=c('male', 'female', 'female', 'male', 'female'),
  mood=c('happy', 'sad', 'happy', 'sad','happy'),
  outcome=c(1, 1, 0, 0, 0))

4

如果您不想使用任何外部包,我有自己的函数:

one_hot_encoding = function(df, columns="season"){
  # create a copy of the original data.frame for not modifying the original
  df = cbind(df)
  # convert the columns to vector in case it is a string
  columns = c(columns)
  # for each variable perform the One hot encoding
  for (column in columns){
    unique_values = sort(unique(df[column])[,column])
    non_reference_values  = unique_values[c(-1)] # the first element is going 
                                                 # to be the reference by default
    for (value in non_reference_values){
      # the new dummy column name
      new_col_name = paste0(column,'.',value)
      # create new dummy column for each value of the non_reference_values
      df[new_col_name] <- with(df, ifelse(df[,column] == value, 1, 0))
    }
    # delete the one hot encoded column
    df[column] = NULL

  }
  return(df)
}

你可以像这样使用它:

df = one_hot_encoding(df, c("season"))

这是最好的答案! - pd441

3

你好,这里是我对同样内容的翻译版本。该函数对所有分类变量进行编码,这些变量需要是'factors'类型,并且删除一个虚拟变量以避免虚拟变量陷阱,并返回一个新的数据框架:

onehotencoder <- function(df_orig) {
  df<-cbind(df_orig)
  df_clmtyp<-data.frame(clmtyp=sapply(df,class))
  df_col_typ<-data.frame(clmnm=colnames(df),clmtyp=df_clmtyp$clmtyp)
  for (rownm in 1:nrow(df_col_typ)) {
    if (df_col_typ[rownm,"clmtyp"]=="factor") {
      clmn_obj<-df[toString(df_col_typ[rownm,"clmnm"])] 
      dummy_matx<-data.frame(model.matrix( ~.-1, data = clmn_obj))
      dummy_matx<-dummy_matx[,c(1,3:ncol(dummy_matx))]
      df[toString(df_col_typ[rownm,"clmnm"])]<-NULL
      df<-cbind(df,dummy_matx)
      df[toString(df_col_typ[rownm,"clmnm"])]<-NULL
    }  }
  return(df)
}

1
由于您删除了一个虚拟变量,所以您可能不应该将函数命名为"onehotencoder"。"one hot"意味着一个虚拟变量始终是"hot",即1或TRUE。 - schotti
“虚拟变量陷阱”我认为是独热编码的完美多重共线性。虚拟变量函数应该自动删除一个变量,就像在dummyVars中一样。 - undefined

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