R ifelse风格的多类别编码习惯用法
我经常遇到这个问题,所以我觉得应该有一个好的成语来形容它。假设我有一个data.frame,它有一系列属性,包括“product”。我还有一个键,可以将产品转换为品牌+规模。产品代码1-3为泰诺,4-6为阿维,7-9为拜耳,10-12为通用 最快的编码方式是什么 如果有3个或更少的类别,我倾向于使用嵌套的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
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]