2016-12-29 13 views
3

私は集計関数をラップする方法を模索していますが(実際にはどのような関数でも可能です)、data.table(1つのdplyrの例もあります)とr data.table言語上の関数プログラミング/メタプログラミング/コンピューティング

  • パフォーマンス(data.table潜在的な最適化に関しては、実装事項が適用される場合がありますん)
  • 可読性(に関して関数型プログラミング/メタプログラミングのためのベストプラクティスに思ってすることはで一般的に合意された標準などがありましたほとんどのパッケージはdata.tableを利用しています)
  • 容易性neralization基本的なアプリケーションは、寸法は、によって両方のそれぞれの得られた変数名を集約するために、柔軟テーブルを集約すなわち凝集する変数をパラメータ化することである

(方法メタプログラミングの違いは、「一般化」があります)および集約関数。

  1. fn_dt_agg1(ここで私は集計関数をパラメータ化方法を見つけ出すことができませんでした)
  2. @jangoreckiに触発fn_dt_agg2( ':私は(ほぼ)data.table 3と1つのdplyrの方法で同じ機能を実装していますsの彼は、「言語上のコンピューティング」と呼んで答えhere
  3. メタプログラミングの別のアプローチであるように思わ@Arunの答えhereに触発fn_dt_agg3()
  4. fn_df_agg1(dplyrで同じの私の謙虚なアプローチ)

ライブラリ

library(data.table) 
library(dplyr) 

データ

n_size <- 1*10^6 
sample_metrics <- sample(seq(from = 1, to = 100, by = 1), n_size, rep = T) 
sample_dimensions <- sample(letters[10:12], n_size, rep = T) 
df <- 
    data.frame(
    a = sample_metrics, 
    b = sample_metrics, 
    c = sample_dimensions, 
    d = sample_dimensions, 
    x = sample_metrics, 
    y = sample_dimensions, 
    stringsAsFactors = F) 

dt <- as.data.table(df) 

実装

1 fn_dt_agg1

fn_dt_agg1 <- 
    function(dt, metric, metric_name, dimension, dimension_name) { 

    temp <- dt[, setNames(lapply(.SD, function(x) {sum(x, na.rm = T)}), 
         metric_name), 
      keyby = dimension, .SDcols = metric] 
    temp[] 
} 

res_dt1 <- 
    fn_dt_agg1(
    dt = dt, metric = c("a", "b"), metric_name = c("a", "b"), 
    dimension = c("c", "d"), dimension_name = c("c", "d")) 

2 fn_dt_agg2

fn_dt_agg2 <- 
    function(dt, metric, metric_name, dimension, dimension_name, 
      agg_type) { 

    j_call = as.call(c(
    as.name("."), 
    sapply(setNames(metric, metric_name), 
      function(var) as.call(list(as.name(agg_type), 
             as.name(var), na.rm = T)), 
      simplify = F) 
    )) 

    dt[, eval(j_call), keyby = dimension][] 
} 

res_dt2 <- 
    fn_dt_agg2(
    dt = dt, metric = c("a", "b"), metric_name = c("a", "b"), 
    dimension = c("c", "d"), dimension_name = c("c", "d"), 
    agg_type = c("sum")) 

all.equal(res_dt1, res_dt2) 
#TRUE 

3 fn_dt_agg3

fn_dt_agg3 <- 
    function(dt, metric, metric_name, dimension, dimension_name, agg_type) { 

    e <- eval(parse(text=paste0("function(x) {", 
           agg_type, "(", "x, na.rm = T)}"))) 

    temp <- dt[, setNames(lapply(.SD, e), 
         metric_name), 
      keyby = dimension, .SDcols = metric] 
    temp[] 
} 

res_dt3 <- 
    fn_dt_agg3(
    dt = dt, metric = c("a", "b"), metric_name = c("a", "b"), 
    dimension = c("c", "d"), dimension_name = c("c", "d"), 
    agg_type = "sum") 

all.equal(res_dt1, res_dt3) 
#TRUE 

4 fn_df_agg1

fn_df_agg1 <- 
    function(df, metric, metric_name, dimension, dimension_name, agg_type) { 

    all_vars <- c(dimension, metric) 
    all_vars_new <- c(dimension_name, metric_name) 
    dots_group <- lapply(dimension, as.name) 

    e <- eval(parse(text=paste0("function(x) {", 
           agg_type, "(", "x, na.rm = T)}"))) 

    df %>% 
     select_(.dots = all_vars) %>% 
     group_by_(.dots = dots_group) %>% 
     summarise_each_(funs(e), metric) %>% 
     rename_(.dots = setNames(all_vars, all_vars_new)) 
} 

res_df1 <- 
    fn_df_agg1(
    df = df, metric = c("a", "b"), metric_name = c("a", "b"), 
    dimension = c("c", "d"), dimension_name = c("c", "d"), 
    agg_type = "sum") 

all.equal(res_dt1, as.data.table(res_df1)) 
#"Datasets has different keys. 'target': c, d. 'current' has no key." 

ベンチマーキング

私はベンチマークの専門家ではないが、パフォーマンスに関する問題を明らかにしている可能性のある4つの実装すべてのベンチマークを実行した私が一般的に合意したベストプラクティスを適用していない場合)。私はfn_dt_agg1が1つ少ないパラメータ(集約関数)を持つので最も速くなることを期待していましたが、それはかなりの影響を及ぼさないようです。私はまた、比較的遅いdplyr機能にも驚いていましたが、これは私の目的のために悪い設計選択のためかもしれません。

library(microbenchmark) 
bench_res <- 
    microbenchmark(
    fn_dt_agg1 = 
     fn_dt_agg1(
     dt = dt, metric = c("a", "b"), 
     metric_name = c("a", "b"), 
     dimension = c("c", "d"), 
     dimension_name = c("c", "d")), 
    fn_dt_agg2 = 
     fn_dt_agg2(
     dt = dt, metric = c("a", "b"), 
     metric_name = c("a", "b"), 
     dimension = c("c", "d"), 
     dimension_name = c("c", "d"), 
     agg_type = c("sum")), 
    fn_dt_agg3 = 
     fn_dt_agg3(
     dt = dt, metric = c("a", "b"), 
     metric_name = c("a", "b"), 
     dimension = c("c", "d"), 
     dimension_name = c("c", "d"), 
     agg_type = c("sum")), 
    fn_df_agg1 = 
     fn_df_agg1(
     df = df, metric = c("a", "b"), metric_name = c("a", "b"), 
     dimension = c("c", "d"), dimension_name = c("c", "d"), 
     agg_type = "sum"), 
    times = 100L) 

bench_res 

# Unit: milliseconds 
#  expr  min  lq  mean median  uq  max neval 
# fn_dt_agg1 28.96324 30.49507 35.60988 32.62860 37.43578 140.32975 100 
# fn_dt_agg2 27.51993 28.41329 31.80023 28.93523 33.17064 84.56375 100 
# fn_dt_agg3 25.46765 26.04711 30.11860 26.64817 30.28980 153.09715 100 
# fn_df_agg1 88.33516 90.23776 97.84826 94.28843 97.97154 172.87838 100 

他のリソース

+0

re:agg2 "彼は「言語上のコンピューティング」と呼んでいます - 私はそうではありませんが、あなたが底にリンクしている公式の定義です。 – jangorecki

答えて

4

私はeval(parse())はお勧めしません。

fn_dt_agg4 <- 
    function(dt, metric, metric_name, dimension, dimension_name, agg_type) { 

    e <- function(x) getFunction(agg_type)(x, na.rm = T) 

    temp <- dt[, setNames(lapply(.SD, e), 
          metric_name), 
       keyby = dimension, .SDcols = metric] 
    temp[] 
    } 

これにより、セキュリティリスクが回避されます。

PS:options("datatable.verbose" = TRUE)を設定すると、最適化に関するdata.tableの動作を確認できます。

+0

'getFunction'と' match.fun'の間には重要な違いがありますか? – Axeman

+0

私は 'getFunction'について知らなかった。それ以外のところでそれを見たことはありません。なぜ 'eval(parse))'が推奨されないのでしょうか?私は@マットダウエル[ここ]から他の答えでそれを見ていた(http://stackoverflow.com/questions/10675182/in-r-data-table-how-do-i-pass-variable-parameters-to-an -Expression)と@Arun [here](http://stackoverflow.com/questions/26883859/using-eval-in-data-table?rq=1) – Triamus

+1

@Axeman私は分かりません。後者は、文字以外の入力が可能です。 – Roland