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

이 함수 x는 제공된 경우에만 인수를 반환합니다 . NULL두 번째 인수에 기본값을 사용하고 해당 인수가 아닌 NULL경우 함수는 두 숫자를 더합니다.

또는 다음과 같이 함수를 작성할 수 있습니다 (두 번째 인수는 이름으로 지정해야하지만 대신 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

개인적으로 나는 첫 번째 버전을 선호합니다. 그러나 나는 둘 다 좋은 것과 나쁜 것을 볼 수 있습니다. 첫 번째 버전은 오류가 발생하기 쉽지 않지만 두 번째 버전은 임의의 수의 옵션을 통합하는 데 사용될 수 있습니다.

R에서 선택적 인수를 지정하는 "올바른"방법이 있습니까? 지금까지 첫 번째 접근 방식을 결정했지만 둘 다 때때로 "해키"를 느낄 수 있습니다.


xy.coords일반적으로 사용되는 방법을 보려면 소스 코드 를 확인하십시오.
Carl Witthoft

5
소스 코드 xy.coords에 의해 언급 칼 Witthoft의 리터에서 찾을 수 있습니다 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
나에게는 이런 식으로 누락을 사용하는 데 큰 단점이 있습니다. 함수 인수를 감추면 더 이상 필요한 인수와 옵션이 무엇인지 알 수 없습니다.
hadley

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

4
missing()한 함수에서 다른 함수로 인수를 전달하려고 할 때 끔찍합니다.
John Smith

55

솔직히 말해서 나는 실제로 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 ()을 사용합니다.


2
흥미 롭군 그것은 합리적으로 들리지만, 당신은 함수에 어떤 인수가 필요하고 어떤 것이 선택적인지에 대해 당황한 적이 있습니까? 나는 한 모르겠어요 이제까지 실제로 있었다 경험 ... 그
조쉬 오브라이언

2
@ JoshO'Brien 나는 솔직히 말해서 코딩 스타일에 그 문제가 없었습니다. 적어도 문서화 또는 소스 코드를 읽는 것으로 인해 큰 문제는 없었습니다. 그리고 이것이 제가 주로 코딩 스타일의 문제라고 주로 말하는 이유입니다. 나는 NULL꽤 오래 동안 길을 사용하고 있으며 아마도 소스 코드를 볼 때 더 익숙해 졌을 것입니다. 나에게 더 자연스러운 것 같습니다. 즉, 기본 R은 두 가지 접근 방식을 모두 취하므로 실제로 개인 취향에 달려 있습니다.
LyzandeR

2
지금까지, 난 정말 정말 모두 사용에 도착 무엇 때문에 나는 올바른로 두 답변을 표시 할 수 있으면 좋겠다 is.nullmissing문맥에 따라 어떤 인수가 사용됩니다.
SimonG

5
괜찮습니다 @SimonG 및 감사합니다 :). 나는 두 답변 모두 매우 훌륭하며 때로는 상황에 따라 달라진다는 데 동의합니다. 이것은 매우 좋은 질문이며, 답변이 여기에 주요 목표 인 매우 좋은 정보와 지식을 제공한다고 생각합니다.
LyzandeR

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 방법 옵션은 나에게 가장 먼저 떠오른 것 중 하나였습니다.
rawr

2
돌이켜 보면, 나는 할당의 OP의 방법 좋아되었다 NULL, 함수 서명에로 기능을 만들기위한 더 편리 체인 잘합니다.
Jthorpe

10

몇 가지 옵션이 있으며 공식적인 올바른 방법은 없으며 실제로 잘못된 정보는 없지만 컴퓨터와 코드를 읽는 다른 사람에게 다른 정보를 전달할 수 있습니다.

주어진 예제에서 가장 명확한 옵션은 ID 기본값을 제공하는 것입니다.이 경우 다음과 같이하십시오.

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

이것은 지금까지 표시된 옵션 중 가장 짧은 옵션이며 가독성은 가독성을 높이는 데 도움이 될 수 있습니다 (때로는 실행 속도도 향상). 반환되는 것은 x와 y의 합이라는 것이 분명하며 y에 0을 더한 값이 주어지지 않음을 알 수 있습니다. x에 추가하면 x가됩니다. 분명히 더하기보다 복잡한 것이 사용되면 다른 정체성 값이 필요할 것입니다 (존재하는 경우).

이 접근법에 대해 내가 정말로 좋아하는 한 가지는 args함수를 사용하거나 도움말 파일을 볼 때 기본값이 무엇인지 분명하다는 것입니다 (세부 사항까지 스크롤 할 필요가 없습니다. ).

이 방법의 단점은 기본값이 복잡한 경우 (여러 줄의 코드가 필요함) 모든 값을 기본값으로 설정하려고하면 가독성이 떨어지고 missing또는 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.