Как использовать 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]
Это его совокупность – агностический эквивалент:
- Определение типов данных столбцов фрейма данных
- Изменение формы кадра с дубликатами
- Установка нескольких регрессионных моделей с dplyr
- Подключение к SQL Server RODBC
- Использование функций из нескольких столбцов в вызове dplyr mutate_at
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 в функциях и циклах? ,
- Замена значений из столбца с использованием условия в R
- Использование списков внутри столбцов data.table
- Передача аргументов командной строки в R CMD BATCH
- Как заполнить geom_polygon разными цветами выше и ниже y = 0?
- Формат даты-времени как сезонов в R?
- R и объектно-ориентированное программирование
- Соберите несколько наборов столбцов
- Сгенерировать фиктивную переменную
Это может быть не самое большее, чем 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
Таким образом, нам не нужно знать имена столбцов. Однако мы должны указать, какие столбцы группировать и какие столбцы агрегировать.