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


119

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

以下の2つの最小限の例fun1では、例としてを使用してでfun2操作を実行できるため、望ましい結果が得られます。ただし、どちらも一見(少なくとも私には)エレガントではないx$columnmax()

  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()ますか?

回答:


108

列名を直接使用することができます:

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")

14
文字列としてではなく列名を渡す方法はありますか?
kmm

2
文字として引用された列名または列の整数インデックスを渡す必要があります。単に渡すだけでB、B自体がオブジェクトであると見なされます。
シェーン

そうですか。複雑な置換、評価などがどのように行われたかわかりません
kmm

3
ありがとう![[解決策は私のために働いた唯一のものであることがわかりました。
EcologyTom 2018年

1
こんにちは@Luis、この回答を
EcologyTom

78

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

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

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

そしてz、列xとの合計である新しい列を作成する関数を書きたいと思います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、「再帰的(リストのような)オブジェクト」セクションで説明しています。

最も簡単で最も頻繁に推奨されるソリューションは、単純に$toに切り替えて[[、関数の引数を文字列として渡すことです。

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

あなたがいる場合、実際にすべてのそれらの引用符を入力してからユーザーを保存したい、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。裸の名前を文字列に変換するための追加の作業がたくさんあるだけです。

最後に、本当に豪華にしたい場合は、追加する2つの列の名前を渡すのではなく、より柔軟にして2つの変数の他の組み合わせを許可することを決定する場合があります。その場合はeval()、2つの列を含む式を使用することになります。

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あなたが本当に何をやっている知っていれば、など。


1
これが選択された最良の回答ではない理由がわかりません。
イアン

私もです!素晴らしい説明!
アルフレドGマルケス

22

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

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

9
あなたは引用符を使用して考える習慣を醜くする必要があります。それらを使用しないのは醜いです!どうして?インタラクティブにしか使用できない関数を作成したため、それを使ってプログラムするのは非常に困難です。
ハドリー

27
私はより良い方法を示して満足していますが、これとqplot(x = mpg、data = mtcars)の違いを確認できません。ggplot2は列を文字列として渡さないので、その方がよいと思います。これはインタラクティブにしか使用できないと言うのはなぜですか?どのような状況下で望ましくない結果につながるでしょうか?プログラミングするのはどのように難しいですか?投稿の本文に、より柔軟な方法を表示します。
Ian Fellows

4
5年後-)..なぜ必要か:parent.frame()?
mql4beginner 2015年

15
7年後:引用符を使用しても醜いですか?
Spacedman 2017年

11

別の方法は、tidy evaluationアプローチを使用することです。データフレームの列を文字列または裸の列名として渡すことは非常に簡単です。詳細はtidyeval こちら

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)によって作成されました



1

追加の考えとして、引用符で囲まれていない列名をカスタム関数に渡す必要がある場合は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に作成されました

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.