2010-04-14 68 views
69

我正在嘗試編寫一個函數來接受它的data.frame(x)和column。該函數在x上執行一些計算,然後返回另一個data.frame。我堅持使用最佳實踐方法將列名傳遞給函數。下面將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()

回答

66

你可以使用直接列名:

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

有什麼辦法可以將列名不作爲字符串? – kmm 2010-04-14 23:13:02

+2

您需要將列名引用爲字符或列的整數索引。只要傳遞'B'就會認爲B是一個對象本身。 – Shane 2010-04-14 23:14:56

+0

我明白了。我不確定我是如何以複雜的替代品,eval等結尾的。 – kmm 2010-04-14 23:17:36

17

我個人認爲將字段作爲字符串傳遞是非常難看的。我喜歡做這樣的事情:

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

你需要擺脫使用引號思維的習慣是醜陋的。不使用它們是醜陋的!爲什麼?因爲你創建了一個只能交互使用的函數 - 使用它編程是非常困難的。 – hadley 2010-04-15 13:21:02

+23

我很高興能夠看到更好的方式,但是我無法看到它和qplot之間的區別(x = mpg,data = mtcars)。 ggplot2從不將字段作爲字符串傳遞,我認爲它最好。你爲什麼說這隻能交互使用?在什麼情況下會導致不良結果?如何編程更難?在帖子的主體中,我展示了它是如何更靈活的。 – 2010-04-15 15:44:34

+3

5年後 - )..爲什麼我們需要:parent.frame()? – mql4beginner 2015-06-21 11:48:51

39

這個答案將涵蓋許多相同的要素,現有的答案,但這個問題(通過列名的功能)出現往往不夠,我想那裏是這個答案涵蓋了更全面的一點。

假設我們有一個非常簡單的數據幀:

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哈德利的高級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等,如果你真的知道你在做什麼。