用于多个类别的ifelse式重新编码的成语

我经常碰到这种情况,所以我觉得应该有一个很好的习惯用法。 假设我有一个包含“product”的一系列属性的data.frame。 我也有一个把产品翻译成品牌+尺寸的钥匙。 产品代码1-3是Tylenol,4-6是Advil,7-9是Bayer,10-12是Generic。

什么是最快的(在人的时间方面)来编码呢?

如果有3个或更less的类别,我倾向于使用嵌套的ifelse ,如果有3个或更less的类别,则input数据表并合并。 Stata有一个recode命令 ,对于这样的事情来说非常漂亮,不过我认为它会促进数据代码混合太多。

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

可以使用列表作为关联数组来定义brand -> product code映射,即:

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

一旦你有了这个,你可以反过来创build一个product code -> brand列表(可能需要大量的内存),或者只是使用searchfunction:

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

我相信有更好的方法来编写这个函数( for循环令我讨厌!),但至less它是vector化的,所以它只需要一个遍历列表。

使用它会是这样的:

 > 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 

recodelevels<-解决scheme是非常好的,但他们也比这一个明显慢(和一旦你有find.key这对人类来说比recode更容易,并与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 

(我不能让switch版本正确地进行基准testing,但似乎比以上所有都快,尽pipe对于人类而言,这比recode解决scheme更糟糕。)

您可以将您的variables转换为一个因子,并通过levels<-函数来更改其级别。 在一个命令中可能是这样的:

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

在步骤中:

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

我喜欢car包装中的recodefunction:

 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 

我经常使用下面的技巧:

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

然后,

 > 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" 

“数据库方法”是为产品密钥定义保留一个单独的表(data.frame)。 因为你说你的产品密钥不仅仅是一个品牌,而且也是一个尺寸:

 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) 

然后,您可以使用merge来join您的数据:

 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 

正如您注意到的那样,行的顺序不会通过merge保存。 如果这是一个问题, plyr包有一个join函数,可以保持顺序:

 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 

最后,如果你的表很大,速度是一个问题,考虑使用data.tables(来自data.table包)而不是data.frames。

这一个需要一些打字,但如果你真的有一个巨大的数据集这可能是要走的路。 talkstats.com的Bryangoodrich和Dason教会了我这个。 它使用一个哈希表或创build一个包含查找表的环境。 我实际上保留这一个在我的.Rprofile(哈希函数是)字典types查找。

我复制你的数据1000倍,使其更大一点。

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

在这种情况下,哈希是缓慢的,但如果你有更多的水平重新编码,那么它会增加速度超过其他人。

比嵌套的ifelse更可读:

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

警告:效率不高。

我倾向于使用这个function:

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

如:

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

如果您在示例中使用连续组的代码,则可能会cut芥末:

 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 

也有arules:discretize ,但我更喜欢它,因为它使您从值的范围分隔标签:

 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 

为了完整性(也许是最快和最简单的解决scheme),可以创build并命名向量,并将其用于查找。 信用: 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')获得输出$unname(product.code[dat$product])

顶级解决scheme的速度标杆

$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此解决scheme与@ kohske的解决scheme非常相似,非数字查找。