2012-05-03 11 views
17

Przebiegam to na tyle często, że sądzę, że musi to być dobry idiom. Załóżmy, że mam data.frame z wieloma atrybutami, w tym "produktem". Mam też klucz, który przekłada produkty na markę + rozmiar. Kody produktów 1-3 to Tylenol, 4-6 to Advil, 7-9 to Bayer, 10-12 to leki generyczne.Idiom do rekodowania w stylu ifelse dla wielu kategorii

Jaki jest najszybszy (jeśli chodzi o czas ludzki) sposób, aby to zakodować?

Używam zagnieżdżonych ifelse, jeśli są 3 lub mniej kategorii, a następnie wypisać tabelę danych i połączyć ją, jeśli jest więcej niż 3. Jakieś lepsze pomysły? Stata ma numer recode command, który jest całkiem niezły z tego powodu, chociaż uważam, że promuje on kodowanie danych trochę za dużo.

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

Mnóstwo kreatywności na wyświetlaczu tutaj. Trudno znaleźć odpowiedź. –

Odpowiedz

14

Jeden przydałby listę jako tablicy asocjacyjnej zdefiniować odwzorowanie brand -> product code, a mianowicie:

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

Gdy masz ten można następnie albo odwrócić to, aby utworzyć listę product code -> brand (może trwać dużo pamięci), lub po prostu użyć funkcji wyszukiwania:

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

jestem pewien, że są lepsze sposoby pisania tej funkcji (for l oop mnie denerwuje!), ale przynajmniej jest wektoryzowany, więc wymaga tylko jednego przejścia przez listę.

Korzystanie byłoby coś jak:

> 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 

recode i levels<- rozwiązania są bardzo ładne, ale są one znacznie wolniej niż ten (i raz masz find.key jest to łatwiejsze dla-ludzi niż recode i na równi z 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 

(nie mogę dostać wersję do benchmarku switch prawidłowo, ale wydaje się być szybszy niż wszystkie z powyższym, mimo że jest jeszcze gorzej-dla-ludzi niż roztwór recode)

+0

Zabawne rozwiązanie, ale zdecydowanie nie przechodzi przez szybsze dla ludzi zbiórki! –

+0

Dlaczego nie? 'find.key' to funkcja ogólna, którą można po prostu skopiować do kodu i użyć. – huon

+0

Nowsza wersja wygląda na bardzo łatwą w użyciu. Ta wersja nie: 'cbind (dat, dat $ marka = znajdź <- find.key (dat $ product, brands))'. Ale teraz, kiedy naprawdę na to patrzę, to też nie jest skomplikowane. Poranna głupota :-) –

3

Nieco bardziej czytelny niż zagnieżdżonych ifelse „s.

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

Zastrzeżenie: nie bardzo wydajny.

+1

+1 Niezbyt wydajne, ale przyjemne. –

12

Lubię funkcję recode w pakiecie 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

Jedynym problemem z 'recode' jest to, że działa on poprzez przetwarzanie łańcuchów, więc jeśli twoje kody/dane zdarzają się mieć średniki i = znaki w nich to jest wielki ból głowy ... –

19

Można przekonwertować zmienną do czynnika i zmieniać jej poziom przez levels<- funkcji. W jednym poleceniu może to być tak:

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

w krokach:

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

To jest zdecydowanie najłatwiejszy sposób, nawet jeśli twoje Pierwsze połączenie z 'poziomami <-'prawdopodobnie zmyli wiele osób. :) –

+2

Ładny skrót! Znalazłem jego wyjaśnienie tutaj: [link] (http://stackoverflow.com/q/10449366/1460352) – nassimhddd

6

Ten jeden zajmuje trochę pisać, ale jeśli naprawdę mają ogromny zbiór danych może to być droga. Bryangoodrich i Dason z talkstats.com nauczyli mnie tego. Używa tabeli mieszania lub tworzy środowisko, które zawiera tabelę wyszukiwania. Tak naprawdę trzymam ten na mojej.Rprofile (funkcja skrótu) dla wyszukiwań typu słownikowego.

Powieliłem twoje dane 1000 razy, aby było nieco większe.

################################################# 
# 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])  

W tym przypadku mieszanie jest powolne, ale jeśli masz więcej poziomów do recodowania, zwiększy to prędkość nad innymi.

7

często używam techniki poniżej:

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

Następnie

> 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

"Podejście baza" jest utrzymanie oddzielnej tabeli (a data.frame) dla definicji kluczy produktu. To sprawia, że ​​nawet więcej sensu, skoro mówisz klucze produktu przekładają się nie tylko markę, ale również rozmiar:

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) 

Następnie można dołączyć swoje dane za pomocą 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 

Jak można zauważyć, kolejność wierszy nie jest zachowywana przez merge. Jeśli jest to problem, pakiet plyr ma join funkcję, która pozwala zachować porządek:

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 

Wreszcie, jeśli tabele są duże i prędkość jest problem, należy rozważyć użycie data.tables (z pakietu data.table) zamiast data.frames.

+0

Czy nie istnieje opcja ', sort = FALSE' dla scalania, która zachowuje kolejność wierszy? –

1

Jeżeli masz kody w sekwencyjnych grupach jak na przykład ten może cut musztardę:

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

staram się korzystać z tej funkcji:

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

Jak w:

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

Jest też arules:discretize, ale lubię go mniej, ponieważ oddziela cię Etykiety z zakresu wartości:

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

Dla kompletności (i prawdopodobnie najszybszego i najprostszego rozwiązania) można stworzyć wektor o nazwie i użyć go do wyszukiwania. kredytowe: 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') Aby uzyskać wyjście $unname(product.code[dat$product])

Bench oznakowanie dla prędkości z najlepszych rozwiązań

$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 To rozwiązanie jest bardzo podobna do użytkownika @ kohske rozwiązania, ale będzie pracować dla nie- wyszukiwanie numeryczne.