web-dev-qa-db-ja.com

dplyr関数に引数を渡す

Sepal.Lengthのどの値がSepal.Widthの複数の値に関連付けられているかを検出するdplyrを使用して、次の計算をパラメーター化します。

library(dplyr)

iris %>%
    group_by(Sepal.Length) %>%
    summarise(n.uniq=n_distinct(Sepal.Width)) %>%
    filter(n.uniq > 1)

通常、私はこのようなものを書くでしょう:

not.uniq.per.group <- function(data, group.var, uniq.var) {
    iris %>%
        group_by(group.var) %>%
        summarise(n.uniq=n_distinct(uniq.var)) %>%
        filter(n.uniq > 1)
}

ただし、dplyr非標準の評価 を使用するため、このアプローチではエラーがスローされます。この関数はどのように書かれるべきですか?

50
asnr

dplyr関数の標準評価版を使用する必要があり(関数名に '_'を追加するだけで、つまり_group_by__&_summarise__)、関数に文字列を渡すことができます。その後、シンボルに変換する必要があります。 summarise_の引数をパラメーター化するには、lazyevalパッケージで定義されているinterp()を使用する必要があります。具体的に:

_library(dplyr)
library(lazyeval)

not.uniq.per.group <- function(df, grp.var, uniq.var) {
    df %>%
        group_by_(grp.var) %>%
        summarise_( n_uniq=interp(~n_distinct(v), v=as.name(uniq.var)) ) %>%
        filter(n_uniq > 1)
}

not.uniq.per.group(iris, "Sepal.Length", "Sepal.Width")
_

dplyrの最近のバージョンでは、dplyr関数の標準評価バージョンが "soft deprecated" であり、非標準評価が優先されることに注意してください。

非標準評価での作業の詳細については、 Programming with dplyr vignette を参照してください。

48
asnr

0.5までの古いdplyrバージョンと同様に、新しいdplyrには標準評価(SE)と非標準評価(NSE)の両方の機能があります。しかし、それらは以前とは異なって表現されています。

NSE関数が必要な場合は、 裸の式を渡し、enquoを使用してそれらをquosuresとしてキャプチャします 。 SE関数が必要な場合は、引用符(または記号)を直接渡し、dplyr呼び出しで引用符を外します。質問に対するSEソリューションは次のとおりです。

_library(tidyverse)
library(rlang)

f1 <- function(df, grp.var, uniq.var) {
   df %>%
       group_by(!!grp.var) %>%
       summarise(n_uniq = n_distinct(!!uniq.var)) %>%
       filter(n_uniq > 1)  
}

a <- f1(iris, quo(Sepal.Length), quo(Sepal.Width))
b <- f1(iris, sym("Sepal.Length"), sym("Sepal.Width"))
identical(a, b)
#> [1] TRUE
_

SEバージョンで文字列引数を使用できるようにする方法に注意してください-sym()を使用して最初にそれらをシンボルに変換するだけです。詳細については、 dplyrを使用したプログラミング ビネットを参照してください。

18
Paul

dplyrの開発版(まもなくリリースされる0.6.0)、変数を渡すためにわずかに異なる構文を使用することもできます。

f1 <- function(df, grp.var, uniq.var) {
   grp.var <- enquo(grp.var)
   uniq.var <- enquo(uniq.var)

   df %>%
       group_by(!!grp.var) %>%
       summarise(n_uniq = n_distinct(!!uniq.var)) %>%
       filter(n_uniq >1)  


}

res2 <- f1(iris, Sepal.Length, Sepal.Width) 
res1 <- not.uniq.per.group(iris, "Sepal.Length", "Sepal.Width")
identical(res1, res2)
#[1] TRUE

ここで、enquoは引数を取り、関数引数を遅延して要約内で評価することにより、値をquosure(ベースRでの置換に類似)として返します。 UQ)そのため、評価されます。

11
akrun

dplyr(0.7.4)の現在のバージョンでは、標準の評価関数バージョンの使用(関数名に「_」を追加、例えばgroup_by_)は非推奨です。代わりに、関数を記述するときにtidyevalを使用する必要があります。

関数がどのように見えるかの例を次に示します。

# definition of your function
not.uniq.per.group <- function(data, group.var, uniq.var) {
  # enquotes variables to be used with dplyr-functions
  group.var <- enquo(group.var)
  uniq.var <- enquo(uniq.var)

  # use '!!' before parameter names in dplyr-functions
  data %>%
    group_by(!!group.var) %>%
    summarise(n.uniq=n_distinct(!!uniq.var)) %>%
    filter(n.uniq > 1)
}

# call of your function
not.uniq.per.group(iris, Sepal.Length, Sepal.Width)

すべての詳細を知りたい場合は、dplyr-teamによる excellent vignette がこれの仕組みについてです。

5

過去に、主キー以外のすべての列を探索し、グループごとに複数の一意の値を検索することを除いて、あなたがやっていることと似たようなことをする関数を作成しました。

find_dups = function(.table, ...) {
  require(dplyr)
  require(tidyr)
  # get column names of primary key
  pk <- .table %>% select(...) %>% names
  other <- names(.table)[!(names(.table) %in% pk)]
  # group by primary key,
  # get number of rows per unique combo,
  # filter for duplicates,
  # get number of distinct values in each column,
  # gather to get df of 1 row per primary key, other column,
  # filter for where a columns have more than 1 unique value,
  # order table by primary key
  .table %>%
    group_by(...) %>%
    mutate(cnt = n()) %>%
    filter(cnt > 1) %>%
    select(-cnt) %>%
    summarise_each(funs(n_distinct)) %>%
    gather_('column', 'unique_vals', other) %>%
    filter(unique_vals > 1) %>%
    arrange(...) %>%
    return
  # Final dataframe:
  ## One row per primary key and column that creates duplicates.
  ## Last column indicates how many unique values of
  ## the given column exist for each primary key.
}

この関数は、パイピング演算子でも機能します。

dat %>% find_dups(key1, key2)

lazyevalを使用して匿名関数を呼び出してからdoを使用すると、getを回避できます。このソリューションをより一般的に使用して、複数の集計を使用できます。私は通常、関数を別々に書きます。

library(dplyr)

not.uniq.per.group <- function(df, grp.var, uniq.var) {
  df %>%
    group_by_(grp.var) %>%
    do((function(., uniq.var) {
      with(., data.frame(n_uniq = n_distinct(get(uniq.var))))
    }      
  )(., uniq.var)) %>%
  filter(n_uniq > 1)
}

not.uniq.per.group(iris, "Sepal.Length", "Sepal.Width")
2
bmayer

rlang 0.4を使用してcurly curly{{疑似演算子:

library(dplyr)

not.uniq.per.group <- function(data, group.var, uniq.var) {
  data %>%
    group_by({{group.var}}) %>%
    summarise(n.uniq=n_distinct({{uniq.var}})) %>%
    filter(n.uniq > 1)
}

iris %>% not.uniq.per.group(Sepal.Length, Sepal.Width)
#> # A tibble: 25 x 2
#>    Sepal.Length n.uniq
#>           <dbl>  <int>
#>  1          4.4      3
#>  2          4.6      4
#>  3          4.8      3
#>  4          4.9      5
#>  5          5        8
#>  6          5.1      6
#>  7          5.2      4
#>  8          5.4      4
#>  9          5.5      6
#> 10          5.6      5
#> # ... with 15 more rows
2