Bir data.frame sütun adını bir işleve iletin


119

Bir data.frame ( x) ve columnondan a kabul etmek için bir fonksiyon yazmaya çalışıyorum . Fonksiyon x üzerinde bazı hesaplamalar yapar ve daha sonra başka bir data.frame döndürür. Sütun adını işleve geçirmek için en iyi uygulamalar yöntemine bağlı kaldım.

İki minimal örnek fun1ve fun2aşağıda x$column, max()örnek olarak kullanarak , üzerinde işlem yapabilmek için istenen sonucu verir . Bununla birlikte, her ikisi de görünüşte (en azından benim için) uygunsuz olana güveniyor

  1. çağırmak substitute()ve muhtemeleneval()
  2. sütun adını bir karakter vektörü olarak geçirme ihtiyacı.

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)Örneğin işlevi olarak adlandırabilmek istiyorum . Düşündüğüm ancak denemediğim diğer seçenekler:

  • Geçiş columnkolon sayısının bir tam sayı olarak. Sanırım bu önlenir substitute(). İdeal olarak, işlev ikisini de kabul edebilir.
  • with(x, get(column)), ancak işe yarasa bile, bunun yine de substitute
  • Yararlanın formula()ve match.call()ben çok deneyimi olan, ikisi de.

Alt soru : do.call()Tercih edilir eval()mi?

Yanıtlar:


108

Sütun adını doğrudan kullanabilirsiniz:

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

Yedek, eval vb. Kullanmaya gerek yoktur.

İstenilen işlevi bir parametre olarak bile geçebilirsiniz:

fun1 <- function(x, column, fn) {
  fn(x[,column])
}
fun1(df, "B", max)

Alternatif olarak, kullanmak [[bir seferde tek bir sütun seçmek için de işe yarar:

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

14
Sütun adını dizge olarak aktarmanın bir yolu var mı?
kmm

2
Bir karakter olarak alıntılanan sütun adını veya sütun için tamsayı indeksini iletmeniz gerekir. Sadece geçmek B, B'nin kendisinin bir nesne olduğunu varsayacaktır.
Shane

Anlıyorum. Kıvrımlı yedek, değerlendirme vb.
İle

3
Teşekkürler! [[Çözümün benim için işe yarayan tek çözüm olduğunu buldum .
EcologyTom

1
Merhaba @Luis, bu cevabı
EcologyTom

78

Bu cevap, mevcut cevaplarla aynı unsurların çoğunu kapsayacaktır, ancak bu konu (sütun adlarının işlevlere aktarılması), işleri biraz daha kapsamlı bir şekilde kapsayan bir cevap olmasını istediğim kadar sık ​​ortaya çıkıyor.

Çok basit bir veri çerçevemiz olduğunu varsayalım:

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

ve zsütunların toplamı olan yeni bir sütun oluşturan bir işlev yazmak istiyoruz xve y.

Buradaki çok yaygın bir engel, doğal (ancak yanlış) bir girişimin genellikle şöyle görünmesidir:

foo <- function(df,col_name,col1,col2){
      df$col_name <- df$col1 + df$col2
      df
}

#Call foo() like this:    
foo(dat,z,x,y)

Buradaki sorun df$col1, ifadenin değerlendirilmemesidir col1. dfKelimenin tam anlamıyla çağrılan bir sütun arar col1. Bu davranış, ?Extract"Özyinelemeli (liste benzeri) Nesneler" bölümünde açıklanmaktadır .

En basit ve en sık önerilen çözüm geçiş basitçe olduğu $için [[dizeleri olarak işlev argümanları ve pass:

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

Bu, batırması en zor yöntem olduğu için genellikle "en iyi uygulama" olarak kabul edilir. Sütun adlarını dizeler olarak geçirmek, elde edebileceğiniz kadar nettir.

Aşağıdaki iki seçenek daha gelişmiştir. Pek çok popüler paket bu tür teknikleri kullanır, ancak bunları iyi kullanmak daha fazla özen ve beceri gerektirir, çünkü bunlar ince karmaşıklıklar ve beklenmeyen başarısızlık noktaları ortaya çıkarabilir. Hadley'in Advanced R kitabının bu bölümü, bu sorunlardan bazıları için mükemmel bir referanstır.

Eğer varsa gerçekten tüm bu tırnak yazarak kullanıcıyı kaydetmek istediğiniz, seçeneklerden biri kullanılarak dizeleri çıplak, tırnaksız sütun adlarını dönüştürmek olabilir 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

Açıkçası, bu biraz saçma bir olasılık, çünkü gerçekten de aynı şeyi yapıyoruz new_column1, sadece çıplak isimleri dizelere dönüştürmek için fazladan bir sürü işle.

Son olarak, gerçekten süslü olmak istiyorsak , eklemek için iki sütunun adlarını iletmek yerine, daha esnek olmaya ve iki değişkenin diğer kombinasyonlarına izin vermeye karar verebiliriz. Bu durumda, muhtemelen eval()iki sütunu içeren bir ifadeye başvurabiliriz :

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

Sırf eğlence olsun diye, hala deparse(substitute())yeni sütunun adını kullanıyorum . Burada aşağıdakilerin tümü çalışacaktır:

> 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

Yani kısa cevap temelde şudur: data.frame sütun adlarını dizeler olarak geçirin ve [[tek sütunları seçmek için kullanın . Sadece delving başlamak eval, substitutegerçekten ne yaptığını biliyorsan, vb.


1
Bunun neden seçilmiş en iyi cevap olmadığından emin değilim.
Ian

Ben de değil! Harika açıklama!
Alfredo G Marquez

22

Şahsen, sütunu bir dizge olarak iletmenin oldukça çirkin olduğunu düşünüyorum. Şunun gibi bir şey yapmayı severim:

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

hangi sonuç verecek:

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

Bir veri çerçevesinin belirtiminin nasıl isteğe bağlı olduğuna dikkat edin. sütunlarınızın işlevleriyle bile çalışabilirsiniz:

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

9
Tırnak kullanmanın çirkin olduğunu düşünme alışkanlığından kurtulmanız gerekir. Bunları kullanmamak çirkin! Neden? Yalnızca etkileşimli olarak kullanılabilen bir işlev oluşturduğunuz için, onunla programlamak çok zordur.
hadley

27
Daha iyi bir yol gösterildiğim için mutluyum, ancak bununla qplot (x = mpg, data = mtcars) arasındaki farkı göremiyorum. ggplot2 hiçbir zaman bir sütunu dizge olarak geçirmez ve bence bunun için daha iyi. Neden bunun yalnızca etkileşimli olarak kullanılabileceğini söylüyorsunuz? Hangi durumda istenmeyen sonuçlara yol açar? Programlamak nasıl daha zor? Yazının gövdesinde nasıl daha esnek olduğunu gösteriyorum.
Ian Fellows

4
5 yıl sonra -) .. Neden ihtiyacımız var: parent.frame ()?
mql4beginner

15
7 yıl sonra: Alıntı kullanmamak hala çirkin mi?
Spacedman

12

Başka bir yol da tidy evaluationyaklaşımı kullanmaktır . Bir veri çerçevesinin sütunlarını dizeler veya çıplak sütun adları olarak geçirmek oldukça basittir. tidyeval Burada daha fazlasını görün .

library(rlang)
library(tidyverse)

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

Sütun adlarını dizeler olarak kullanın

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

Çıplak sütun adları kullanın

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 tarihinde tarafından oluşturuldu Reprex paketi (v0.2.1.9000)



1

Ek bir düşünce olarak, sütun adını alıntılanmamış özel işleve geçirmek gerekirse, belki de match.call()bu durumda, şuna bir alternatif olarak yararlı olabilir 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

Sütun adında bir yazım hatası varsa, bir hatayla durmak daha güvenli olacaktır:

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 paketi (v0.2.1) tarafından 2019-01-11 tarihinde oluşturuldu

Yukarıdaki cevaplarda belirtildiği gibi alıntılanan sütun adını iletmekten daha fazla yazım ve karmaşıklık olduğu için bu yaklaşımı kullanacağımı sanmıyorum, ama iyi bir yaklaşım.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.