2013-04-24 21 views
15

originale cornice di dati: Recode fattore categorico con N categorie in N colonne binarie

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 

Nuovo telaio dati:

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 

Per piccoli gruppi di dati questo va bene, ma diventa sl ow per dataset molto più grandi.

Qualcuno sa di un modo per farlo senza utilizzare il loop?

+1

Il tuo primo frame di dati aveva due variabili, ma sembra che tu abbia convertito solo il secondo. Puoi chiarirlo un po '? – joran

+0

stai sovrascrivendo i tuoi dati. Dovrebbe avere 6 colonne in uscita. – Arun

+0

Scusate, è stato un errore da parte mia - l'ho risolto nel codice sopra. Ci dovrebbero essere tre nuove colonne per ogni colonna originale nell'esempio sopra. Grazie per averlo capito! –

risposta

21

Ancora meglio con l'aiuto di @ di AnandaMahto funzionalità di ricerca,

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 

credo che questo è quello che stai cercando. Sarei felice di cancellare se non è così. Grazie a @ G.Grothendieck (ancora una volta) per lo excellent usage di 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: L'output è solo:

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

Nota 2: Questo dà una matrix. Abbastanza ovvio, ma ancora, avvolgere con as.data.frame(.) se si desidera un data.frame.

0

Qui è una soluzione per il caso più generale, quando la quantità di lettere non è specificato 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) 
} 

Questa funzione prende un vettore di caratteri e ricodifica in valori binari. Per elaborare tutte le variabili in df:

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

Un approccio abbastanza diretto è usare solo table su ciascuna colonna, tabulare i valori nella colonna per il numero di righe nella 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 

I abbiamo usato factor su "x" per assicurarmi che anche nei casi in cui ci siano, diciamo, nessun valore "c" in una colonna, ci sarà ancora una colonna "c" nell'output, piena di zeri.

8

C'è una funzione nel pacchetto di caret che fa quello che ti serve, dummyVars. Ecco l'esempio di utilizzo E 'preso dalla documentazione autori: 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 

Le opzioni model.matrix potrebbero essere utili nel caso in cui aveste dati sparsi e voleva utilizzare Matrix::sparse.model.matrix

2

ho recentemente imbattuto un altro modo.Ho notato che quando si esegue una delle funzioni di contrasto con contrasts impostato su FALSE, si ottiene una codifica a caldo. Ad esempio, 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 

Per ottenere questo comportamento per tutti i fattori, è possibile creare una nuova funzione di contrasto e impostarlo come predefinito. Ad esempio,

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) 

Questo si traduce in

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 
1

appena visto una domanda chiusa diretto a qui, e nessuno ha parlato usando ancora il pacchetto dummies:

È possibile ricodificare le variabili utilizzando il La funzione dummy.data.frame() che è costruita sopra model.matrix() ma ha una sintassi più semplice, alcune buone opzioni e restituirà 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 

Alcune belle aspetti di questa funzione è che si può facilmente specificare delimitatore per i nuovi nomi (sep=), omettere le variabili non codificato (all=F) e viene fornito con una propria opzione dummy.classes che consente di specificare quali classi di colonna dovrebbe essere codificato.

È anche possibile utilizzare la funzione dummy() per applicarla a una sola colonna.