programing tip

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

itbloger 2020. 8. 12. 07:52
반응형

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

This is often considered "best practice" since it is the method that is hardest to screw up. Passing the column names as strings is about as unambiguous as you can get.

The following two options are more advanced. Many popular packages make use of these kinds of techniques, but using them well requires more care and skill, as they can introduce subtle complexities and unanticipated points of failure. This section of Hadley's Advanced R book is an excellent reference for some of these issues.

If you really want to save the user from typing all those quotes, one option might be to convert bare, unquoted column names to strings using 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

This is, frankly, a bit silly probably, since we're really doing the same thing as in new_column1, just with a bunch of extra work to convert bare names to strings.

Finally, if we want to get really fancy, we might decide that rather than passing in the names of two columns to add, we'd like to be more flexible and allow for other combinations of two variables. In that case we'd likely resort to using eval() on an expression involving the two columns:

new_column3 <- function(df,col_name,expr){
    col_name <- deparse(substitute(col_name))
    df[[col_name]] <- eval(substitute(expr),df,parent.frame())
    df
}

Just for fun, I'm still using deparse(substitute()) for the name of the new column. Here, all of the following will work:

> 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

So the short answer is basically: pass data.frame column names as strings and use [[ to select single columns. Only start delving into eval, substitute, etc. if you really know what you're doing.


Personally I think that passing the column as a string is pretty ugly. I like to do something like:

get.max <- function(column,data=NULL){
    column<-eval(substitute(column),data, parent.frame())
    max(column)
}

which will yield:

> get.max(mpg,mtcars)
[1] 33.9
> get.max(c(1,2,3,4,5))
[1] 5

Notice how the specification of a data.frame is optional. you can even work with functions of your columns:

> get.max(1/mpg,mtcars)
[1] 0.09615385

Another way is to use tidy evaluation approach. It is pretty straightforward to pass columns of a data frame either as strings or bare column names. See more about tidyeval here.

library(rlang)
library(tidyverse)

set.seed(123)
df <- data.frame(B = rnorm(10), D = rnorm(10))

Use column names as strings

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

Use bare column names

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
#>

Created on 2019-03-01 by the reprex package (v0.2.1.9000)


As an extra thought, if is needed to pass the column name unquoted to the custom function, perhaps match.call() could be useful as well in this case, as an alternative to 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

If there is a typo in the column name, then would be safer to stop with an error:

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

Created on 2019-01-11 by the reprex package (v0.2.1)

I do not think I would use this approach since there is extra typing and complexity than just passing the quoted column name as pointed in the above answers, but well, is an approach.

참고URL : https://stackoverflow.com/questions/2641653/pass-a-data-frame-column-name-to-a-function

반응형