Перекодировать категориальный фактор с 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
Для небольших наборов данных это хорошо, но он становится медленным для гораздо больших наборов данных.
Кто-нибудь знает способ сделать это без использования цикла?
Ответы
Ответ 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))