在R的data.table计算中使用前一行的值

98

我想在一个data.table中创建一个新的列,该列根据另一列的当前值和另一列的前一个值进行计算。我能够访问以前的行吗?

例如:

> DT <- data.table(A=1:5, B=1:5*10, C=1:5*100)
> DT
   A  B   C
1: 1 10 100
2: 2 20 200
3: 3 30 300
4: 4 40 400
5: 5 50 500
> DT[, D := C + BPreviousRow] # What is the correct code here?

正确答案应该是

> DT
   A  B   C   D
1: 1 10 100  NA
2: 2 20 200 210
3: 3 30 300 320
4: 4 40 400 430
5: 5 50 500 540

我通常会给我的数据表设置一个关键列:DT <- data.table(A=..., key = "A") - PatrickT
7个回答

115

使用v1.9.6中实现的shift()函数,这就很简单了。

DT[ , D := C + shift(B, 1L, type="lag")]
# or equivalently, in this case,
DT[ , D := C + shift(B)]

来自NEWS

  1. 新函数shift()实现了对于向量列表数据框数据表的快速lead/lag操作。它接受一个type参数,可以是"lag"(默认)或"lead"。它可以很方便地与:=set()一起使用。例如:DT[, (cols) := shift(.SD, 1L), by=id]。请参阅?shift获取更多信息。

查看历史记录以获取以前的答案。


7
你可能还会发现.I很有用,它保存了当前组中行的索引。 - Steve Lianoglou
7
请使用seq_len(.N - 1)代替1:(.N-1),这样可以避免与1:0相关的问题。 - mnel
1
+1 给 .SD 的例子 -- 我试图使用 lapply,但结果出现了问题。这种方法简单得多。 - MichaelChirico
我在哪里可以找到包含所有这些新信息的更新PDF文件? 官方的1.9.4文档和网络研讨会都没有包含它。 而Rmd 1.9.5文档也不太方便,也没有包含它。 - skan
@Arun,你的第一个解决方案在我的data.table 1.9.5中显示“Error in C + shift(B, 1L, type =“lag”):二元运算符的非数字参数”。 - skan
显示剩余3条评论

56

使用 dplyr ,您可以这样做:

mutate(DT, D = lag(B) + C)

这意味着:

#   A  B   C   D
#1: 1 10 100  NA
#2: 2 20 200 210
#3: 3 30 300 320
#4: 4 40 400 430
#5: 5 50 500 540

24

有几位回答了具体的问题。请看下面的代码,这是一个我在像这样的情况下使用的通用函数,可能会有所帮助。与其只获取上一行,您可以向“过去”或“未来”移动任意数量的行。

rowShift <- function(x, shiftLen = 1L) {
  r <- (1L + shiftLen):(length(x) + shiftLen)
  r[r<1] <- NA
  return(x[r])
}

# Create column D by adding column C and the value from the previous row of column B:
DT[, D := C + rowShift(B,-1)]

# Get the Old Faithul eruption length from two events ago, and three events in the future:
as.data.table(faithful)[1:5,list(eruptLengthCurrent=eruptions,
                                 eruptLengthTwoPrior=rowShift(eruptions,-2), 
                                 eruptLengthThreeFuture=rowShift(eruptions,3))]
##   eruptLengthCurrent eruptLengthTwoPrior eruptLengthThreeFuture
##1:              3.600                  NA                  2.283
##2:              1.800                  NA                  4.533
##3:              3.333               3.600                     NA
##4:              2.283               1.800                     NA
##5:              4.533               3.333                     NA

这是一个很棒的答案,我很生气因为我已经赞过其他答案了,因为这个答案更加通用。事实上,我将在我的geneorama包中使用它(如果您不介意)。 - geneorama
当然,去试试吧。我本来希望能有些空闲时间并将其作为拉取请求提交到“data.table”软件包中,但是唉... - dnlbrky
data.table版本1.9.5新增了一个名为shift的类似函数。请参阅@Arun提供的更新答案 - dnlbrky

13

基于@Steve Lianoglou上面的评论,为什么不直接:

DT[, D:= C + c(NA, B[.I - 1]) ]
#    A  B   C   D
# 1: 1 10 100  NA
# 2: 2 20 200 210
# 3: 3 30 300 320
# 4: 4 40 400 430
# 5: 5 50 500 540

避免使用 seq_lenhead 或任何其他函数。


2
不错 - 但是如果你想在一组中查找前一个,这种方法就行不通了。 - Matthew
1
@Matthew 你是对的。如果按组进行子集操作,我会用 seq_len(.N) 替换 .I - Gary Weissman

9

跟随Arun的解决方案,不需要引用.N,也可以得到类似的结果。

> DT[, D := C + c(NA, head(B, -1))][]
   A  B   C   D
1: 1 10 100  NA
2: 2 20 200 210
3: 3 30 300 320
4: 4 40 400 430
5: 5 50 500 540

有没有理由更喜欢一种方法而不是另一种?还是仅仅是审美上的差异? - Corvus
我认为在这种情况下(即.N容易获得的情况下),这主要是审美选择。我不知道有任何重要的区别。 - Ryogi

2
这是我的直觉解决方案:

#create data frame
df <- data.frame(A=1:5, B=seq(10,50,10), C=seq(100,500, 100))`
#subtract the shift from num rows
shift  <- 1 #in this case the shift is 1
invshift <- nrow(df) - shift
#Now create the new column
df$D <- c(NA, head(df$B, invshift)+tail(df$C, invshift))`

在这里,invshift,即行数减1,为4。nrow(df)提供了数据框或向量中的行数。同样地,如果您想获取更早的值,可以从nrow中减去2、3等,并在开头相应地放置NA。


-2

这可以在循环中完成。

# Create the column D
DT$D <- 0
# for every row in DT
for (i in 1:length(DT$A)) {
  if(i==1) {
    #using NA at first line
    DT[i,4] <- NA
  } else {
    #D = C + BPreviousRow
    DT[i,4] <- DT[i,3] + DT[(i-1), 2]   
  }
}

使用 for 循环,您甚至可以使用此新列的行的上一个值 DT[(i-1), 4]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接