melt.data.table na.rm 作为 measure.vars 列表的第一个元素

melt.data.table with na.rm for first element of list of measure.vars

我想探索 melt data.table 的最佳方法 na.rm 仅适用于 measure.vars.[=38 列表的第一个元素=]

我有一个data.table如下:

library(data.table)
library(lubridate)

dt.master <- data.table(user = seq(1,5),
                    visit_id = c(2,4,NA,4,8),
                    visit_date = c(dmy("10/02/2018"), dmy("11/04/2018"), NA, dmy("02/03/2018"), NA),
                    offer_id = c(1,3,NA,NA,NA),
                    offer_date = c(dmy("15/02/2018"), dmy("18/04/2018"), NA, NA, NA))

dt.master:

   user visit_id visit_date offer_id offer_date
1:    1        2 2018-02-10        1 2018-02-15
2:    2        4 2018-04-11        3 2018-04-18
3:    3       NA       <NA>       NA       <NA>
4:    4        4 2018-03-02       NA       <NA>
5:    5        8       <NA>       NA       <NA>

我想为每个用户获取商业 activity 的 "story"(即:他们的访问量和报价)。

dt.melted <- melt(dt.master,
                  id.vars = "user",
                  measure.vars = list(c("visit_id", "offer_id"), c("visit_date", "offer_date")),
                  variable.name = "level",
                  value.name = c("level_id", "level_date"))

dt.melted:

    user level level_id level_date
 1:    1     1        2 2018-02-10
 2:    2     1        4 2018-04-11
 3:    3     1       NA       <NA>
 4:    4     1        4 2018-03-02
 5:    5     1        8       <NA>
 6:    1     2        1 2018-02-15
 7:    2     2        3 2018-04-18
 8:    3     2       NA       <NA>
 9:    4     2       NA       <NA>
10:    5     2       NA       <NA>

但是,我不希望 NAs 出现在 level_id 列中,即:

   user level level_id level_date
1:    1     1        2 2018-02-10
2:    2     1        4 2018-04-11
3:    4     1        4 2018-03-02
4:    5     1        8       <NA>
5:    1     2        1 2018-02-15
6:    2     2        3 2018-04-18

遗憾的是,样本的数据质量实在是太差了,所以level_date并不总是可用的。因此,na.rm = T 无效,因为我会得到:

dt.melted.na <- melt(dt.master,
                     id.vars = "user",
                     measure.vars = list(c("visit_id", "offer_id"), c("visit_date", "offer_date")),
                     variable.name = "level",
                     value.name = c("level_id", "level_date"),
                     na.rm = TRUE)

dt.melted.na:

   user level level_id level_date
1:    1     1        2 2018-02-10
2:    2     1        4 2018-04-11
3:    4     1        4 2018-03-02
4:    1     2        1 2018-02-15
5:    2     2        3 2018-04-18

有没有办法只对 measure.vars 中列表的第一个元素使用 na.rm = TRUE 我目前正在探索其他解决方法(例如填充visit_date and offer_date with "false" dates when visit_id and offer_id are available),但我想知道是否有一个优雅的解决方案。

如果 melt()na.rm 参数采用一个布尔值向量,一个代表 measure.vars 列表中的每个元素,例如

melt(dt.master,
     id.vars = "user",
     measure.vars = list(c("visit_id", "offer_id"), c("visit_date", "offer_date")),
     variable.name = "level",
     value.name = c("level_id", "level_date"),
     na.rm = c(TRUE, FALSE))   # not possible with data.table v1.11.0

由于此功能尚未实现,另一种方法是 在使用 na.rm = TRUE 重塑为长格式后添加缺失的行。 OP 有 ,由于问题大小和内存限制,必须使用 na.rm = TRUE

rbind(
  dt.melted.na,
  dt.master[!is.na(visit_id) & is.na(visit_date), .(user, level = 1L, level_id = visit_id)],
  dt.master[!is.na(offer_id) & is.na(offer_date), .(user, level = 2L, level_id = offer_id)],
  fill = TRUE
)
   user level level_id level_date
1:    1     1        2 2018-02-10
2:    2     1        4 2018-04-11
3:    4     1        4 2018-03-02
4:    1     2        1 2018-02-15
5:    2     2        3 2018-04-18
6:    5     1        8       <NA>

这种方法相当笨拙和冗长,但可能有助于克服内存限制。它本质上是对缺失行的重塑 "by hand"。

还有另一种可能不那么冗长的选择:

incomplete_rows <- 
  melt(dt.master[!is.na(visit_id) & is.na(visit_date) | !is.na(offer_id) & is.na(offer_date)],
       id.vars = "user",
       measure.vars = list(c("visit_id", "offer_id"), c("visit_date", "offer_date")),
       variable.name = "level",
       value.name = c("level_id", "level_date"))[!is.na(level_id)]
rbind(
  dt.melted.na,
  incomplete_rows
)

这里,所有行都是从 dt.master 中挑选出来的,这些行是部分不完整的,重新整形为长格式,然后过滤。如果这只涉及 dt.master 行的一小部分,这也可能适用于有限的内存。