(有效地)合并随机密钥子集

时间:2015-05-20 18:51:59

标签: r data.table

我有两个data.table个;我想从匹配键的那些中随机地将一个元素分配给另一个元素。我现在这样做的方式很慢。

让我们具体;这里是一些示例数据:

dt1<-data.table(id=sample(letters[1:5],500,replace=T),var1=rnorm(500),key="id")
dt2<-data.table(id=c(rep("a",4),rep("b",8),rep("c",2),rep("d",5),rep("e",7)),
                place=paste(sample(c("Park","Pool","Rec Center","Library"),
                                   26,replace=T),
                            sample(26)),key="id")

我想为每次观察添加两个随机选择的placedt1,但place必须与id匹配。

这就是我现在正在做的事情:

get_place<-function(xx) sapply(xx,function(x) dt2[.(x),sample(place,1)])

dt1[,paste0("place",1:2):=list(get_place(id),get_place(id))]

这很有效,但它的相当很慢 - 在我的电脑上运行需要66秒,基本上是eon。

似乎有一个问题似乎是我无法正确利用键控:

dt2[.(dt1$id),mult="random"]这样的东西会很完美,但它似乎不可能。

有什么建议吗?

3 个答案:

答案 0 :(得分:6)

一个简单的答案

dt2[.(dt1),as.list(c(
  place=sample(place,size=2,replace=TRUE)
)),by=.EACHI,allow.cartesian=TRUE]

这种方法很简单,并说明data.table功能,如笛卡尔连接和by=.EACHI,但速度非常慢,因为dt1的每一行(i)样本和(ii)强制结果列表。

答案更快

nsamp <- 2
dt3   <- dt2[.(unique(dt1$id)),list(i0=.I[1]-1L,.N),by=.EACHI]
dt1[.(dt3),paste0("place",1:nsamp):=
  replicate(nsamp,dt2$place[i0+sample(N,.N,replace=TRUE)],simplify=FALSE)
,by=.EACHI]

replicatesimplify=FALSE一起使用(同样在@ bgoldst的回答中)也是最有意义的:

  • 它返回一个向量列表,它是格式data.table在创建新列时所需的格式。
  • replicate是重复模拟的标准R函数。

基准。我们应该查看不同的功能,而不是在我们继续修改dt1时:

# candidate functions
frank2 <- function(){
  dt3   <- dt2[.(unique(dt1$id)),list(i0=.I[1]-1L,.N),by=.EACHI]
  dt1[.(dt3),
    replicate(nsamp,dt2$place[i0+sample(N,.N,replace=TRUE)],simplify=FALSE)
  ,by=.EACHI]
}
david2 <- function(){
  indx <- dt1[,.N, id]
  sim <- dt2[.(indx),
    replicate(2,sample(place,size=N,replace=TRUE),simplify=FALSE)
  ,by=.EACHI]
  dt1[, sim[,-1,with=FALSE]]
}
bgoldst<-function(){
  dt1[,
    replicate(2,ave(id,id,FUN=function(x) 
      sample(dt2$place[dt2$id==x[1]],length(x),replace=T)),simplify=F)
  ]
}

# simulation
size <- 1e6
nids <- 1e3
npls <- 2:15

dt1 <- data.table(id=sample(1:nids,size=size,replace=TRUE),var1=rnorm(size),key="id")
dt2 <- unique(dt1)[,list(place=sample(letters,sample(npls,1),replace=TRUE)),by=id]

# benchmarking
res <- microbenchmark(frank2(),david2(),bgoldst(),times=10)
print(res,order="cld",unit="relative")

给出了

Unit: relative
      expr      min       lq     mean   median       uq      max neval cld
 bgoldst() 8.246783 8.280276 7.090995 7.142832 6.579406 5.692655    10   b
  frank2() 1.042862 1.107311 1.074722 1.152977 1.092632 0.931651    10  a 
  david2() 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000    10  a 

如果我们改变参数......

# new simulation
size <- 1e4
nids <- 10
npls <- 1e6:2e6

dt1 <- data.table(id=sample(1:nids,size=size,replace=TRUE),var1=rnorm(size),key="id")
dt2 <- unique(dt1)[,list(place=sample(letters,sample(npls,1),replace=TRUE)),by=id]

# new benchmarking
res <- microbenchmark(frank2(),david2(),times=10)
print(res,order="cld",unit="relative")

我们看到了

Unit: relative
     expr    min     lq     mean   median       uq     max neval cld
 david2() 3.3008 3.2842 3.274905 3.286772 3.280362 3.10868    10   b
 frank2() 1.0000 1.0000 1.000000 1.000000 1.000000 1.00000    10  a 

正如人们所期望的那样,哪种方式更快 - 在dt1中折叠david2或在dt2中折叠frank2 - 取决于折叠压缩了多少信息

答案 1 :(得分:3)

用于此目的的完美函数是ave(),因为它允许为向量的每个组运行函数,并自动将返回值映射回组的元素:

set.seed(1);
dt1 <- data.table(id=sample(letters[1:5],500,replace=T), var1=rnorm(500), key='id' );
dt2 <- data.table(id=c(rep('a',4),rep('b',8),rep('c',2),rep('d',5),rep('e',7)), place=paste(sample(c('Park','Pool','Rec Center','Library'),26,replace=T), sample(26) ), key='id' );
dt1[,paste0('place',1:2):=replicate(2,ave(id,id,FUN=function(x) sample(dt2$place[dt2$id==x[1]],length(x),replace=T)),simplify=FALSE)]
dt1;
##      id       var1        place1        place2
##   1:  a -0.4252677 Rec Center 23       Park 12
##   2:  a -0.3892372       Park 12    Library 22
##   3:  a  2.6491669       Park 14 Rec Center 23
##   4:  a -2.2891240 Rec Center 23       Park 14
##   5:  a -0.7012317    Library 22       Park 12
##  ---
## 496:  e -1.0624084    Library 16    Library 16
## 497:  e -0.9838209     Library 4    Library 26
## 498:  e  1.1948510    Library 26       Pool 21
## 499:  e -1.3353714       Pool 18    Library 26
## 500:  e  1.8017255       Park 20       Pool 21

这适用于data.frame以及data.table s。

编辑:添加基准

这个解决方案似乎最快,至少在Frank下面做出修正之后。

frank<-function(){dt2[.(dt1),as.list(c(
  place=sample(place,size=2,replace=TRUE))),
  by=.EACHI,allow.cartesian=TRUE]}
david<-function(){
  dt1[,paste0("place",1:2):=
        lapply(1:2,function(x) get_place(id,.N)),by=id]}
bgoldst<-function(){dt1[,paste0("place",1:2):=
                          replicate(2,ave(id,id,FUN=function(x) 
                            sample(dt2$place[dt2$id==x[1]],length(x),replace=T)),
                                    simplify=F)]}

microbenchmark(times=1000L,frank(),david(),bgoldst())

Unit: milliseconds
      expr      min       lq     mean   median       uq      max neval cld
   frank() 5.125843 5.353918 6.276879 5.496042 5.772051 15.57155  1000  b 
   david() 6.049172 6.305768 7.172360 6.455687 6.669202 93.06398  1000   c
 bgoldst() 1.421330 1.521046 1.847821 1.570573 1.628424 89.60315  1000 a  

答案 2 :(得分:3)

当您在每行上运行sapply时,您基本上没有使用任何data.table功能。或者,您可以使用二进制连接和by参数,每个id只采样一次。您可以按如下方式定义get_place

get_place <- function(tempid, N) dt2[.(tempid), sample(place, N, replace = TRUE)]

然后只需做

dt1[, place1 := get_place(id, .N), by = id]

或者一般的解决方案是

indx <- 1:2
dt1[, paste0("place", indx) := lapply(indx, function(x) get_place(id, .N)), by = id]

这是一个更大dt1

的基准
size = 1e6
set.seed(123)
dt1 <- data.table(id=sample(letters[1:5],size,replace=TRUE),var1=rnorm(size),key="id")

使用@bgoldst答案中定义的相同功能

microbenchmark(times = 10L, frank(), david(), bgoldst())
# Unit: milliseconds
# expr              min         lq       mean     median         uq        max neval
# frank()   11627.68324 11771.4227 11887.1232 11804.6342 12012.4636 12238.1031    10
# david()      84.62109   122.1117   121.1003   123.5861   128.0042   132.3591    10
# bgoldst()   372.02267   400.8867   445.6231   421.3168   445.9076   709.5458    10

这是同一个想法的另一个更快的变体(如@ Frank的基准测试中所示):

indx<- dt1[,.N, id]
sim <- dt2[.(indx),replicate(2,sample(place,size=N,replace=TRUE),simplify=FALSE),by=.EACHI]
dt1[,paste0("place",1:2):=`[.listof`(sim,-1)]