R data.table 按类别递增并将 NA 设置为最后一个非缺失值

R data.table increment by category and set NA to last non-missing value

[背景]

我有一些用户在线活动的数据:

[目标]

我想为每个 pageType 生成一个新列,并计算完全相同类型的先前页面浏览量(不包括当前页面浏览量)。

[示例数据]

要生成实际数据的样本:

library(data.table)
DT <- data.table("userId"=rep(1:3, each=10),
                 "pageType"=c("home", "content", "home", "content", "home", "home", "content", "content", "home", "home",
                              "content", "content", "home", "home", "content", "home", "home", "content", "home", "content",
                              "home", "home", "content", "content", "home", "home", "content", "content", "home", "content"))

> DT
    userId pageType
 1:      1     home
 2:      1  content
 3:      1     home
 4:      1  content
 5:      1     home
 6:      1     home
 7:      1  content
 8:      1  content
 9:      1     home
10:      1     home
...    ...      ...

[我的尝试]

我试过两种方法解决这个问题,但是都太慢了。我也觉得我的解决方案没有使用 data.table 它设计的方式。

解决方案一

  1. 过滤 pageType 并增加 userId
  2. 为其他pageType设置缺失值。

代码如下:

FixPageView <- function(data, type) {
  val <- 0
  for (i in 1:nrow(data)) {
    if (is.na(data[[type]][i])) {
      set(data, i, type, val)
    } else {
      val <- data[[type]][i]
    }
  }
}
DT[pageType=="home", numHomePagesViewed:=0:(.N-1), by=userId]
DT[pageType=="content", numContentPagesViewed:=0:(.N-1), by=userId]
FixPageView(DT, "numHomePagesViewed")
FixPageView(DT, "numContentPagesViewed")

> DT
    userId pageType numHomePagesViewed numContentPagesViewed
 1:      1     home                  0                     0
 2:      1  content                  0                     0
 3:      1     home                  1                     0
 4:      1  content                  1                     1
 5:      1     home                  2                     1
 6:      1     home                  3                     1
 7:      1  content                  3                     2
 8:      1  content                  3                     3
 9:      1     home                  4                     3
10:      1     home                  5                     3
...    ...      ...                ...                   ...

方案二

双重for循环并逐行设置。

DT[, numHomePagesViewed := 0L][, numContentPagesViewed := 0L]
for (i in unique(DT$userId)) {
  home_inc <- -1L
  content_inc <- -1L
  for (j in 1L:nrow(DT[userId==i])) {
    if (DT$pageType[(i-1L)*10L + j] == "home") {
      home_inc <- home_inc + 1L
      set(DT, (i-1L)*10L + j, "numHomePagesViewed", home_inc)
    } else {
      set(DT, (i-1L)*10L + j, "numHomePagesViewed", max(0, home_inc))
    }
    if (DT$pageType[(i-1L)*10L + j] == "content") {
      content_inc <- content_inc + 1L
      set(DT, (i-1L)*10L + j, "numContentPagesViewed", content_inc)
    } else {
      set(DT, (i-1L)*10L + j, "numContentPagesViewed", max(0, content_inc))
    }
  }
}

> DT
    userId pageType numHomePagesViewed numContentPagesViewed
 1:      1     home                  0                     0
 2:      1  content                  0                     0
 3:      1     home                  1                     0
 4:      1  content                  1                     1
 5:      1     home                  2                     1
 6:      1     home                  3                     1
 7:      1  content                  3                     2
 8:      1  content                  3                     3
 9:      1     home                  4                     3
10:      1     home                  5                     3
...    ...      ...                ...                   ...

[问题]

  1. 我可以做些什么来提高速度?
  2. 有没有更“data.table”的方法解决这个问题?

我会尝试:

DT[,lapply(unique(pageType),
   function(x) pmax(cumsum(pageType==x)-1,0)),by=userId]

接下来,您必须重命名获得的列。

按照评论中的建议,您可以用一行分配名称:

DT[, paste0("num",unique(DT$pageType),"PagesViewed") := 
      lapply(unique(pageType), function(x) pmax(cumsum(pageType==x)-1,0)), by=userId]