我碰到这往往不够运行我想必须有它一个很好的成语。 假设我有一堆的属性,包括data.frame“的产品。” 我也有转换产品品牌+大小的关键。 产品代码1-3是泰诺,4-6是布洛芬,7-9是拜耳,10-12是通用。
什么是最快的(在人类时间而言)的实现代码吗?
我倾向于使用嵌套ifelse
的,如果有3个或更少的类别,并输入了数据表和合并它,如果有超过3个更好的想法? 塔塔有一个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")
Answer 1:
人们可以使用一个列表作为关联数组来定义brand -> product code
映射,即:
brands <- list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)
一旦你有了这个,你就可以任意翻转它来创建一个product code -> brand
列表(可能占用大量的内存),或只使用一个搜索功能:
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
循环很讨厌我!),但至少它是矢量化,所以只需要在列表中一次通过。
使用它会是这样的:
> 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
和levels<-
解决方案是非常好的,但他们也不止这一个显著慢(但只要你拥有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
版本标杆正常,但它似乎比上述所有的速度更快,但它更是雪上加霜换人比recode
解决方案。)
Answer 2:
您可以将您的变量转换为一个因素,通过改变其等级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)
Answer 3:
我喜欢的recode
的功能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
Answer 4:
我经常使用下面的技巧:
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"
Answer 5:
“数据库的方法”是保持一个单独的表(一个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
:
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。
Answer 6:
这其中需要一些打字,但如果你真的有一个巨大的数据集,这可能是要走的路。 Bryangoodrich和达诚在talkstats.com教了我这一个。 它使用一个哈希表或创建包含一个查找表环境。 其实我对于保持字典类型看起坐这一个对我.Rprofile(散列函数即是)。
我复制你的数据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])
在这种情况下,散列是缓慢的,但如果你有更多的层次,以重新编码,然后它会较其他人的速度增加。
Answer 7:
一定程度上比嵌套更易读ifelse
的:
unlist(lapply(as.character(dat$product), switch,
`1`=,`2`=,`3`='tylenol',
`4`=,`5`=,`6`='advil',
`7`=,`8`=,`9`='bayer',
`10`=,`11`=,`12`='generic'))
警告:不是很有效。
Answer 8:
我倾向于使用此功能:
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)))
Answer 9:
如果您在连续组码类似的例子中,这可能会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
Answer 10:
还有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
Answer 11:
另一个版本,将在此情况下工作:
c("Tylenol","Advil","Bayer","Generic")[(dat$product %/% 3.1) + 1]
Answer 12:
为了完整(也可能是最快,最简单的解决方案),可以创建并命名为载体,用它来查找。 信用: 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])
台式打标速度与最佳方案
$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
该解决方案是非常相似的@ kohske的解决方案,但会工作的非数值查找。
文章来源: Idiom for ifelse-style recoding for multiple categories