2017-02-21 10 views
3

При оценке полезности data.table (против dplyr) решающим фактором является возможность использовать его в рамках функций и циклов.
Для этого я изменил фрагмент кода, используемый в этом сообщении: data.table vs dplyr: can one do something well the other can't or does poorly?, так что вместо жестко закодированных имен переменных набора данных («вырезать» и «ценовые» переменные набора данных «бриллианты»), он становится агрегированным по дате - cut-n-paste готова для использования внутри любой функции или цикла (если мы не знаем названия столбцов заранее).Как использовать data.table внутри функций и циклов?

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

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?

Пожалуйста, поделитесь своими идеями ниже. Это обсуждение предназначено для дополнения и консолидации связанных бит с других должностей (например, перечисленных здесь: How can one work fully generically in data.table in R with column names in variables). В конце концов, было бы здорово создать специальную виньетку для использования data.table в пределах functions и loops.

Второй вопрос:
Это dplyr проще для этой цели? - Для этого вопроса, однако, я установил отдельный пост: Is dplyr easier than data.table to be used within functions and loops?.

+1

Если вы хотите отказаться от обмана или плохих имен столбцов и/или очистить их сначала, я думаю, это проще. – Frank

+1

предложение: было бы лучше разместить новый qn, чем изменить предыдущий qn, потому что новый не будет получать большую видимость.try 'dt [eval (qGroup), (strVarToBeReplaced): = 999, by = strGroup] []' вместо – chinsoon12

+0

У меня нет вашего вопроса. Как ваш код «набор данных-агностик» более общий, чем просто использование data.table напрямую? – Hugh

ответ

2

Это может быть не самым 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=.

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

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

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

, которые могут быть объединены с «квази-PERL типа строки интерполяции fn$ из gsubfn пакета, чтобы улучшить читаемость решения EVAL» как suggested by G. Grothendieck.

При этом, код цикла становится в конце концов:

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 (КРАН-релиз на 2016-08-19), то stringr пакет получил функцию строки интерполяции str_interp(), которая является альтернативой gsubfn пакета здесь.

С str_interp(), центральный оператор в цикл стал бы

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

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

1

ОП запросил набор данных-агностик для группировки и агрегации.

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

Добавленный уровень абстракции может использоваться для подхода dataset-agnostic. Промежуточные итоги, которые вычисляются с использованием двойного цикла вложенной for в примере 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  x  y  z 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 

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

Смежные вопросы