Передайте имя столбца data.frame в функцию

Я пытаюсь написать функцию для приема data.frame(x) и column. Функция выполняет некоторые вычисления по x, а затем возвращает другой data.frame. Я привязался к методу лучших практик, чтобы передать имя столбца функции.

Два минимальных примера fun1 и fun2 ниже приводят желаемый результат, позволяя выполнять операции с x$column, используя max() в качестве примера. Однако оба полагаются на, казалось бы, (по крайней мере, на меня) inelegant

  • вызов substitute() и, возможно, eval()
  • необходимо передать имя столбца в виде символьного вектора.

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). Другие варианты, которые я рассмотрел, но не пробовал:

  • Pass column как целое число столбца. Я думаю, что это позволит избежать substitute(). В идеале функция может принимать и.
  • with(x, get(column)), но, даже если он работает, я думаю, что это все равно потребует substitute
  • Используйте formula() и match.call(), с которыми у меня много опыта.

Подзапрос: предпочтительнее do.call() более eval()?

Ответ 1

Вы можете просто использовать имя столбца напрямую:

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"))

Нет необходимости использовать замену, eval и т.д.

Вы даже можете передать желаемую функцию в качестве параметра:

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")

Ответ 2

Этот ответ будет охватывать многие из тех же элементов, что и существующие ответы, но эта проблема (передающая имена столбцов в функции) возникает достаточно часто, и я хочу, чтобы там был ответ, который охватывал вещи немного более всесторонне.

Предположим, у нас очень простой фрейм данных:

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

и мы хотели бы написать функцию, которая создает новый столбец z который представляет собой сумму столбцов x и y.

Очень распространенным камнем преткновения здесь является то, что естественная (но некорректная) попытка часто выглядит так:

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()), некотируемых имен столбцов в строки с использованием 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 и т.д., Если вы действительно знаете, что делаете.

Ответ 3

Лично я считаю, что передача столбца в виде строки довольно уродливая. Мне нравится делать что-то вроде:

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

Ответ 4

В качестве дополнительной мысли, если необходимо передать имя столбца без кавычек в пользовательскую функцию, возможно, 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

Создано в 2019-01-11 пакетом представлением (v0.2.1)

Я не думаю, что я бы использовал этот подход, поскольку существует дополнительная типизация и сложность, чем просто передача имени столбца в кавычках, как указано в приведенных выше ответах, но это подход.