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

时间:2013-04-24 19:08:15

标签: r dataframe factors

原始数据框:

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

对于小型数据集,这很好,但对于更大的数据集,它会变慢。

任何人都知道如何在不使用循环的情况下执行此操作?

7 个答案:

答案 0 :(得分: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的{​​{1}}!

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:这给出matrix。相当明显,但如果你想要一个as.data.frame(.),仍然可以用data.frame包裹它。

答案 1 :(得分:8)

插入符号包中有一个功能可以满足您的需要,即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选项可能很有用

答案 2 :(得分: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”列,用零填充。

答案 3 :(得分:3)

我最近遇到了另一种方式。我注意到,当您运行FALSE设置为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

这导致

     string filePath = Path.GetDirectoryName(Application.ExecutablePath);
     string fileName = "sampledata.csv";
     string query = string.Empty;

     //query = "SELECT * FROM " + fileName;
     //query = "SELECT * FROM " + fileName + " WHERE [COLUMN 2] > 1";
     query = "SELECT * FROM " + fileName + " WHERE [COLUMN 3] > 0.02";

     // 32-bit
     // OdbcConnection conn = new OdbcConnection("Driver=Microsoft Text Driver (*.txt, *.csv);Dbq=" + filePath + ";Extensions=csv;");
     // 64-bit
     OdbcConnection conn = new OdbcConnection("Driver=Microsoft Access Text Driver (*.txt, *.csv);Dbq=" + filePath + ";Extensions=csv;");
     conn.Open();
     OdbcCommand cmd = new OdbcCommand(query, conn);
     OdbcDataAdapter adapter = new OdbcDataAdapter(cmd);
     DataSet mydata = new DataSet("CSVData");
     adapter.Fill(mydata);
     conn.Close();
     conn.Dispose();

     dataGridView1.AutoGenerateColumns = true;
     dataGridView1.DataSource = mydata;
     dataGridView1.DataMember = mydata.Tables[0].ToString();

答案 4 :(得分:3)

刚刚看到一个封闭的问题,并且没人提到使用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()功能将其应用于一列。

答案 5 :(得分: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))

答案 6 :(得分: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