如何使用R包data.table和滚动连接查找最后一个或下一个条目

use*_*975 7 r data.table

Lets say I have a data table like this.

   customer_id time_stamp value
1:           1        223     4
2:           1        252     1
3:           1        456     3
4:           2        455     5
5:           2        632     2
Run Code Online (Sandbox Code Playgroud)

这样customer_id和time_stamp一起形成一个唯一的密钥.我想添加一些新列,指示"value"的上一个和最后一个值.也就是说,我想要这样的输出.

  customer_id time_stamp value value_PREV value_NEXT
1:           1        223     4         NA          1
2:           1        252     1          4          3
3:           1        456     3          1         NA
4:           2        455     5         NA          2
5:           2        632     2          5         NA
Run Code Online (Sandbox Code Playgroud)

我希望这很快,并且可以处理稀疏,不规则的时间.我认为data.table滚动连接会为我做.但是,滚动连接似乎找到最后一次或同一时间.因此,如果您对同一个表的两个副本进行滚动连接(在将_PREV添加到副本的列名之后),则这不起作用.您可以通过在副本的时间变量中添加一个小数字来捏造它,但这有点尴尬.

有没有办法简单地使用rollin join或其他一些data.table方法?我找到了一种有效的方法,但它仍然需要大约40行R代码.如果滚动连接可以被告知寻找最后一次不包括相同的时间,这似乎是一个单行.或许还有其他一些巧妙的伎俩.

这是示例数据.

data=data.table(customer_id=c(1,2,1,1,2),time_stamp=c(252,632,456,223,455),value=c(1,2,3,4,5))
data_sorted=data[order(customer_id,time_stamp)]
Run Code Online (Sandbox Code Playgroud)

这是我写的代码.请注意,将NA放入customer_id不同的行会引发警告,可能需要更改.我让他们在下面评论.有没有人建议更换这两条线?

add_prev_next_cbind<-function(data,ident="customer_id",timecol="time_stamp",prev_tag="PREV",
                   next_tag="NEXT",sep="_"){
  o=order(data[[ident]],data[[timecol]])
  uo=order(o)
  data=data[o,]
  Nrow=nrow(data)
  Ncol=ncol(data)
  #shift it, put any junk in the first row
  data_prev=data[c(1,1:(Nrow-1)),]
  #shift it, put any junk in the last row
  data_next=data[c(2:(Nrow),Nrow),]
  #flag the rows where the identity changes, these get NA
  prev_diff=data[[ident]] != data_prev[[ident]]
  prev_diff[1]=T
  next_diff=data[[ident]] != data_next[[ident]]  
  next_diff[Nrow]=T
  #change names
  names=names(data)
  names_prev=paste(names,prev_tag,sep=sep)
  names_next=paste(names,next_tag,sep=sep)
  setnames(data_prev,names,names_prev)
  setnames(data_next,names,names_next)
  #put NA in rows where prev and next are from a different ident
  #replace the next two lines with something else
  #data_prev[prev_diff,]<-NA
  #data_next[next_diff,]<-NA
  data_all=cbind(data,data_prev,data_next)
  data_all=data_all[uo,]
  return(data_all)
}
Run Code Online (Sandbox Code Playgroud)

Aru*_*run 9

更新:#965现在在1.9.5中实现.来自新闻:

  1. 新函数shift()快速实现lead/lagvector,list,data.framesdata.tables.它需要一个type参数,可以是"滞后"(默认)或"引导",并始终返回一个列表,这使得它与:=or 一起使用非常方便set().例如:DT[, (cols) := shift(.SD, 1L), by=id].请查看?shift更多信息.

现在我们可以这样做:

dt[, c("value_PREV", "value_NEXT") := c(shift(value, 1L, type="lag"), 
                     shift(value, 1L, type="lead")), by=customer_id]
Run Code Online (Sandbox Code Playgroud)

你根本不需要滚动连接.你可以用head和做tail.假设你data.table是DT:

setkey(DT, "customer_id")
DT[, list(time_stamp = time_stamp, 
          prev.val = c(NA, head(value, -1)), 
          next.val = c(tail(value, -1), NA)), 
by=customer_id]
#   customer_id time_stamp prev.val next.val
# 1:           1        223       NA        1
# 2:           1        252        4        3
# 3:           1        456        1       NA
# 4:           2        455       NA        2
# 5:           2        632        5       NA
Run Code Online (Sandbox Code Playgroud)

编辑:更好:

DT[, `:=`(prev.val = c(NA, head(value, -1)), 
          next.val = c(tail(value, -1), NA)), 
          by=customer_id]
Run Code Online (Sandbox Code Playgroud)


Mat*_*wle 6

是的,如果我不想roll等同,那么如果它是double类型,或者使用整数并加1或减1L,我也会稍微关闭一下.

DT = data.table( customer_id=c(1,2,1,1,2), 
                 time_stamp=as.integer(c(252,632,456,223,455)),
                 value=c(1,2,3,4,5))
setkey(DT, customer_id, time_stamp)
DT[ DT[,list(customer_id,time_stamp+1L,value)], value_PREV:=i.value, roll=-Inf]
DT[ DT[,list(customer_id,time_stamp-1L,value)], value_NEXT:=i.value, roll=+Inf]
DT
   customer_id time_stamp value value_PREV value_NEXT
1:           1        223     4         NA          1
2:           1        252     1          4          3
3:           1        456     3          1         NA
4:           2        455     5         NA          2
5:           2        632     2          5         NA
Run Code Online (Sandbox Code Playgroud)

不得不采取的列子集DTi像这是一个有点尴尬,我同意.

现在已经提交了FR#2628来添加新参数rollequal=TRUE|FALSE.然后它会是:

setkey(DT, customer_id, time_stamp)
DT[ DT, value_PREV:=i.value, roll=-Inf, rollequal=FALSE]
DT[ DT, value_NEXT:=i.value, roll=+Inf, rollequal=FALSE]
Run Code Online (Sandbox Code Playgroud)

通过避免i列的副本而不需要为time_stamp-1L和分配也会更快time_stamp+1L.

但在这种情况下,它是一个自我加入DT,DT而且DT关键是独一无二的,所以正如Arun所说,roll不需要加入.可能需要快速移位或滞后功能来避免速度c()和/ head()tail()速度的开销.

谢谢你的突出!