Как использовать data.table внутри функций и циклов?

Оценивая полезность data.table (vs. dplyr ), критический фактор – это способность использовать его внутри функций и циклов.
Для этого я изменил fragment кода, использованный в этом сообщении: data.table vs dplyr: можно ли что-то сделать хорошо, а другое не может или плохо? так что вместо жестко закодированных имен переменных набора данных («вырезать» и «ценовые» переменные набора данных «бриллианты») он становится набором данных-агностиком – cut-n-paste, готовым для использования внутри любой функции или цикла (когда мы не знаем названия столбцов заранее).

Это исходный код:

 library(data.table) dt <- data.table(ggplot2::diamonds) dt[cut != "Fair", .(mean(price),.N), by = cut] 

Это его совокупность – агностический эквивалент:

 dt <- data.table(diamonds) nVarGroup <- 2 #"cut" nVarMeans <- 7 #"price" strGroupConditions <- levels(dt[[nVarGroup]])[-1] # "Good" "Very Good" "Premium" "Ideal" strVarGroup <- names(dt)[nVarGroup] strVarMeans <- names(dt)[nVarMeans] qAction <- quote(mean(get(strVarMeans))) #! w/o get() it does not work! qGroup <- quote(get(strVarGroup) %in% strGroupConditions) #! w/o get() it does not work! dt[eval(qGroup), .(eval(qAction), .N), by = strVarGroup] 

Примечание (Спасибо за ответ ниже): если вам нужно изменить значение переменной по ссылке, вам нужно использовать () , а не get() , как показано ниже:

 strVarToBeReplaced <- names(dt)[1] dt[eval(qGroup), (strVarToBeReplaced) := eval(qAction), by = strGroup][] 

Теперь: вы можете вырезать-вставить следующий код для всех ваших потребностей цикла:

 for(nVarGroup in 2:4) # Grouped by several categorical values... for(nVarMeans in 5:10) { # ... get means of all numerical parameters strGroupConditions <- levels(dt[[nVarGroup]])[-1] strVarGroup <- names(dt)[nVarGroup] strVarMeans <- names(dt)[nVarMeans] qAction <- quote(mean(get(strVarMeans))) qGroup <- quote(get(strVarGroup) %in% strGroupConditions) p <- dt[eval(qGroup), .(AVE=eval(qAction), COUNT=.N), by = strVarGroup] print(sprintf("nVaGroup=%s, nVarMeans=%s: ", strVarGroup, strVarMeans)) print(p) } 

Мой первый вопрос:
Вышеприведенный код, позволяя требуемые функциональные / циклические потребности, выглядит довольно запутанным. – Он использует разные множественные (возможно, непротиворечивые) неинтуитивные трюки, такие комбинации () , get() , quote() / eval() , [[]] ). Кажется, слишком много для такой простой потребности …

Есть ли еще один лучший способ доступа и изменения значений data.tables в циклах? Возможно, с on= , lapply / .SD / .SDcols ?

Пожалуйста, поделитесь своими идеями ниже. Эта дискуссия направлена ​​на дополнение и консолидацию связанных битов с другими сообщениями (например, перечисленными здесь: как можно полностью работать в data.table в R с именами столбцов в переменных ). В конце концов, было бы здорово создать специальную виньетку для использования data.table в functions и loops .

Второй вопрос:
С другой стороны, проще? – Однако для этого вопроса я установил отдельную запись: проще ли использовать dplyr, чем data.table в функциях и циклах? ,

Это может быть не самое большее, чем data.table или самое быстрое решение, но я бы упорядочил код в этом конкретном цикле следующим образом:

 for(nVarGroup in 2:4) { # Grouped by several categorical values... for(nVarMeans in 5:10) { # ... get means of all numerical parameters strGroupConditions <- levels(dt[[nVarGroup]])[-1] strVarGroup <- names(dt)[nVarGroup] strVarMeans <- names(dt)[nVarMeans] # qAction <- quote(mean(get(strVarMeans))) # qGroup <- quote(get(strVarGroup) %in% strGroupConditions) # p <- dt[eval(qGroup), .(AVE = eval(qAction), COUNT = .N), by = strVarGroup] setkeyv(dt, strVarGroup) p <- dt[strGroupConditions, .(AVE = lapply(.SD, mean), COUNT = .N), by = strVarGroup, .SDcols = strVarMeans] print(sprintf("nVaGroup = %s, nVarMeans = %s", strVarGroup, strVarMeans)) print(p) } } 

Я оставил старый код в качестве комментария для справки.

qAction заменяется использованием lapply(.SD, mean) вместе с параметром .SDcols .

qGroup для подмножества строк заменяется комбинацией установки ключа и предоставления вектора желаемых значений в качестве параметра i .


В случае более сложного выражения подмножества я попытался бы использовать неэквивалентные (или условные) объединения, используя синтаксис on= .

Или следуйте совету Мэтта Доула, чтобы создать одно выражение для оценки, «похоже на построение динамического оператора SQL для отправки на сервер».

Мэтт предложил создать вспомогательную функцию

 EVAL <- function(...) eval(parse(text = paste0(...)), envir = parent.frame(2)) 

которые могут быть объединены с «строчной интерполяцией строки типа gsubfn пакета gsubfn для улучшения удобочитаемости решения EVAL », как предположил Г. Гротендик .

При этом код цикла будет в конечном итоге:

 EVAL <- function(...) eval(parse(text = paste0(...)), envir = parent.frame(2)) library(gsubfn) for(nVarGroup in 2:4) { # Grouped by several categorical values... for(nVarMeans in 5:10) { # ... get means of all numerical parameters strGroupConditions = levels(dt[[nVarGroup]])[-1] strVarGroup = names(dt)[nVarGroup] strVarMeans = names(dt)[nVarMeans] p <- fn$EVAL("dt[$strVarGroup %in% strGroupConditions, .(AVE=mean($strVarMeans), COUNT=.N), by = strVarGroup]" ) print(sprintf("nVaGroup = %s, nVarMeans = %s", strVarGroup, strVarMeans)) print(p) } } 

Теперь оператор data.table выглядит примерно так же, как «родной» оператор, за исключением того, что $strVarGroup и $strVarMeans используются там, где упоминается содержимое переменных.


С версией 1.1.0 (выпуск CRAN в 2016-08-19) пакет stringr получил функцию строковой интерполяции str_interp() которая является альтернативой пакету gsubfn .

С помощью str_interp() центральный оператор в цикле for будет

 p <- EVAL(stringr::str_interp( "dt[${strVarGroup} %in% strGroupConditions, .(AVE=mean(${strVarMeans}), COUNT=.N), by = strVarGroup]" )) 

и вызов library(gsubfn) можно удалить.

ОП запросил dataset-агностический эквивалент для группировки и агрегации.

С версией разработки 1.10.5 в data.table появились новые функции data.table Sets : rollup() , cube() и groupingsets() которые позволяют агрегировать на разных уровнях группировки сразу же, производя подтаблицы и общую сумму.

Добавленный уровень абстракции можно использовать для подхода, основанного на наборе данных . Промежуточные суммы, которые вычисляются с использованием двойного вложенного цикла в примере OP, могут быть получены также

 library(data.table) # version 1.10.5 required dt = data.table(ggplot2::diamonds) groupingsets(dt, c(lapply(.SD, mean), list(COUNT = .N)), by = names(dt)[2:4], .SDcols = 5:10, id = FALSE, sets = as.list(names(dt)[2:4])) 
  cut color clarity depth table price xyz COUNT 1: Ideal NA NA 61.70940 55.95167 3457.542 5.507451 5.520080 3.401448 21551 2: Premium NA NA 61.26467 58.74610 4584.258 5.973887 5.944879 3.647124 13791 3: Good NA NA 62.36588 58.69464 3928.864 5.838785 5.850744 3.639507 4906 4: Very Good NA NA 61.81828 57.95615 3981.760 5.740696 5.770026 3.559801 12082 5: Fair NA NA 64.04168 59.05379 4358.758 6.246894 6.182652 3.982770 1610 6: NA E NA 61.66209 57.49120 3076.752 5.411580 5.419029 3.340689 9797 7: NA I NA 61.84639 57.57728 5091.875 6.222826 6.222730 3.845411 5422 8: NA J NA 61.88722 57.81239 5323.818 6.519338 6.518105 4.033251 2808 9: NA H NA 61.83685 57.51781 4486.669 5.983335 5.984815 3.695965 8304 10: NA F NA 61.69458 57.43354 3724.886 5.614961 5.619456 3.464446 9542 11: NA G NA 61.75711 57.28863 3999.136 5.677543 5.680192 3.505021 11292 12: NA D NA 61.69813 57.40459 3169.954 5.417051 5.421128 3.342827 6775 13: NA NA SI2 61.77217 57.92718 5063.029 6.401370 6.397826 3.948478 9194 14: NA NA SI1 61.85304 57.66254 3996.001 5.888383 5.888256 3.639845 13065 15: NA NA VS1 61.66746 57.31515 3839.455 5.572178 5.581828 3.441007 8171 16: NA NA VS2 61.72442 57.41740 3924.989 5.657709 5.658859 3.491478 12258 17: NA NA VVS2 61.66378 57.02499 3283.737 5.218454 5.232118 3.221465 5066 18: NA NA VVS1 61.62465 56.88446 2523.115 4.960364 4.975075 3.061294 3655 19: NA NA I1 62.73428 58.30378 3924.169 6.761093 6.709379 4.207908 741 20: NA NA IF 61.51061 56.50721 2864.839 4.968402 4.989827 3.061659 1790 

Таким образом, нам не нужно знать имена столбцов. Однако мы должны указать, какие столбцы группировать и какие столбцы агрегировать.

Давайте будем гением компьютера.