[r] data.frame 열 이름을 함수에 전달

나는 data.frame ( x) 및 a 를 받아들이는 함수를 작성하려고 column합니다. 이 함수는 x에서 일부 계산을 수행하고 나중에 다른 data.frame을 반환합니다. 열 이름을 함수에 전달하는 모범 사례 방법을 고수하고 있습니다.

두 개의 최소 예제 fun1fun2아래 는 예제로 x$column사용하여에서 작업을 수행 할 수있는 원하는 결과를 생성합니다 max(). 그러나 둘 다 겉보기에 (적어도 나에게는) 우아하지 않은 것에 의존합니다.

  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()내가 가진 많은 경험을 가지고, 어느 쪽도 아니합니다.

Subquestion : do.call()더 선호 eval()합니까?



답변

열 이름을 직접 사용할 수 있습니다.

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)

또는를 사용 [[하면 한 번에 하나의 열을 선택할 수도 있습니다.

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


답변

이 답변은 기존 답변과 동일한 요소를 많이 다루지 만,이 문제 (열 이름을 함수에 전달)는 좀 더 포괄적으로 다루는 답변이 있기를 원할 정도로 자주 발생합니다.

매우 간단한 데이터 프레임이 있다고 가정합니다.

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

우리는 새로운 열을 생성하는 기능을 쓰고 싶은 z컬럼의 합 xy.

여기서 매우 일반적인 걸림돌은 자연 스럽지만 잘못된 시도가 종종 다음과 같이 보인다는 것입니다.

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

이것은 가장 망치기 어려운 방법이기 때문에 종종 “모범 사례”로 간주됩니다. 열 이름을 문자열로 전달하는 것은 가능한 한 모호하지 않습니다.

다음 두 가지 옵션이 더 고급입니다. 많은 인기있는 패키지가 이러한 종류의 기술을 사용하지만이를 잘 사용 하려면 미묘한 복잡성과 예상치 못한 실패 지점을 도입 할 수 있으므로 더 많은주의와 기술이 필요합니다. Hadley의 Advanced R 책 의이 섹션은 이러한 문제 중 일부에 대한 훌륭한 참고 자료입니다.

당신이 경우 정말 모든 따옴표를 입력에서 사용자를 저장하려면, 하나의 옵션을 사용하여 문자열로 베어, 인용 부호로 둘러싸이지 않은 열 이름을 변환 할 수 있습니다 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. 단지 이름을 문자열로 변환하기위한 추가 작업을 많이합니다.

마지막으로, 정말 멋지게 추가 할 두 열의 이름을 전달하는 것보다 더 유연하고 두 변수의 다른 조합을 허용하는 것으로 결정할 수 있습니다. 이 경우 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 열 이름을 문자열로 전달하고 [[단일 열을 선택 하는 데 사용 하는 것입니다. 만에 탐구하기 시작 eval, substitute당신이 정말 당신이 무슨 일을하는지 알고있는 경우 등.


답변

개인적으로 열을 문자열로 전달하는 것은 매우 추한 것이라고 생각합니다. 나는 다음과 같은 것을 좋아합니다.

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


답변

또 다른 방법은 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
#>

reprex 패키지 (v0.2.1.9000)에 의해 2019-03-01에 생성됨


답변

추가 생각으로 인용되지 않은 열 이름을 사용자 지정 함수에 전달해야하는 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에 생성됨

위의 답변에서 지적한 것처럼 인용 된 열 이름을 전달하는 것보다 추가 입력과 복잡성이 있기 때문에이 접근 방식을 사용하지 않을 것이라고 생각하지만 접근 방식입니다.


답변