web-dev-qa-db-ja.com

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

私は、Rでオプションの引数を持つ関数を書くための「正しい」方法が何かに興味があります。時が経つにつれて、ここでは異なる道筋をたどるいくつかのコードを見つけました。このトピックにおいて。

これまで、私は次のようなオプションの引数を書きました。

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

xのみが指定されている場合、この関数は単にその引数を返します。 2番目の引数にデフォルトのNULL値を使用し、その引数が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にオプションの引数を指定する "正しい"方法はありますか?これまでのところ、私は最初のアプローチに取りかかりましたが、両方とも時々少し「ハック」を感じることができます。

137
SimonG

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

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

fooBar(3,1.5)
# [1] 4.5
fooBar(3)
# [1] 3
101
Josh O'Brien

正直に言うと、OPの最初の方法で実際にNULLの値で開始してからis.nullで確認するのが好きです(主にこれは非常に単純で理解しやすいので)。それはおそらく人々がコーディングに慣れている方法に依存しますが、Hadleyは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()を使用して引数が指定されているかどうかを確認します。

45
LyzandeR

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

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

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 = '')

最後に、ドットを別の関数に渡さずにuse ...を選択した場合、関数が未使用のパラメーターを無視していることをユーザーに警告しますveryそれ以外は混乱します:

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

いくつかの選択肢がありますが、どれも公式の正しい方法ではありませんし、どれも本当に間違っているわけではありません。ただし、これらはさまざまな情報をコンピュータや他の人のコードを読んでいる人に伝えます。

与えられた例のために私は最も明確なオプションはアイデンティティデフォルト値を供給することであると思う、この場合このようなことをする:

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

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

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

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

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

だから私は「正しい」方法はあなたがその特定の議論をどうするつもりであるかそしてあなたがあなたのコードの読者に伝えたいどんな情報に依存するかと思います。

7
Greg Snow

何が必須で何がオプションなのかを明確にするために、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"が初期化されていることを追跡するのを困難にするからです。

6

組み込みの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))
    }
}
6
user5359531

これはどう?

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
1
Keyu Nie