2011-02-19 10 views
80

Tengo un marco de datos R que contiene un factor que quiero "expandir" para que para cada nivel de factor, haya una columna asociada en un nuevo marco de datos, que contiene un indicador 1/0. Por ejemplo, supongamos que tengo:Expande automáticamente un factor R en una colección de variables de indicador 1/0 para cada nivel de factor

df.original <-data.frame(eggs = c("foo", "foo", "bar", "bar"), ham = c(1,2,3,4)) 

Quiero:

df.desired <- data.frame(foo = c(1,1,0,0), bar=c(0,0,1,1), ham=c(1,2,3,4)) 

Porque para algunos análisis para el que es necesario tener una trama de datos completamente numérico (por ejemplo, análisis de componentes principales), pensé que esta característica podría ser incorporado. Escribir una función para hacer esto no debería ser demasiado difícil, pero puedo prever algunos desafíos relacionados con los nombres de columna y si algo existe ya, preferiría usar eso.

Respuesta

105

utilizar la función model.matrix:

model.matrix(~ Species - 1, data=iris) 
+0

¿Puedo simplemente agregar que este método es mucho más rápido que usar 'cast' para mí. –

+0

@RyanChase, en las 14 horas transcurridas entre la redacción de tu comentario y que yo lo haya notado para responder, podrías haber consultado la página de ayuda '?formula' y encontró la respuesta en el segundo párrafo de la sección de Detalles. O podría haber intentado el código con y sin el "-1" y comparar la salida para ver los efectos. Pero supongo que eres más paciente que yo. El "-1" especifica que no se ajusta a una intersección (hay otras formas también) y, por lo tanto, crea una variable indicadora para cada nivel en lugar de diferencias basadas en contrastes. –

+0

@GregSnow Repasé el segundo párrafo de '? Formula' así como'? Model.matrix', pero no estaba claro (podría ser simplemente mi falta de profundidad de conocimiento en álgebra matricial y formulación de modelos). Después de excavar más, he podido deducir que el -1 solo especifica que no se incluya la columna "interceptar". Si omite el -1, verá una columna de intercepción de 1 en el resultado con una columna binaria omitida. Puede ver qué valores la columna omitida son 1 en función de las filas donde los valores de las otras columnas son 0. La documentación parece críptica: ¿hay otro buen recurso? –

5

probablemente variable ficticia es similar a lo que desea. Entonces, model.matrix es útil:

> with(df.original, data.frame(model.matrix(~eggs+0), ham)) 
    eggsbar eggsfoo ham 
1  0  1 1 
2  0  1 2 
3  1  0 3 
4  1  0 4 
14

Si su trama de datos se hace solamente de factores (o que están trabajando en un subconjunto de variables que son todos factores), también puede utilizar la función de la acm.disjonctifade4 paquete:

R> library(ade4) 
R> df <-data.frame(eggs = c("foo", "foo", "bar", "bar"), ham = c("red","blue","green","red")) 
R> acm.disjonctif(df) 
    eggs.bar eggs.foo ham.blue ham.green ham.red 
1  0  1  0   0  1 
2  0  1  1   0  0 
3  1  0  0   1  0 
4  1  0  0   0  1 
No

exactamente el caso de que se describi ng, pero puede ser útil también ...

+0

Gracias, esto me ayudó mucho ya que usa menos memoria que model.matrix! – Serhiy

+0

Me gusta cómo se nombran las variables; No me gusta que se devuelvan como datos numéricos hambrientos de almacenamiento cuando * deberían * (en mi humilde opinión) simplemente sean lógicos. – dsz

8

Una forma rápida utilizando el paquete reshape2:

require(reshape2) 

> dcast(df.original, ham ~ eggs, length) 

Using ham as value column: use value_var to override. 
    ham bar foo 
1 1 0 1 
2 2 0 1 
3 3 1 0 
4 4 1 0 

Tenga en cuenta que esto produce, precisamente, los nombres de las columnas que desee.

+0

Bueno. Pero ten cuidado con el duplicado de jamón. digamos, d <- data.frame (eggs = c ("foo", "bar", "foo"), ham = c (1,2,1)); dcast (d, ham ~ eggs, length) hace foo = 2. – kohske

+0

@Kohske, cierto, pero suponía que 'ham' es una identificación de fila única. Si 'ham' no es una identificación única, entonces uno debe usar alguna otra identificación única (o crear una ficticia) y usarla en lugar de' ham'. La conversión de una etiqueta categórica a un indicador binario solo tendría sentido para identificadores únicos. –

3

Acabo de encontrar este hilo antiguo y pensé que agregaría una función que utiliza ade4 para tomar un marco de datos que consta de factores y/o datos numéricos y devuelve un marco de datos con factores como códigos ficticios.

dummy <- function(df) { 

    NUM <- function(dataframe)dataframe[,sapply(dataframe,is.numeric)] 
    FAC <- function(dataframe)dataframe[,sapply(dataframe,is.factor)] 

    require(ade4) 
    if (is.null(ncol(NUM(df)))) { 
     DF <- data.frame(NUM(df), acm.disjonctif(FAC(df))) 
     names(DF)[1] <- colnames(df)[which(sapply(df, is.numeric))] 
    } else { 
     DF <- data.frame(NUM(df), acm.disjonctif(FAC(df))) 
    } 
    return(DF) 
} 

Probémoslo.

df <-data.frame(eggs = c("foo", "foo", "bar", "bar"), 
      ham = c("red","blue","green","red"), x=rnorm(4))  
dummy(df) 

df2 <-data.frame(eggs = c("foo", "foo", "bar", "bar"), 
      ham = c("red","blue","green","red")) 
dummy(df2) 
5

Una entrada tardía class.ind del paquete nnet

library(nnet) 
with(df.original, data.frame(class.ind(eggs), ham)) 
    bar foo ham 
1 0 1 1 
2 0 1 2 
3 1 0 3 
4 1 0 4 
0

que necesitaba una función de 'explotar' factores que es un poco más flexible, e hizo que se basa en la función de la acm.disjonctif paquete ade4. Esto le permite elegir los valores desglosados, que son 0 y 1 en acm.disjonctif. Solo explota factores que tienen 'pocos' niveles. Las columnas numéricas se conservan.

# Function to explode factors that are considered to be categorical, 
# i.e., they do not have too many levels. 
# - data: The data.frame in which categorical variables will be exploded. 
# - values: The exploded values for the value being unequal and equal to a level. 
# - max_factor_level_fraction: Maximum number of levels as a fraction of column length. Set to 1 to explode all factors. 
# Inspired by the acm.disjonctif function in the ade4 package. 
explode_factors <- function(data, values = c(-0.8, 0.8), max_factor_level_fraction = 0.2) { 
    exploders <- colnames(data)[sapply(data, function(col){ 
     is.factor(col) && nlevels(col) <= max_factor_level_fraction * length(col) 
    })] 
    if (length(exploders) > 0) { 
    exploded <- lapply(exploders, function(exp){ 
     col <- data[, exp] 
     n <- length(col) 
     dummies <- matrix(values[1], n, length(levels(col))) 
     dummies[(1:n) + n * (unclass(col) - 1)] <- values[2] 
     colnames(dummies) <- paste(exp, levels(col), sep = '_') 
     dummies 
     }) 
    # Only keep numeric data. 
    data <- data[sapply(data, is.numeric)] 
    # Add exploded values. 
    data <- cbind(data, exploded) 
    } 
    return(data) 
} 
0

Aquí hay una manera más clara de hacerlo. Uso model.matrix para crear las variables booleanas ficticias y luego fusionarlas nuevamente en el marco de datos original.

df.original <-data.frame(eggs = c("foo", "foo", "bar", "bar"), ham = c(1,2,3,4)) 
df.original 
# eggs ham 
# 1 foo 1 
# 2 foo 2 
# 3 bar 3 
# 4 bar 4 

# Create the dummy boolean variables using the model.matrix() function. 
> mm <- model.matrix(~eggs-1, df.original) 
> mm 
# eggsbar eggsfoo 
# 1  0  1 
# 2  0  1 
# 3  1  0 
# 4  1  0 
# attr(,"assign") 
# [1] 1 1 
# attr(,"contrasts") 
# attr(,"contrasts")$eggs 
# [1] "contr.treatment" 

# Remove the "eggs" prefix from the column names as the OP desired. 
colnames(mm) <- gsub("eggs","",colnames(mm)) 
mm 
# bar foo 
# 1 0 1 
# 2 0 1 
# 3 1 0 
# 4 1 0 
# attr(,"assign") 
# [1] 1 1 
# attr(,"contrasts") 
# attr(,"contrasts")$eggs 
# [1] "contr.treatment" 

# Combine the matrix back with the original dataframe. 
result <- cbind(df.original, mm) 
result 
# eggs ham bar foo 
# 1 foo 1 0 1 
# 2 foo 2 0 1 
# 3 bar 3 1 0 
# 4 bar 4 1 0 

# At this point, you can select out the columns that you want. 
Cuestiones relacionadas