Recodificar el factor categórico con N categorías en N columnas binarias

Marco de datos original:

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 aC
 2 aa
 3 cc
 4 ba
 5 cc
 6 cb
 7 aa
 8 ab
 9 ac
 10 ab

Nuevo dataframe:

 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

Para conjuntos de datos pequeños, esto está bien, pero se vuelve lento para conjuntos de datos mucho más grandes.

¿Alguien sabe de una manera de hacer esto sin usar bucle?

Mejor aún con la ayuda de las capacidades de búsqueda de @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 

Creo que esto es lo que estás buscando. Me encantaría eliminar si no es así. ¡Gracias a @ G.Grothendieck (una vez más) por el excelente uso de 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 

Nota: Su salida es justa:

 with(df, model.matrix(~ v2 + 0)) 

Nota 2: Esto da una matrix . Bastante obvio, pero aún así, envuélvalo con as.data.frame(.) Si desea un data.frame .

Hay una función en el paquete de caret que hace lo que necesita, dummyVars. Aquí está el ejemplo de su uso tomado de la documentación de los autores: 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 

Las opciones de model.matrix pueden ser útiles en caso de que tenga datos dispersos y desee utilizar Matrix::sparse.model.matrix

Un enfoque bastante directo es simplemente usar la table en cada columna, tabulando los valores en la columna por el número de filas en el data.frame :

 allLevels <- levels(factor(unlist(df))) do.call(cbind, lapply(df, function(x) table(sequence(nrow(df)), factor(x, levels = allLevels)))) # abcabc # 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 

Utilicé factor en "x" para asegurarme de que incluso en los casos en que hay, por ejemplo, valores "c" en una columna, todavía haya una columna "c" en la salida, llena de ceros.

Recientemente me encontré con otra forma. Noté que cuando ejecuta cualquiera de las funciones de contrasts con contrasts configurados en FALSE , le da una encoding en caliente. Por ejemplo, contr.sum(5, contrasts = FALSE) da

  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 

Para obtener este comportamiento para todos sus factores, puede crear una nueva función de contraste y configurarla como predeterminada. Por ejemplo,

 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) 

Esto resulta en

  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 

Acabo de ver una pregunta cerrada dirigida aquí, y nadie ha mencionado aún el uso del paquete dummies :

Puede recodificar sus variables utilizando la función dummy.data.frame() que se construye sobre model.matrix() pero tiene una syntax más sencilla, algunas buenas opciones y devolverá un 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 

Algunos aspectos agradables de esta función es que puede especificar fácilmente delímetro para los nuevos nombres ( sep= ), omitir las variables no codificadas ( all=F ) y viene con su propia opción dummy.classes que le permite especificar qué clases de columna deberían estar codificado

También puede simplemente usar la función dummy() para aplicar esto a solo una columna.

Aquí hay una solución para un caso más general, cuando la cantidad de letras no se especifica a priori:

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

Esta función toma un vector de caracteres y lo vuelve a codificar en valores binarios. Para procesar todas las variables en df :

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