将具有N个类别因子的数据重编码为N个二进制列

19

原始数据框:

v1 = sample(letters[1:3], 10, replace=TRUE)
v2 = sample(letters[1:3], 10, replace=TRUE)
df = data.frame(v1,v2)
df
   v1 v2
1   b  c
2   a  a
3   c  c
4   b  a
5   c  c
6   c  b
7   a  a
8   a  b
9   a  c
10  a  b

新数据框:

new_df = data.frame(row.names=rownames(df))
for (i in colnames(df)) {
    for (x in letters[1:3]) {
        #new_df[x] = as.numeric(df[i] == x)
        new_df[paste0(i, "_", x)] = as.numeric(df[i] == x)
    }
}
   v1_a v1_b v1_c v2_a v2_b v2_c
1     0    1    0    0    0    1
2     1    0    0    1    0    0
3     0    0    1    0    0    1
4     0    1    0    1    0    0
5     0    0    1    0    0    1
6     0    0    1    0    1    0
7     1    0    0    1    0    0
8     1    0    0    0    1    0
9     1    0    0    0    0    1
10    1    0    0    0    1    0

对于小型数据集,这样做是可以的,但对于更大的数据集来说,效率会变得很慢。

有人知道一种不使用循环的方法吗?


1
你的第一个数据框有两个变量,但看起来你只转换了第二个变量。你能再解释一下吗? - joran
你正在覆盖你的数据。输出应该有6列。 - Arun
抱歉,那是我的错误——我已经在上面的代码中修复了它。在上面的示例中,每个原始列应该有三个新列。感谢你的指出! - Keith Hughitt
@Keith,你有检查过已发布的答案吗? - Arun
1
@Arun 完成了。有很多有用的解决方案。我感激每个人的贡献! - Keith Hughitt
7个回答

24
更好的是,在 @AnandaMahto 的搜索能力的帮助下,
model.matrix(~ . + 0, data=df, contrasts.arg = lapply(df, contrasts, contrasts=FALSE))
#    v1a v1b v1c v2a v2b v2c
# 1    0   1   0   0   0   1
# 2    1   0   0   1   0   0
# 3    0   0   1   0   0   1
# 4    0   1   0   1   0   0
# 5    0   0   1   0   0   1
# 6    0   0   1   0   1   0
# 7    1   0   0   1   0   0
# 8    1   0   0   0   1   0
# 9    1   0   0   0   0   1
# 10   1   0   0   0   1   0

我认为这就是你要找的内容。如果不是,我很乐意删除。感谢 @G.Grothendieck(再次)对 model.matrix出色使用

cbind(with(df, model.matrix(~ v1 + 0)), with(df, model.matrix(~ v2 + 0)))
#    v1a v1b v1c v2a v2b v2c
# 1    0   1   0   0   0   1
# 2    1   0   0   1   0   0
# 3    0   0   1   0   0   1
# 4    0   1   0   1   0   0
# 5    0   0   1   0   0   1
# 6    0   0   1   0   1   0
# 7    1   0   0   1   0   0
# 8    1   0   0   0   1   0
# 9    1   0   0   0   0   1
# 10   1   0   0   0   1   0

注意:你的输出只是:
with(df, model.matrix(~ v2 + 0))

注意2:这会给出一个矩阵。相当明显,但是如果你想要一个数据框,请使用as.data.frame(.)将其包装起来。

9

caret包中有一个函数dummyVars,可以实现你需要的功能。以下是作者文档中对该函数使用的示例:http://topepo.github.io/caret/preprocess.html

library(earth)
data(etitanic)

dummies <- caret::dummyVars(survived ~ ., data = etitanic)
head(predict(dummies, newdata = etitanic))

  pclass.1st pclass.2nd pclass.3rd sex.female sex.male     age sibsp parch
1          1          0          0          1        0 29.0000     0     0
2          1          0          0          0        1  0.9167     1     2
3          1          0          0          1        0  2.0000     1     2
4          1          0          0          0        1 30.0000     1     2
5          1          0          0          1        0 25.0000     1     2
6          1          0          0          0        1 48.0000     0     0

如果您有稀疏数据并希望使用Matrix::sparse.model.matrix,那么model.matrix选项可能很有用。


4

我看到有一个关闭的问题指向这里,但是没有人提到使用 dummies 包:

你可以使用 dummy.data.frame() 函数重新编码变量,它是建立在 model.matrix() 之上的,但具有更简单的语法、一些不错的选项,并且会返回一个数据框:

> dummy.data.frame(df, sep="_")
   v1_a v1_b v1_c v2_a v2_b v2_c
1     0    1    0    0    0    1
2     1    0    0    1    0    0
3     0    0    1    0    0    1
4     0    1    0    1    0    0
5     0    0    1    0    0    1
6     0    0    1    0    1    0
7     1    0    0    1    0    0
8     1    0    0    0    1    0
9     1    0    0    0    0    1
10    1    0    0    0    1    0

这个函数的一些优点是,你可以很容易地指定新名称的分隔符(sep=),省略未编码的变量(all=F)并且它自带选项dummy.classes,允许你指定应编码哪些列的类别。
你也可以只使用dummy()函数来将其应用于单个列。

3

最近我发现了另一种方法。我注意到,当你运行任何对比函数,并将 contrasts 设置为 FALSE 时,它会给出 one-hot 编码。例如,contr.sum(5, contrasts = FALSE) 就会给出:

  1 2 3 4 5
1 1 0 0 0 0
2 0 1 0 0 0
3 0 0 1 0 0
4 0 0 0 1 0
5 0 0 0 0 1

为了使所有的因素都具有这种行为,您可以创建一个新的对比度函数并将其设置为默认值。例如:
contr.onehot = function (n, contrasts, sparse = FALSE) {
  contr.sum(n = n, contrasts = FALSE, sparse = sparse)
}

options(contrasts = c("contr.onehot", "contr.onehot"))
model.matrix(~ . - 1, data = df)

这会导致:
   v1a v1b v1c v2a v2b v2c
1    0   0   1   0   0   1
2    0   1   0   1   0   0
3    0   0   1   0   1   0
4    1   0   0   0   1   0
5    0   1   0   0   1   0
6    0   1   0   0   0   1
7    1   0   0   0   1   0
8    0   1   0   0   1   0
9    0   1   0   1   0   0
10   0   0   1   0   0   1

3

一种相对直接的方法是为每个列使用 table 函数,通过data.frame中的行数制表该列的值:

allLevels <- levels(factor(unlist(df)))
do.call(cbind, 
        lapply(df, function(x) table(sequence(nrow(df)), 
                                     factor(x, levels = allLevels))))
#    a b c a b c
# 1  0 1 0 0 0 1
# 2  1 0 0 1 0 0
# 3  0 0 1 0 0 1
# 4  0 1 0 1 0 0
# 5  0 0 1 0 0 1
# 6  0 0 1 0 1 0
# 7  1 0 0 1 0 0
# 8  1 0 0 0 1 0
# 9  1 0 0 0 0 1
# 10 1 0 0 0 1 0

我已经在"x"上使用了factor,以确保即使在某些列中没有"c"值的情况下,输出中仍然会有一个"c"列,其中填充了零。


0

这里提供了一种更通用的解决方案,当字母数量未事先指定时:

convertABC <- function(x) {

    hold <- rep(0,max(match(as.matrix(df),letters))) # pre-format output

    codify <- function(x) {                          # define function for single char

        output <- hold                               # take empty vector
        output[match(x,letters)] <- 1                # place 1 according to letter pos
        return(output)
    }

    to.return <- t(sapply(as.character(x),codify))   # apply it to whole vector
    rownames(to.return) <- 1:nrow(to.return)         # nice rownames
    colnames(to.return) <- do.call(c,list(letters[1:max(match(as.matrix(df),letters))])) # nice columnnames
    return(to.return)
}

该函数接受一个字符向量,并将其重新编码为二进制值。要处理 df 中的所有变量:

do.call(cbind,lapply(df,convertABC))

0
library(correlationfunnel)
library(dplyr)
v1 = sample(letters[1:3], 10, replace=TRUE)
v2 = sample(letters[1:3], 10, replace=TRUE)
df = data.frame(v1,v2)
df

   v1 v2
1   b  c
2   c  c
3   c  a
4   c  c
5   a  a
6   b  b
7   b  c
8   b  c
9   c  a
10  b  c

df$id= 1:nrow(df)
df %>%
   select(-id) %>%
   binarize()

# A tibble: 10 x 6
   v1__a v1__b v1__c v2__a v2__b v2__c
   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1     0     1     0     0     0     1
 2     0     0     1     0     0     1
 3     0     0     1     1     0     0
 4     0     0     1     0     0     1
 5     1     0     0     1     0     0
 6     0     1     0     0     1     0
 7     0     1     0     0     0     1
 8     0     1     0     0     0     1
 9     0     0     1     1     0     0
10     0     1     0     0     0     1

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