Передайте функції стовпця data.frame до функції


119

Я намагаюся написати функцію, щоб прийняти data.frame ( x) та a 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()?

Відповіді:


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
Чи є спосіб передавати ім'я стовпця не як рядок?
кмм

2
Потрібно або передати ім'я стовпця, вказане як символ, або цілий індекс для стовпця. Щойно мимохідь Bприпустимо, що B - це сам об'єкт.
Шейн

Я бачу. Я не впевнений, як я опинився у складі перекрученого замінника, eval тощо
кмм

3
Дякую! Я знайшов, що [[рішення було єдиним, яке працювало на мене.
EcologyTom

1
Привіт @Luis, ознайомтеся з цією відповіддю
EcologyTom

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 від Hadley є чудовим посиланням на деякі з цих питань.

Якщо ви дійсно хочете врятувати користувача від введення всіх цих лапок, одним із варіантів може бути перетворення голих, не цитованих імен стовпців у рядки, використовуючи 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і т.д. , якщо ви дійсно знаєте , що ви робите.


1
Не впевнений, чому це не найкраще обрана відповідь.
Ян

Я також! Чудове пояснення!
Альфредо Г Маркес

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 років потому: чи не використання цитат ще некрасиво?
Спайсмен

11

Інший спосіб - використовувати 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
#>

Створено 2019-03-01 пакетом reprex (v0.2.1.9000)



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

Створено 2019-01-11 пакетом reprex (v0.2.1)

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.