R関数でオプションの引数を指定する「正しい」方法


165

Rでオプションの引数を使用して関数を作成する「正しい」方法に興味があります。時間の経過とともに、ここで別のルートをとるいくつかのコードに遭遇し、適切な(公式の)位置を見つけることができませんでした。このトピックにおいて。

これまでは、次のようなオプションの引数を記述しています。

fooBar <- function(x,y=NULL){
  if(!is.null(y)) x <- x+y
  return(x)
}
fooBar(3) # 3
fooBar(3,1.5) # 4.5

この関数は、指定された場合にのみ引数を返しますxNULL2番目の引数にデフォルト値を使用し、その引数が偶然ではないNULL場合、関数は2つの数値を加算します。

あるいは、次のような関数を書くこともできます(2番目の引数は名前で指定する必要がありますが、代わりに、unlist(z)またはz <- sum(...)代わりに定義することもできます)。

fooBar <- function(x,...){
  z <- list(...)
  if(!is.null(z$y)) x <- x+z$y
  return(x)
}
fooBar(3) # 3
fooBar(3,y=1.5) # 4.5

個人的に私は最初のバージョンを好む。しかし、私は両方で良い面と悪い面を見ることができます。最初のバージョンは少しエラーが発生しにくいですが、2番目のバージョンは任意の数のオプションを組み込むために使用できます。

Rでオプションの引数を指定する「正しい」方法はありますか?これまでのところ、私は最初のアプローチに落ち着きましたが、両方とも時々少し「ハッキー」に感じるかもしれません。


のソースコードをチェックしてxy.coords、一般的に使用されるアプローチを確認してください。
Carl Witthoft、2015

5
Carl Witthoft lがxy.coords言及したソースコードは、xy.coords
RubenLagunaに

回答:


129

missing()引数yが指定されているかどうかをテストするためにも使用できます。

fooBar <- function(x,y){
    if(missing(y)) {
        x
    } else {
        x + y
    }
}

fooBar(3,1.5)
# [1] 4.5
fooBar(3)
# [1] 3

5
私は行方不明が好きです。特に、多くのNULLデフォルト値がある場合、パッケージドキュメントにx = NULL、y = NULL、z = NULLは含まれません
rawr

5
@rawr missing()は、「それが何を意味するかを言う」という意味でもより表現力があります。さらに、ユーザーは、それが意味のある場所でNULLの値を渡すことができます。
Josh O'Brien

31
私にとって、この方法でミッシングを使用することには大きな欠点があります。関数の引数をざっと見ると、どの引数が必要で、どの引数がオプションであるかがわかりません。
ハドリー2015

3
@param x numeric; something something; @param y numeric; **optional** something something; @param z logical; **optional** something something
raw 2015

4
missing()ある関数から別の関数に引数を渡したい場合はひどいです。
ジョンスミス

55

正直に言うと、OPを実際にNULL値で開始してからチェックする最初の方法が好きですis.null(主にそれが非常に単純で理解しやすいためです)。それはおそらく人々がコーディングに慣れている方法に依存しますが、ハドリーis.nullもその方法をサポートしているようです:

Hadleyの本「Advanced-R」第6章、機能、p.84から(オンラインバージョンについては、こちらを確認してください):

missing()関数を使用して、引数が提供されたかどうかを判別できます。

i <- function(a, b) {
  c(missing(a), missing(b))
}
i()
#> [1] TRUE TRUE
i(a = 1)
#> [1] FALSE  TRUE
i(b = 2)
#> [1]  TRUE FALSE
i(1, 2)
#> [1] FALSE FALSE

場合によっては、自明ではないデフォルト値を追加する必要があり、計算に数行のコードが必要になることがあります。関数定義にそのコードを挿入する代わりに、missing()を使用して、必要に応じて条件付きでコードを計算できます。ただし、このため、ドキュメントを注意深く読まないと、必須の引数とオプションの引数を区別するのが難しくなります。代わりに、通常はデフォルト値をNULLに設定し、is.null()を使用して引数が指定されているかどうかを確認します。


2
面白い。それは合理的に聞こえますが、関数に必要な引数とオプションの引数に戸惑うことはありませんか?私は確かに私がしたことないんだけど、これまで実際にその経験を持っていた...
ジョシュ・オブライエン

2
@ JoshO'Brien正直に言うと、どちらのコーディングスタイルでも問題はなかったと思います。少なくとも、ドキュメントやソースコードの読み取りが原因で、大きな問題になることはありませんでした。だから、私が主に言っているのは、実際には慣れ親しんだコーディングスタイルの問題だということです。私はNULLかなり長い間この方法を使用しており、おそらくソースコードを見ると、その方法に慣れているのです。私にはもっと自然に思えます。とは言っても、ベースRは両方のアプローチをとるので、実際には個人の好みに基づいています。
LyzandeR 2015

2
今では、私は本当に私は本当に両方を使用して到着したどのようなので、私が正しいように2つの答えをマークことがしたいis.nullmissingコンテキストに応じて、どのような引数が使用されます。
SimonG 2015年

5
それは大丈夫です@SimonGとありがとう:)。どちらの回答も非常に優れており、状況に応じて異なる場合があることに同意します。これは非常に良い質問であり、答えは非常に良い情報と知識を提供してくれると信じています。
LyzandeR 2015年

24

これらは私の経験則です:

他のパラメータからデフォルト値を計算できる場合は、次のようにデフォルトの式を使用します。

fun <- function(x,levels=levels(x)){
    blah blah blah
}

そうでなければ欠落を使用する場合

fun <- function(x,levels){
    if(missing(levels)){
        [calculate levels here]
    }
    blah blah blah
}

では珍しいケースあなたの事はユーザーが全体のRセッション、使用を継続し、デフォルト値を指定することgetOption

fun <- function(x,y=getOption('fun.y','initialDefault')){# or getOption('pkg.fun.y',defaultValue)
    blah blah blah
}

最初の引数のクラスに応じていくつかのパラメーターが適用される場合は、S3ジェネリックを使用します。

fun <- function(...)
    UseMethod(...)


fun.character <- function(x,y,z){# y and z only apply when x is character
   blah blah blah 
}

fun.numeric <- function(x,a,b){# a and b only apply when x is numeric
   blah blah blah 
}

fun.default <- function(x,m,n){# otherwise arguments m and n apply
   blah blah blah 
}

...別の関数に追加のパラメーターを渡す場合にのみ使用します

cat0 <- function(...)
    cat(...,sep = '')

最後に、あなたが使用することを選択しない場合は...、別の関数にドットを通過することなく、あなたの関数が未使用のパラメータを無視していることをユーザーに警告することができるので、非常にそうでない場合は混乱します:

fun <- (x,...){
    params <- list(...)
    optionalParamNames <- letters
    unusedParams <- setdiff(names(params),optionalParamNames)
    if(length(unusedParams))
        stop('unused parameters',paste(unusedParams,collapse = ', '))
   blah blah blah 
}

s3メソッドオプションも、最初に頭に浮かんだことの1つでした
rawr

2
振り返ってみると、OP NULLが関数シグネチャに代入する方法が好きになりました。連鎖する関数を作成するのに便利だからです。
Jthorpe、2016年

10

いくつかのオプションがあり、それらのいずれも公式の正しい方法ではなく、実際に正しくないことはありませんが、コンピューターやコードを読み取る他の人に異なる情報を伝えることができます。

与えられた例では、最も明確なオプションはIDのデフォルト値を提供することだと思います。この場合は次のようにします。

fooBar <- function(x, y=0) {
  x + y
}

これは、これまでに示したオプションの中で最も短いものであり、短くすることで読みやすさを向上させることができます(場合によっては実行速度も向上します)。返されるのはxとyの合計であることは明らかであり、yに0になる値が指定されていないことがわかります。これをxに追加すると、xになります。明らかに、加算よりも複雑なものを使用する場合は、別のID値が必要になります(存在する場合)。

このアプローチについて私が本当に好きなことの1つは、args関数を使用するとき、またはヘルプファイルを見るときにもデフォルト値が何であるかが明確であることです(詳細にスクロールする必要はありません。使用方法にあります) )。

この方法の欠点は、デフォルト値が複雑な場合(複数行のコードが必要)、おそらくすべてをデフォルト値に入れようとすると読みやすさが低下し、missingorまたはNULLアプローチがはるかに合理的になります。

メソッド間の他の違いのいくつかは、パラメーターが別の関数に渡されるとき、match.callまたはまたはsys.call関数を使用するときに現れます。

したがって、「正しい」方法は、その特定の引数を使用して何を計画するか、およびコードの読者に伝えたい情報によって異なると思います。


7

何が必要で何がオプションかを明確にするために、NULLを使用する傾向があります。Jthorpeによって提案された、他の引数に依存するデフォルト値の使用に関する警告の一言。関数が呼び出されたときではなく、引数が最初に参照されたときに値が設定されます!例えば:

foo <- function(x,y=length(x)){
    x <- x[1:10]
    print(y)
}
foo(1:20) 
#[1] 10

一方、xを変更する前にyを参照すると、次のようになります。

foo <- function(x,y=length(x)){
    print(y)
    x <- x[1:10]
}
foo(1:20) 
#[1] 20

関数の早い段階で呼び出されていないかのように、初期化されている「y」を追跡するのが困難になるため、これは少し危険です。


7

組み込みsink関数には、関数に引数を設定するさまざまな方法の良い例があることを指摘したかっただけです。

> sink
function (file = NULL, append = FALSE, type = c("output", "message"),
    split = FALSE)
{
    type <- match.arg(type)
    if (type == "message") {
        if (is.null(file))
            file <- stderr()
        else if (!inherits(file, "connection") || !isOpen(file))
            stop("'file' must be NULL or an already open connection")
        if (split)
            stop("cannot split the message connection")
        .Internal(sink(file, FALSE, TRUE, FALSE))
    }
    else {
        closeOnExit <- FALSE
        if (is.null(file))
            file <- -1L
        else if (is.character(file)) {
            file <- file(file, ifelse(append, "a", "w"))
            closeOnExit <- TRUE
        }
        else if (!inherits(file, "connection"))
            stop("'file' must be NULL, a connection or a character string")
        .Internal(sink(file, closeOnExit, FALSE, split))
    }
}

1

これはどう?

fun <- function(x, ...){
  y=NULL
  parms=list(...)
  for (name in names(parms) ) {
    assign(name, parms[[name]])
  }
  print(is.null(y))
}

次に試してください:

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