Применить функцию через столбцы в data.table с помощью do.call и .SD в R - PullRequest
0 голосов
/ 08 мая 2018

Я пытаюсь создать вариант pmax / pmin, который работает с дополнительным параметром filter_value в произвольном наборе столбцов, который будет определен с использованием .SD / .SDcols. Первая версия функции ниже жестко кодирует значение фильтра, но работает с .SD:

testFuncV1 <- function(...) { 
  cols <- list(...)
  num_cols <- length(cols)
  num_records <- length(cols[[1]])
  max_records <- c()
  for (record_num in 1:num_records) {
    v <- c()
    for (l in cols) {
      v <- c(v, l[[record_num]])
    }
    filt_v <- Filter(function(x) { x <= 1 }, v)
    if (length(filt_v) == 0) {
      max_records <- c(max_records, NA) 
    } else {
      max_records <- c(max_records, max(filt_v))
    }
  }
  max_records
}

test_dt_v1 <- data.table(a = c(1,3,5), b = c(2,3,-1), c = c(-3, 5, 2))

test_dt_v1[, max_with_filter := do.call(testFuncV1, .SD), .SDcols = c('a', 'b', 'c')]

возвращается:

   a  b  c max_with_filter
1: 1  2 -3               1
2: 3  3  5              NA
3: 5 -1  2              -1

Вторая версия функции, приведенной ниже, принимает второй параметр filter, но я не смог заставить его работать с .SD, и, скорее, мне пришлось передавать отдельные векторы столбцов в виде списка, чтобы получить вещи на работу:

testFuncV2 <- function(cols, filter) { 
  num_cols <- length(cols)
  num_records <- length(cols[[1]])
  max_records <- c()
  for (record_num in 1:num_records) {
    v <- c()
    for (l in cols) {
      v <- c(v, l[[record_num]])
    }
    filt_v <- Filter(function(x) { x <= filter }, v)
    if (length(filt_v) == 0) {
      max_records <- c(max_records, NA) 
    } else {
      max_records <- c(max_records, max(filt_v))
    }
  }
  max_records
}

test_dt_v2 <- data.table(a = c(1,3,5), b = c(2,3,-1), c = c(-3, 5, 2))

test_dt_v2[, max_with_filter := do.call(testFuncV2, list(list(test_dt_v2$a, test_dt_v2$b, test_dt_v2$c), 1))]

также возвращает:

   a  b  c max_with_filter
1: 1  2 -3               1
2: 3  3  5              NA
3: 5 -1  2              -1

В идеале я мог бы либо выяснить подход, который работает с .SD с использованием do.call, либо заменить что-то, что работает с lapply (с которым я тоже экспериментировал, но безрезультатно). Заранее спасибо!

1 Ответ

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

Вот вариант с использованием apply(MARGIN=1, ...)

func <- function(x, threshold) {
    if (any(x <= threshold)) return(max(x[x <= threshold])) 
    NA
}
test_dt_v1[, max_with_filter := apply(.SD, 1, func, threshold=1),
    .SDcols=c("a","b","c")]

Другой вариант, использующий do.call и pmax путем преобразования значений, превышающих 1, в первую очередь NA (идея пришла от линейного максимума для R )

test_dt_v1[, max_with_filter := do.call(pmax, c(`is.na<-`(.SD, .SD>1), na.rm=T))]
...