Espansione automatica di un fattore R in una raccolta di variabili indicatore 1/0 per ogni livello di fattore


108

Ho un frame di dati R contenente un fattore che voglio "espandere" in modo che per ogni livello di fattore, ci sia una colonna associata in un nuovo frame di dati, che contiene un indicatore 1/0. Ad esempio, supponiamo di avere:

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

Voglio:

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

Perché per alcune analisi per le quali è necessario disporre di un data frame completamente numerico (ad esempio, analisi dei componenti principali), ho pensato che questa funzione potesse essere incorporata. Scrivere una funzione per farlo non dovrebbe essere troppo difficile, ma posso prevederne alcuni sfide relative ai nomi delle colonne e se qualcosa esiste già, preferisco usarlo.

Risposte:


131

Usa la model.matrixfunzione:

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

1
Posso solo aggiungere che questo metodo è stato molto più veloce dell'uso castper me.
Matt Weller

3
@GregSnow Ho rivisto il 2 ° paragrafo di ?formulacosì come ?model.matrix, ma non era chiaro (potrebbe essere solo la mia mancanza di conoscenza approfondita in algebra matriciale e formulazione del modello). Dopo aver scavato di più, sono stato in grado di capire che -1 specifica semplicemente di non includere la colonna "intercetta". Se tralasci -1, vedrai una colonna di intercettazione di 1 nell'output con una colonna binaria esclusa. Puoi vedere quali valori la colonna omessa è 1 in base alle righe in cui i valori delle altre colonne sono 0. La documentazione sembra criptica: c'è un'altra buona risorsa?
Ryan Chase

1
@ RyanChase, ci sono molti tutorial online e libri su R / S (molti hanno brevi descrizioni sulla pagina web r-project.org). Il mio apprendimento di S e R è stato piuttosto eclettico (e lungo), quindi non sono il migliore per dare un'opinione su come i libri / tutorial attuali attraggano i principianti. Sono, tuttavia, un fan della sperimentazione. Provare qualcosa in una nuova sessione R può essere molto illuminante e non pericoloso (il peggio che mi è successo è stato un crash di R, e questo raramente, il che porta a miglioramenti in R). Stackoverflow è quindi una buona risorsa per capire cosa è successo.
Greg Snow

7
E se vuoi convertire tutte le colonne dei fattori, puoi usare:model.matrix(~., data=iris)[,-1]
user890739

1
@colin, non completamente automatico, ma puoi usare naresidper reinserire i valori mancanti dopo averli usati na.exclude. Un rapido esempio:tmp <- data.frame(x=factor(c('a','b','c',NA,'a'))); tmp2 <- na.exclude(tmp); tmp3 <- model.matrix( ~x-1, tmp2); tmp4 <- naresid(attr(tmp2,'na.action'), tmp3)
Greg Snow

17

Se il tuo data frame è composto solo da fattori (o stai lavorando su un sottoinsieme di variabili che sono tutti fattori), puoi anche usare la acm.disjonctiffunzione dal ade4pacchetto:

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

Non esattamente il caso che stai descrivendo, ma può anche essere utile ...


Grazie, questo mi ha aiutato molto in quanto utilizza meno memoria di model.matrix!
Serhiy

Mi piace il modo in cui le variabili vengono denominate; Non mi piace che vengano restituiti come numerici affamati di spazio di archiviazione quando dovrebbero (IMHO) essere solo logici.
dsz

9

Un modo rapido per utilizzare il reshape2pacchetto:

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

Nota che questo produce esattamente i nomi delle colonne che desideri.


Buona. Ma attenzione al duplicato del prosciutto. diciamo, d <- data.frame (eggs = c ("foo", "bar", "foo"), ham = c (1,2,1)); dcast (d, ham ~ eggs, length) rende foo = 2.
kohske

@Kohske, vero, ma pensavo fosse hamun ID riga univoco. Se hamnon è un ID univoco, è necessario utilizzare un altro ID univoco (o crearne uno fittizio) e utilizzarlo al posto di ham. La conversione di un'etichetta categoriale in un indicatore binario avrebbe senso solo per ID univoci.
Prasad Chalasani

6

probabilmente la variabile fittizia è simile a quella che desideri. Quindi, model.matrix è utile:

> 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

6

Un'entrata in ritardo class.inddal nnetpacchetto

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

4

Mi sono appena imbattuto in questo vecchio thread e ho pensato di aggiungere una funzione che utilizza ade4 per prendere un dataframe composto da fattori e / o dati numerici e restituisce un dataframe con fattori come codici fittizi.

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

Proviamolo.

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)

3

Ecco un modo più chiaro per farlo. Uso model.matrix per creare le variabili booleane fittizie e quindi unirle di nuovo nel dataframe originale.

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.

0

Avevo bisogno di una funzione per "esplodere" i fattori che fosse un po 'più flessibile e ne ho creata una basata sulla funzione acm.disjonctif dal pacchetto ade4. Ciò consente di scegliere i valori esplosi, che sono 0 e 1 in acm.disjonctif. Esplode solo fattori che hanno "pochi" livelli. Le colonne numeriche vengono conservate.

# 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)
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.