melt.data.table с na.rm для первого элемента списка measure.vars - PullRequest
0 голосов
/ 09 мая 2018

Я хотел бы изучить лучший способ для melt a data.table с na.rm, применяющим только к первому элементу списка measure.vars.

У меня есть 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>

Я хочу получить для каждого пользователя «историю» коммерческой деятельности (то есть их посещения и их предложения).

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>

Однако я не хочу, чтобы NA s появлялись в столбце 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

Есть ли способ использовать na.rm = TRUE только для первого элемента списка в measure.vars? В настоящее время я изучаю другие обходные пути (например, заполнение visit_date и offer_date с помощью "false" «даты, когда visit_id и offer_id доступны), но я хотел бы знать, если есть элегантное решение.

1 Ответ

0 голосов
/ 09 мая 2018

Элегантное решение было бы, если бы параметр na.rm для melt() принял бы вектор логических значений, по одному для каждого элемента в списке 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>

Этот подход довольно хакерский и многословный, но может помочь преодолеть ограничения памяти. По сути, это изменение формы «вручную» для пропущенных строк.

Существует еще одна альтернатива, которая может быть менее многословной:

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, это может также работать с ограниченной памятью.

...