在R中创建不平衡面板数据的滞后变量

14

我想创建一个变量,包含前一年组内某个变量的值。

     id   date        value
1     1   1992          4.1  
2     1     NA          4.5  
3     1   1991          3.3  
4     1   1990          5.3  
5     1   1994          3.0  
6     2   1992          3.2  
7     2   1991          5.2  

当组内前一年的数据缺失时,应使value_lagged也缺失 - 这可能是因为在组内这是第一个日期(如第4行、第7行),或者是因为数据中存在年份间隔(如第5行)。此外,在当前时间缺失时,value_lagged也应该缺失(如第2行)。

因此得到:

     id   date    value    value_lagged  
1     1   1992      4.1             3.3
2     1     NA      4.5              NA
3     1   1991      3.3             5.3
4     1   1990      5.3              NA
5     1   1994      3.0              NA
6     2   1992      3.2             5.2
7     2   1991      5.2              NA

目前,在 R 中,我使用 data.table 包。

 DT = data.table(id    = c(1,1,1,1,1,2,2),
                 date  = c(1992,NA,1991,1990,1994,1992,1991),
                 value = c(4.1,4.5,3.3,5.3,3.0,3.2,5.2)
                )
 setkey(DT, id, date)
 DT[, value_lagged := DT[J(id, date-1), value], ]
 DT[is.na(date), value_lagged := NA, ]

它很快,但我觉得它有点容易出错。我想知道是否有更好的替代方案,可以使用data.tabledplyr或任何其他包。非常感谢!


Stata中,一个人会这样做:

    tsset id date
    gen value_lagged=L.value

除非你特别希望缺失值的行没有匹配的滞后值,否则你可能想使用 is.na(date) 而不是 is.na(value) - Ricardo Saporta
@Matthew 看起来你已经有一个不错的解决方案了 - 你想要改进它的哪些方面呢? - eddi
我的解决方案对我来说似乎有些复杂和容易出错 - 但可能是因为我刚接触R。即使这是一个不错的解决方案,可能还有更简单的方法来解决它!无论如何,这是一个重要的问题(至少对于Stata用户),所以我认为在stackoverflow上应该有一个明确的问题/答案。 - Matthew
这是你最后两步的轻微改写,可能会稍微少出错一些(因为你不需要多次重复 DT),但我真的看不出你想要什么,除了一个专门设计来做你想要的事情的函数(这就是你的 stata 解决方案所做的):DT[J(id, date + 1, val = value), val_lag := i.val][is.na(date), val_lag := NA] - eddi
这太奇怪了。所以 D1=DT[J(id, date + 1, val = value)], D1[, val_lag:=val]DT[J(id, date + 1, val = value)][, val_lag:=val] 不一样吗? - Matthew
显示剩余8条评论
4个回答

11

我可能会使用连接(join)来解决这个问题:

library(dplyr)

df <- data.frame(
  id = c(1, 1, 1, 1, 1, 2, 2), 
  date = c(1992, NA, 1991, 1990, 1994, 1992, 1991), 
  value = c(4.1, 4.5, 3.3, 5.3, 3.0, 3.2, 5.2)
)


last_year <- df %>% 
  filter(!is.na(date)) %>%
  mutate(date = date + 1, lagged_value = value, value = NULL)

df %>%
  left_join(last_year)
#> Joining by: c("id", "date")
#>   id date value lagged_value
#> 1  1 1992   4.1          3.3
#> 2  1   NA   4.5           NA
#> 3  1 1991   3.3          5.3
#> 4  1 1990   5.3           NA
#> 5  1 1994   3.0           NA
#> 6  2 1992   3.2          5.2
#> 7  2 1991   5.2           NA

8

使用 1.9.5 版本,不需要设置键就可以进行连接,可以按照以下方法进行:

require(data.table) # v1.9.5+
DT[!is.na(date), value_lagged := 
         .SD[.(id = id, date = date - 1), value, on = c("id", "date")]]
#    id date value value_lagged
# 1:  1 1992   4.1          3.3
# 2:  1   NA   4.5           NA
# 3:  1 1991   3.3          5.3
# 4:  1 1990   5.3           NA
# 5:  1 1994   3.0           NA
# 6:  2 1992   3.2          5.2
# 7:  2 1991   5.2           NA

这是你的想法的一个变体。诀窍在于直接在i中使用is.na(),并在j中使用.SD而不是DT。我使用了on=语法,但当然也可以通过设置键来实现相同的想法。


嗨!使用data.table 1.9.5,安装日期为2015年9月11日,会出现以下错误:“Error in [.data.table(.SD, .(id = id, date = date - 1), value, on = c("id", : unused argument (on = c("id", "date"))" - JBJ
使用 remove.packages() 卸载,重新安装并重试。 - Arun

7
创建一个函数tlag,根据给定的时间向量延迟一个向量,并在由id定义的分组内使用它。
library(dplyr)
tlag <- function(x, n = 1L, time) { 
  index <- match(time - n, time, incomparables = NA)
  x[index]
}

df %>% group_by(id) %>% mutate(value_lagged = tlag(value, 1, time = date))

"N=2e6L" 这个数值相当小。0.1秒和0.23秒的差距并不是很显著。你能否尝试在 "2e7L" 或者甚至是 "2e8L" 上进行测试? - Arun
确实并不令人印象深刻。关键是要有一个更易读的解决方案,而事实证明,在2e6时没有性能成本。我刚刚运行了测试:在1e7时,滞后仍然稍微快一些,但纯数据表在1e8时快了两倍。 - Matthew
1
“可读性”的问题在于它不是一个恰当的度量标准,因人而异。例如,我非常喜欢纯粹的data.table解决方案 :)。 - Arun
可读性因人而异,但这并不意味着它不是一个合适的度量标准,对吧?你同意吗?对于浏览我的代码的同事来说,滞后解决方案更容易阅读,不是吗?那么-1和-1混淆呢? - Matthew
我认为这并不必要。你已经展示了你的解决方案(你觉得可以改进,因此提出了问题),并发布了一个更适合你的版本。这是一个很好的答案(我已经点赞了)。 - Arun
显示剩余2条评论

0
现在,通过collapse包中的flagfdifffgrowth功能,针对不规则时间序列和不平衡面板的计算问题提供了一种优雅而快速的通用解决方案。在这里链接可以查看关于滞后不平衡面板的通用答案。
现在,在您的特定应用程序中还有一个额外的罕见事实,即该面板不仅是不平衡的,而且您的时间变量中存在缺失值,这意味着您不知道记录被观察的时间周期。在这种情况下,仅应用collapse::flag是不够的,但是您可以使用collapse::seqid生成一个新的ID变量,将缺失值放置在单独的组中。因此,我的解决方案是:
library(collapse)  
DF = data.frame(id    = c(1,1,1,1,1,2,2),
                date  = c(1992,NA,1991,1990,1994,1992,1991),
                value = c(4.1,4.5,3.3,5.3,3.0,3.2,5.2))

settransform(DF, l_value = flag(value, 1, g = seqid(date, order(id, date)), t = date))
DF
#>   id date value l_value
#> 1  1 1992   4.1     3.3
#> 2  1   NA   4.5      NA
#> 3  1 1991   3.3     5.3
#> 4  1 1990   5.3      NA
#> 5  1 1994   3.0      NA
#> 6  2 1992   3.2     5.2
#> 7  2 1991   5.2      NA

通过 reprex package (v0.3.0) 在2021-07-10创建

我非常有信心这仍然比data.table更快,但我没有测试过。再次说明,对于大多数情况下面板是不平衡的,但记录都由id和时间标识的情况,一个简单的flag(value,1,id,as.integer(date))就可以做得很好并且运行速度非常快。请注意,确保时间变量为整数可以获得效率提升,因为flag将强制非整数时间变量转换为因子,从而也可以消除不规则性。


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