web-dev-qa-db-ja.com

関数にdata.frame列名を渡します

Data.frame(x)とcolumnを受け入れる関数を作成しようとしています。この関数はxに対していくつかの計算を実行し、後で別のdata.frameを返します。列名を関数に渡すためのベストプラクティスメソッドに固執しています。

以下の2つの最小例fun1およびfun2は、max()を例として使用して、x$columnで操作を実行できる望ましい結果を生成します。しかし、どちらも一見(少なくとも私には)優雅さに依存しています

  1. substitute()および場合によってはeval()の呼び出し
  2. 列名を文字ベクトルとして渡す必要があります。
fun1 <- function(x, column){
  do.call("max", list(substitute(x[a], list(a = column))))
}

fun2 <- function(x, column){
  max(eval((substitute(x[a], list(a = column)))))
}

df <- data.frame(B = rnorm(10))
fun1(df, "B")
fun2(df, "B")

たとえば、関数をfun(df, B)として呼び出すことができます。私が検討したが試していない他のオプション:

  • columnを列番号の整数として渡します。これはsubstitute()を回避すると思います。理想的には、関数はどちらでも受け入れることができます。
  • with(x, get(column))、しかし、たとえそれが機能していても、substituteが必要だと思う
  • formula()match.call()を使用してください。どちらも私は経験がありません。

サブクエストdo.call()eval()よりも優先されますか?

105
kmm

列名を直接使用できます:

df <- data.frame(A=1:10, B=2:11, C=3:12)
fun1 <- function(x, column){
  max(x[,column])
}
fun1(df, "B")
fun1(df, c("B","A"))

代替、評価などを使用する必要はありません。

目的の関数をパラメーターとして渡すこともできます。

fun1 <- function(x, column, fn) {
  fn(x[,column])
}
fun1(df, "B", max)

または、[[を使用すると、一度に1つの列を選択することもできます。

df <- data.frame(A=1:10, B=2:11, C=3:12)
fun1 <- function(x, column){
  max(x[[column]])
}
fun1(df, "B")
91
Shane

この答えは、既存の答えと同じ要素の多くをカバーしますが、この問題(関数に列名を渡す)が頻繁に発生するため、物事をより包括的にカバーする答えが必要です。

非常に単純なデータフレームがあるとします。

dat <- data.frame(x = 1:4,
                  y = 5:8)

そして、列zxの合計である新しい列yを作成する関数を作成したいと思います。

ここで非常に一般的な障害は、自然な(しかし正しくない)試みがしばしば次のように見えることです:

foo <- function(df,col_name,col1,col2){
      df$col_name <- df$col1 + df$col2
      df
}

#Call foo() like this:    
foo(dat,z,x,y)

ここでの問題は、df$col1が式col1を評価しないことです。 dfで文字通りcol1と呼ばれる列を探すだけです。この動作については、「[再帰的(リストのような)オブジェクト」セクションの?Extractで説明しています。

最も簡単で最も推奨される解決策は、単に$から[[に切り替えて、関数の引数を文字列として渡すことです。

new_column1 <- function(df,col_name,col1,col2){
    #Create new column col_name as sum of col1 and col2
    df[[col_name]] <- df[[col1]] + df[[col2]]
    df
}

> new_column1(dat,"z","x","y")
  x y  z
1 1 5  6
2 2 6  8
3 3 7 10
4 4 8 12

これは、最も困難な方法であるため、「ベストプラクティス」と見なされることがよくあります。列名を文字列として渡すことは、取得できるほど明確です。

次の2つのオプションはより高度です。多くの一般的なパッケージはこれらの種類の手法を使用しますが、それらを使用するwellは微妙な複雑さと予期しない障害点をもたらす可能性があるため、より多くの注意とスキルが必要です。 This HadleyのAdvanced R本のセクションは、これらの問題のいくつかの優れたリファレンスです。

reallyユーザーがすべての引用符を入力しないようにしたい場合、1つのオプションは、deparse(substitute())を使用して、引用符なしの裸の列名を文字列に変換することです。

new_column2 <- function(df,col_name,col1,col2){
    col_name <- deparse(substitute(col_name))
    col1 <- deparse(substitute(col1))
    col2 <- deparse(substitute(col2))

    df[[col_name]] <- df[[col1]] + df[[col2]]
    df
}

> new_column2(dat,z,x,y)
  x y  z
1 1 5  6
2 2 6  8
3 3 7 10
4 4 8 12

これは、率直に言って少しばかげていると思います。なぜなら、new_column1と同じことを実際に行っているからです。

最後に、really空想を取得したい場合、追加する2つの列の名前を渡すのではなく、より柔軟で、2つの変数の他の組み合わせが可能です。その場合、2つの列を含む式でeval()を使用することになりそうです。

new_column3 <- function(df,col_name,expr){
    col_name <- deparse(substitute(col_name))
    df[[col_name]] <- eval(substitute(expr),df,parent.frame())
    df
}

楽しみのために、私はまだ新しい列の名前にdeparse(substitute())を使用しています。ここでは、次のすべてが機能します。

> new_column3(dat,z,x+y)
  x y  z
1 1 5  6
2 2 6  8
3 3 7 10
4 4 8 12
> new_column3(dat,z,x-y)
  x y  z
1 1 5 -4
2 2 6 -4
3 3 7 -4
4 4 8 -4
> new_column3(dat,z,x*y)
  x y  z
1 1 5  5
2 2 6 12
3 3 7 21
4 4 8 32

したがって、短い答えは基本的には、data.frameの列名を文字列として渡し、[[を使用して単一の列を選択します。 evalsubstituteなどの詳細な調査を開始するのは、自分が何をしているのかを本当に知っている場合だけです。

63
joran

個人的には、列を文字列として渡すのはかなり見苦しいと思います。私は次のようなことをするのが好きです:

get.max <- function(column,data=NULL){
    column<-eval(substitute(column),data, parent.frame())
    max(column)
}

次のようになります:

> get.max(mpg,mtcars)
[1] 33.9
> get.max(c(1,2,3,4,5))
[1] 5

Data.frameの指定がオプションであることに注意してください。列の機能を操作することもできます。

> get.max(1/mpg,mtcars)
[1] 0.09615385
22
Ian Fellows

別の方法は、 tidy evaluation アプローチを使用することです。データフレームの列を文字列またはそのままの列名として渡すのは非常に簡単です。 tidyevalhere の詳細を参照してください。

library(rlang)
library(tidyverse)

set.seed(123)
df <- data.frame(B = rnorm(10), D = rnorm(10))

列名を文字列として使用する

fun3 <- function(x, ...) {
  # capture strings and create variables
  dots <- ensyms(...)
  # unquote to evaluate inside dplyr verbs
  summarise_at(x, vars(!!!dots), list(~ max(., na.rm = TRUE)))
}

fun3(df, "B")
#>          B
#> 1 1.715065

fun3(df, "B", "D")
#>          B        D
#> 1 1.715065 1.786913

裸の列名を使用する

fun4 <- function(x, ...) {
  # capture expressions and create quosures
  dots <- enquos(...)
  # unquote to evaluate inside dplyr verbs
  summarise_at(x, vars(!!!dots), list(~ max(., na.rm = TRUE)))
}

fun4(df, B)
#>          B
#> 1 1.715065

fun4(df, B, D)
#>          B        D
#> 1 1.715065 1.786913
#>

2019-03-01に reprexパッケージ (v0.2.1.9000)によって作成されました

7
Tung

余分な考えとして、列名を引用符で囲まずにカスタム関数に渡す必要がある場合、この場合、match.call()の代替として、おそらくdeparse(substitute())も有用です。

df <- data.frame(A = 1:10, B = 2:11)

fun <- function(x, column){
  arg <- match.call()
  max(x[[arg$column]])
}

fun(df, A)
#> [1] 10

fun(df, B)
#> [1] 11

列名にタイプミスがある場合は、エラーで停止する方が安全です。

fun <- function(x, column) max(x[[match.call()$column]])
fun(df, typo)
#> Warning in max(x[[match.call()$column]]): no non-missing arguments to max;
#> returning -Inf
#> [1] -Inf

# Stop with error in case of typo
fun <- function(x, column){
  arg <- match.call()
  if (is.null(x[[arg$column]])) stop("Wrong column name")
  max(x[[arg$column]])
}

fun(df, typo)
#> Error in fun(df, typo): Wrong column name
fun(df, A)
#> [1] 10

reprexパッケージ (v0.2.1)によって2019-01-11に作成

上記の回答で指摘されているように引用符で囲まれた列名を渡すよりも余分な入力と複雑さがあるため、このアプローチを使用するとは思わないが、それはアプローチです。

0
Valentin