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

119

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

Два минимальных примера fun1и fun2приведенные ниже дают желаемый результат, позволяя выполнять операции 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(), ни с одним из которых у меня нет большого опыта.

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

KMM
источник

Ответы:

108

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

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")
Шейн
источник
14
Есть ли способ передать имя столбца не как строку?
kmm
2
Вам нужно либо передать имя столбца, указанное в виде символа, либо целочисленный индекс для столбца. Просто прохождение Bбудет предполагать, что B сам является объектом.
Шейн
Понимаю. Я не уверен, как я закончил с запутанной заменой, eval и т. Д.
kmm
3
Спасибо! Я обнаружил, что [[решение было единственным, которое сработало для меня.
EcologyTom
1
Привет @Luis, посмотрите этот ответ
EcologyTom 01
78

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

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

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

Это часто считается «лучшей практикой», так как это метод, который труднее всего испортить. Передача имен столбцов в виде строк настолько однозначна, насколько это возможно.

Следующие два варианта являются более продвинутыми. Многие популярные пакеты используют такие методы, но их правильное использование требует большей осторожности и навыков, поскольку они могут внести тонкие сложности и непредвиденные точки отказа. Этот раздел книги Хэдли 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и т.д. , если вы действительно знаете , что вы делаете.

Joran
источник
1
Не уверен, почему это не лучший ответ.
Ян
Я тоже! Отличное объяснение!
Alfredo G Marquez,
22

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

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
Ян Феллоуз
источник
9
Вам нужно избавиться от привычки считать, что цитаты некрасивы. Не использовать их некрасиво! Зачем? Поскольку вы создали функцию, которую можно использовать только в интерактивном режиме, с ней очень сложно программировать.
Хэдли
27
Я рад, что мне показали лучший способ, но я не вижу разницы между этим и qplot (x = mpg, data = mtcars). ggplot2 никогда не передает столбец как строку, и я думаю, что для этого лучше. Почему вы говорите, что это можно использовать только в интерактивном режиме? В какой ситуации это привело бы к нежелательным результатам? Насколько сложнее программировать? В теле сообщения я показываю, насколько он более гибкий.
Ian Fellows
4
5 лет спустя -) .. Зачем нам: parent.frame ()?
mql4beginner
15
7 лет спустя: использование цитат по-прежнему некрасиво?
Spacedman
12

Другой способ - использовать 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
#>

Создано 01.03.2019 пакетом REPEX (v0.2.1.9000)

Tung
источник
1

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

Создано 11.01.2019 пакетом REPEX (v0.2.1)

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

Valentin
источник