R ifelse风格的多类别编码习惯用法

R ifelse风格的多类别编码习惯用法,r,r-factor,recode,R,R Factor,Recode,我经常遇到这个问题,所以我觉得应该有一个好的成语来形容它。假设我有一个data.frame,它有一系列属性,包括“product”。我还有一个键,可以将产品转换为品牌+规模。产品代码1-3为泰诺,4-6为阿维,7-9为拜耳,10-12为通用 最快的编码方式是什么 如果有3个或更少的类别,我倾向于使用嵌套的ifelse,如果有3个以上的类别,则键入数据表并将其合并。还有更好的主意吗?Stata有一个非常适合这种类型的东西,尽管我相信它促进了数据代码的混合 dat <- structure(l

我经常遇到这个问题,所以我觉得应该有一个好的成语来形容它。假设我有一个data.frame,它有一系列属性,包括“product”。我还有一个键,可以将产品转换为品牌+规模。产品代码1-3为泰诺,4-6为阿维,7-9为拜耳,10-12为通用

最快的编码方式是什么

如果有3个或更少的类别,我倾向于使用嵌套的
ifelse
,如果有3个以上的类别,则键入数据表并将其合并。还有更好的主意吗?Stata有一个非常适合这种类型的东西,尽管我相信它促进了数据代码的混合

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可以使用列表作为关联数组来定义
品牌->产品代码
映射,即:

brands <- list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)
我确信有更好的方法来编写这个函数(for
循环的
让我恼火!),但至少它是矢量化的,所以它只需要一次遍历列表

使用它将类似于:

> 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

(我无法使
开关
版本正确地进行基准测试,但它似乎比上述所有版本都快,尽管它对人类来说比
重新编码
解决方案更糟糕。)

比嵌套的
ifelse
可读性更强:

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

警告:效率不高。

我喜欢
汽车
软件包中的
重新编码
功能:

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
库(车)
dat$品牌dat
#产品品牌
#11一般性
#2.11一般性
#3.9拜耳
#4.9拜耳
#5 6 Advil
#6.1泰诺
#7.11一般性
#8.5 Advil
#拜耳
#10 11通用
#11.5 Advil
#12 11通用
#13 4 Advil
#14.3泰诺
#15 10一般性
#拜耳公司
#17 10一般性
#18 5 Advil
#19 9拜耳
#20 8拜耳

您可以将变量转换为一个因子,并通过
levels更改其级别。这需要键入一些内容,但如果您确实拥有一个庞大的数据集,这可能是一种方法。talkstats.com的Bryangoodrich和Dason教了我这个。它使用哈希表或创建包含查找表的环境。实际上,我在.Rprofile(即hash函数)中保留了这个函数,用于字典类型的查找

我将您的数据复制了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])    
#################################################
#散列函数(创建ENW环境)#
#################################################

hash我经常使用以下技术:

key <- c()
key[1:3] <- "Tylenol"
key[4:6] <- "Advil"
key[7:9] <- "Bayer"
key[10:12] <- "Generic"
“数据库方法”是为产品密钥定义保留一个单独的表(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
不会保留行的顺序。如果这是一个问题,
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表(从<代码>数据>表/代码>包)而不是DATA .Frask.< /P> < P>如果您在示例中的顺序组中有代码,这可能是代码>剪切< /代码>芥菜:

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

我倾向于使用这个功能:

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

还有
arules:discreatize
,但我不太喜欢它,因为它可以将标签与值的范围分开:

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
为了完整性(可能是最快最简单的解决方案),可以创建和命名向量,并将其用于查找。 学分:

顶尖解决方案的速度基准测试

$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
$microbenchmark(
命名向量=未命名(产品代码[dat$product]),
find.key=find.key(dat$产品、品牌),

levels=`levels另一个版本,在这种情况下可以工作:

c("Tylenol","Advil","Bayer","Generic")[(dat$product %/% 3.1) + 1]

有趣的解决方案,但肯定无法通过更快的人类集合!为什么不呢?
find.key
是一个通用函数,您可以将粘贴复制到代码中并使用。较新的版本看起来非常易于使用。此版本没有:
cbind(dat,dat$brand=find反应非常好,速度非常快。我非常喜欢+1@gsk3,是的,我在解决如何矢量化
查找.key时遇到了困难,因此没有考虑如何完成最后一步。Night Mind Blank:)
recode
唯一的问题是它是通过处理字符串来工作的,所以如果你的代码/数据中碰巧有分号和=符号,那将是一个非常头痛的问题……这是迄今为止最简单的方法,即使你第一次调用
levelsNice快捷方式!我在这里找到了它的解释:这里展示了大量的创造力。将有一个选择答案很困难。合并时是否有保留行顺序的
,sort=FALSE
选项?您需要调用
unname(product.code[as.character(dat$product]))
,以使其有意义。然后它就成为一个相当普遍的解决方案。
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
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)))
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
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])
$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
c("Tylenol","Advil","Bayer","Generic")[(dat$product %/% 3.1) + 1]