Перекодировать категориальный фактор с N категориями в N двоичных столбцов - программирование
Подтвердить что ты не робот

Перекодировать категориальный фактор с N категориями в N двоичных столбцов

Исходный фрейм данных:

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

Для небольших наборов данных это хорошо, но он становится медленным для гораздо больших наборов данных.

Кто-нибудь знает способ сделать это без использования цикла?

4b9b3361

Ответ 1

Еще лучше с помощью возможностей поиска @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: Это дает matrix. Довольно очевидно, но все же, оберните его as.data.frame(.), если вы хотите data.frame.

Ответ 2

В пакете 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

Параметры model.matrix могут быть полезны, если у вас были разреженные данные и вы хотели использовать Matrix::sparse.model.matrix

Ответ 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

Я использовал factor на "x", чтобы убедиться, что даже в тех случаях, когда в столбце нет, скажем, значений "c", на выходе все равно будет столбец "c", заполненный с нулями.

Ответ 4

Недавно я встретил другой путь. Я заметил, что когда вы запускаете любую из функций контраста с contrasts, установленной на 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

Ответ 5

Только что увиденный закрытый вопрос, который здесь задан, и никто еще не упомянул об использовании пакета dummies:

Вы можете перекодировать переменные с помощью функции dummy.data.frame(), которая построена поверх model.matrix(), но имеет более простой синтаксис, некоторые хорошие параметры и вернет dataframe:

> 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(), чтобы применить ее только к одному столбцу.

Ответ 6

Вот решение для более общего случая, когда количество букв не указано apriori:

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))