2012-05-03 14 views
17

Mi imbatto in questo abbastanza spesso che immagino ci debba essere un buon idioma per questo. Supponiamo di avere un data.frame con una serie di attributi, incluso "prodotto". Ho anche una chiave che traduce i prodotti in brand + size. I codici prodotto 1-3 sono Tylenol, 4-6 sono Advil, 7-9 Bayer, 10-12 sono generici.Idiom per la ricodifica in stile ifelse per più categorie

Qual è il modo più veloce (in termini di tempo umano) di codificarlo?

Io tendo ad usare nidificato ifelse se ci sono 3 o meno categorie e digitare la tabella di dati e unirla in se ci sono più di 3. Qualche idea migliore? Stata ha uno recode command che è abbastanza elegante per questo genere di cose, anche se credo che promuova un po 'troppo il codice dei dati.

dat <- structure(list(product = c(11L, 11L, 9L, 9L, 6L, 1L, 11L, 5L, 
7L, 11L, 5L, 11L, 4L, 3L, 10L, 7L, 10L, 5L, 9L, 8L)), .Names = "product", row.names = c(NA, 
-20L), class = "data.frame") 
+1

Un sacco di creatività SO in mostra qui. Avere difficoltà a scegliere una risposta. –

risposta

14

Si potrebbe utilizzare una lista come un array associativo per definire la mappatura brand -> product code, vale a dire:

brands <- list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12) 

Una volta fatto questo, si può allora o invertire questo per creare una lista product code -> brand (potrebbe richiedere un sacco di memoria), o semplicemente usare una funzione di ricerca:

find.key <- function(x, li, default=NA) { 
    ret <- rep.int(default, length(x)) 
    for (key in names(li)) { 
     ret[x %in% li[[key]]] <- key 
    } 
    return(ret) 
} 

sono sicuro che ci sono modi migliori per scrivere questa funzione (la for l oop mi sta annoiando!), ma almeno è vettorializzato, quindi richiede solo un singolo passaggio nell'elenco.

Uso sarebbe qualcosa di simile:

> dat$brand <- find.key(dat$product, brands) 
> dat 
    product brand 
1  11 Generic 
2  11 Generic 
3  9 Bayer 
4  9 Bayer 
5  6 Advil 
6  1 Tylenol 
7  11 Generic 
8  5 Advil 
9  7 Bayer 
10  11 Generic 
11  5 Advil 
12  11 Generic 
13  4 Advil 
14  3 Tylenol 
15  10 Generic 
16  7 Bayer 
17  10 Generic 
18  5 Advil 
19  9 Bayer 
20  8 Bayer 

Il recode e levels<- soluzioni sono molto belle, ma sono anche molto più lento rispetto a questo (e una volta che hai find.key questo è più facile-per-gli esseri umani che recode e alla pari con il levels<-):

> microbenchmark(
    recode=recode(dat$product,recodes="1:3='Tylenol';4:6='Advil';7:9='Bayer';10:12='Generic'"), 
    find.key=find.key(dat$product, brands), 
    levels=`levels<-`(factor(dat$product),brands)) 
Unit: microseconds 
     expr  min  lq median  uq  max 
1 find.key 64.325 69.9815 76.8950 83.8445 221.748 
2 levels 240.535 248.1470 274.7565 306.8490 1477.707 
3 recode 1636.039 1683.4275 1730.8170 1855.8320 3095.938 

(non riesco a ottenere la versione switch al benchmark correttamente, ma sembra essere più veloce di tutti di quanto sopra, anche se è ancora peggio-per-gli esseri umani rispetto alla soluzione di recode)

+0

Una soluzione divertente, ma sicuramente non passa la raccolta più veloce per l'uomo! –

+0

Perché no? 'find.key' è una funzione generica che puoi semplicemente copiare incolla nel tuo codice e usarla. – huon

+0

La versione più recente sembra molto facile da usare. Questa versione non ha: 'cbind (dat, dat $ brand brand = find <- find.key (dat $ product, brands))'. Ma ora che effettivamente lo guardo, non è complicato. Stupidità mattutina :-) –

3

Un po 'più leggibile rispetto nidificate s ifelse':.

unlist(lapply(as.character(dat$product), switch, 
       `1`=,`2`=,`3`='tylenol', 
       `4`=,`5`=,`6`='advil', 
       `7`=,`8`=,`9`='bayer', 
       `10`=,`11`=,`12`='generic')) 

Caveat: non molto efficiente.

+1

+1 Non molto efficiente ma un po 'divertente. –

12

mi piace la funzione recode nel pacchetto car:

library(car) 

dat$brand <- recode(dat$product, 
    recodes="1:3='Tylenol';4:6='Advil';7:9='Bayer';10:12='Generic'") 

# > dat 
# product brand 
# 1  11 Generic 
# 2  11 Generic 
# 3  9 Bayer 
# 4  9 Bayer 
# 5  6 Advil 
# 6  1 Tylenol 
# 7  11 Generic 
# 8  5 Advil 
# 9  7 Bayer 
# 10  11 Generic 
# 11  5 Advil 
# 12  11 Generic 
# 13  4 Advil 
# 14  3 Tylenol 
# 15  10 Generic 
# 16  7 Bayer 
# 17  10 Generic 
# 18  5 Advil 
# 19  9 Bayer 
# 20  8 Bayer 
+8

l'unico problema con 'recode' è che funziona elaborando le stringhe, quindi se i tuoi codici/dati hanno il punto e virgola e = i segni in essi è un gran mal di testa ... –

19

si potrebbe convertire la variabile ad un fattore e cambiare i suoi livelli da levels<- funzione. In un comando potrebbe essere come:

`levels<-`(
    factor(dat$product), 
    list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12) 
) 

Nei passaggi:

brands <- factor(dat$product) 
levels(brands) <- list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12) 
+0

Questo è di gran lunga il modo più semplice, anche se il tuo la prima chiamata ai 'livelli <-' probabilmente confonderà molte persone. :) –

+2

Bella scorciatoia! Ho trovato la sua spiegazione qui: [link] (http://stackoverflow.com/q/10449366/1460352) – nassimhddd

6

Questo richiede un po 'di battitura, ma se si hanno davvero un enorme set di dati questo può essere la strada da percorrere. Bryangoodrich e Dason di talkstats.com mi hanno insegnato questo. Sta usando una tabella hash o creando un ambiente che contiene una tabella di ricerca. Lo tengo davvero sul mio.Rprofile (la funzione di hash che è) per le ricerche di tipo di dizionario.

Ho replicato i dati 1000 volte per renderlo un po 'più grande.

################################################# 
# THE HASH FUNCTION (CREATES A ENW ENVIRONMENT) # 
################################################# 
hash <- function(x, type = "character") { 
    e <- new.env(hash = TRUE, size = nrow(x), parent = emptyenv()) 
    char <- function(col) assign(col[1], as.character(col[2]), envir = e) 
    num <- function(col) assign(col[1], as.numeric(col[2]), envir = e) 
    FUN <- if(type=="character") char else num 
    apply(x, 1, FUN) 
    return(e) 
} 
################################### 
# YOUR DATA REPLICATED 1000 TIMES # 
################################### 
dat <- dat <- structure(list(product = c(11L, 11L, 9L, 9L, 6L, 1L, 11L, 5L, 
    7L, 11L, 5L, 11L, 4L, 3L, 10L, 7L, 10L, 5L, 9L, 8L)), .Names = "product", row.names = c(NA, 
    -20L), class = "data.frame") 
dat <- dat[rep(seq_len(nrow(dat)), 1000), , drop=FALSE] 
rownames(dat) <-NULL 
dat 
######################### 
# CREATE A LOOKUP TABLE # 
######################### 
med.lookup <- data.frame(val=as.character(1:12), 
    med=rep(c('Tylenol', 'Advil', 'Bayer', 'Generic'), each=3)) 

######################################## 
# USE hash TO CREATE A ENW ENVIRONMENT # 
######################################## 
meds <- hash(med.lookup) 

############################## 
# CREATE A RECODING FUNCTION # 
##############################   
recoder <- function(x){ 
    x <- as.character(x) #turn the numbers to character 
    rc <- function(x){ 
     if(exists(x, env = meds))get(x, e = meds) else NA 
    } 
    sapply(x, rc, USE.NAMES = FALSE) 
} 
############# 
# HASH AWAY # 
############# 
recoder(dat[, 1])  

In questo caso l'hashing è lento, ma se avete più livelli per ricodificare allora aumenterà in termini di velocità rispetto ad altri.

7

Mi capita spesso di utilizzare la tecnica di seguito:

key <- c() 
key[1:3] <- "Tylenol" 
key[4:6] <- "Advil" 
key[7:9] <- "Bayer" 
key[10:12] <- "Generic" 

Poi,

> key[dat$product] 
[1] "Generic" "Generic" "Bayer" "Bayer" "Advil" "Tylenol" "Generic" "Advil" "Bayer" "Generic" 
[11] "Advil" "Generic" "Advil" "Tylenol" "Generic" "Bayer" "Generic" "Advil" "Bayer" "Bayer" 
7

L ' "approccio database" è quello di mantenere una tabella separata (un data.frame) per le chiavi di prodotto definizioni. Rende ancora più senso dal momento che dici le tue chiavi di prodotto si traducono in non è solo un marchio, ma anche una dimensione:

product.keys <- read.table(textConnection(" 

product brand size 
1  Tylenol small 
2  Tylenol medium 
3  Tylenol large 
4  Advil small 
5  Advil medium 
6  Advil large 
7  Bayer small 
8  Bayer medium 
9  Bayer large 
10  Generic small 
11  Generic medium 
12  Generic large 

"), header = TRUE) 

Quindi, è possibile unire i dati utilizzando merge:

merge(dat, product.keys, by = "product") 
# product brand size 
# 1  1 Tylenol small 
# 2  3 Tylenol large 
# 3  4 Advil small 
# 4  5 Advil medium 
# 5  5 Advil medium 
# 6  5 Advil medium 
# 7  6 Advil large 
# 8  7 Bayer small 
# 9  7 Bayer small 
# 10  8 Bayer medium 
# 11  9 Bayer large 
# 12  9 Bayer large 
# 13  9 Bayer large 
# 14  10 Generic small 
# 15  10 Generic small 
# 16  11 Generic medium 
# 17  11 Generic medium 
# 18  11 Generic medium 
# 19  11 Generic medium 
# 20  11 Generic medium 

Come si nota , l'ordine delle righe non viene mantenuto da merge. Se questo è un problema, il pacchetto plyr ha una funzione join che fa mantenere l'ordine:

library(plyr) 
join(dat, product.keys, by = "product") 
# product brand size 
# 1  11 Generic medium 
# 2  11 Generic medium 
# 3  9 Bayer large 
# 4  9 Bayer large 
# 5  6 Advil large 
# 6  1 Tylenol small 
# 7  11 Generic medium 
# 8  5 Advil medium 
# 9  7 Bayer small 
# 10  11 Generic medium 
# 11  5 Advil medium 
# 12  11 Generic medium 
# 13  4 Advil small 
# 14  3 Tylenol large 
# 15  10 Generic small 
# 16  7 Bayer small 
# 17  10 Generic small 
# 18  5 Advil medium 
# 19  9 Bayer large 
# 20  8 Bayer medium 

Infine, se le tabelle sono di grandi dimensioni e la velocità è un problema, è possibile utilizzare data.tables (dal pacchetto data.table) invece di data.frames.

+0

Non esiste un'opzione ', sort = FALSE' per l'unione che conserva l'ordine delle righe? –

1

Se si dispone di codici a gruppi sequenziali come nell'esempio, questo può cut la senape:

cut(dat$product,seq(0,12,by=3),labels=c("Tylenol","Advil","Bayer","Generic")) 
[1] Generic Generic Bayer Bayer Advil Tylenol Generic Advil Bayer 
[10] Generic Advil Generic Advil Tylenol Generic Bayer Generic Advil 
[19] Bayer Bayer 
Levels: Tylenol Advil Bayer Generic 
2

Io tendo ad usare questa funzione:

recoder <- function (x, from = c(), to = c()) { 
    missing.levels <- unique(x) 
    missing.levels <- missing.levels[!missing.levels %in% from] 
    if (length(missing.levels) > 0) { 
    from <- append(x = from, values = missing.levels) 
    to <- append(x = to, values = missing.levels) 
    } 
    to[match(x, from)] 
} 

Come in:

recoder(x = dat$product, from = 1:12, to = c(rep("Product1", 3), rep("Product2", 3), rep("Product3", 3), rep("Product4", 3))) 
0

C'è anche arules:discretize, ma mi piace meno perché ti fa separare e le etichette della gamma di valori:

library(arules) 
discretize(dat$product, method = "fixed", categories = c(1,3,6,9,12), labels = c("Tylenol","Advil","Bayer","Generic")) 

[1] Generic Generic Generic Generic Bayer Tylenol Generic Advil Bayer Generic Advil Generic Advil Advil Generic Bayer Generic Advil Generic Bayer 
Levels: Tylenol Advil Bayer Generic 
0

Per completezza (e la soluzione probabilmente più veloce e più semplice) si possono creare e chiamato vettore e utilizzarlo per la ricerca. credito: http://adv-r.had.co.nz/Subsetting.html#applications

product.code <- c(`1`='Tylenol',`2`='Tylenol',`3`='Tylenon', `4`='Advil', `5` = 'Advil', `6`='Advil', `7`='Bayer', `8`='Bayer', `9`='Bayer', `10`='Generic', `11`='Generic', `12`='Generic') per ottenere il risultato $unname(product.code[dat$product])

bench-marking per la velocità con le migliori soluzioni

$microbenchmark( named_vector = unname(product.code[dat$product]), find.key = find.key(dat$product, brands), levels = `levels<-`(factor(dat$product),brands)) Unit: microseconds expr min lq mean median uq max neval named_vector 11.777 20.4810 26.12832 23.0410 28.1610 207.360 100 find.key 34.305 55.8090 58.75804 59.1370 65.5370 130.049 100 levels 143.361 224.7685 234.02545 247.5525 255.7445 338.944 100 Questa soluzione è molto simile a @ di kohske soluzione, ma avrebbe funzionato per non ricerca numerica.

Problemi correlati