2017-01-31 48 views
8

定製dplyr方法我有定製summary()print()方法具有特定類的對象包。該軟件包還使用美妙的dplyr軟件包進行數據操作 - 我希望我的用戶能夠編寫同時使用我的軟件包和dplyr的腳本。定義中的R包

的一個障礙,它已被他人herehere注意的是,dplyr動詞不保留自定義類 - 這意味着一個ungroup命令可以剝奪他們的自定義類的我data.frames,從而搞砸方法派出summary

哈德利說,「這樣做正確的是你 - 你需要定義你的類能夠正確還原所有類和屬性各dplyr方法的方法」,我試圖採取advice - 但我無法弄清楚如何正確包裝dplyr動詞。

下面是一個簡單的玩具例子。比方說,我已經定義了一個cars課,我有一個自定義summary

這個作品

library(tidyverse) 

class(mtcars) <- c('cars', class(mtcars)) 

summary.cars <- function(x, ...) { 
    #gather some summary stats 
    df_dim <- dim(x) 
    quantile_sum <- map(mtcars, quantile) 

    cat("A cars object with:\n") 
    cat(df_dim[[1]], 'rows and ', df_dim[[2]], 'columns.\n') 

    print(quantile_sum) 

} 

summary(mtcars) 

這裏的問題

small_cars <- mtcars %>% filter(cyl < 6) 
summary(small_cars) 
class(small_cars) 

summary呼籲small_cars只是給了我一般總結,而不是我的自定義的方法,因爲small_cars不再保留dplyr後cars類過濾。

我試過

首先,我試着寫左右filterfilter.cars)的自定義方法。沒有工作,因爲filter實際上是一個包裝周圍filter_,允許非標評價。

所以我寫了一個自定義的filter_方法cars對象,試圖實施@jwdink的advice

filter_.cars <- function(df, ...) { 

    old_classes <- class(df) 
    out <- dplyr::filter_(df, ...) 
    new_classes <- class(out) 

    class(out) <- c(new_classes, old_classes) %>% unique() 

    out 
} 

這並不工作 - 我得到一個無限遞歸錯誤:

Error: evaluation nested too deeply: infinite recursion/options(expressions=)? 
Error during wrapup: evaluation nested too deeply: infinite recursion/options(expressions=)? 

所有我想要做的就是搶在進入DF類,移交給dplyr,然後用相同的類名返回對象,因爲它的dplyr呼叫前了。 如何更改我的filter_包裝來實現這一目標?謝謝!

回答

7

進一步的建議是在the thread提供的,所以我想我會用什麼似乎是最好的做法,就是用NextMethod()更新。

filter_.cars <- function(.data, ...) { 
    result <- NextMethod() 
    reclass(.data, result) 
} 

reclass是一個通用的,至少增加了類回:

reclass <- function(x, result) { 
    UseMethod('reclass') 
} 

reclass.default <- function(x, result) { 
    class(result) <- unique(c(class(x)[[1]], class(result))) 
    result 
} 

但是你可以定義你的類的定製方法,它還會複製回屬性:

reclass.cars <- function(x, result) { 
    class(result) <- unique(c(class(x)[[1]], class(result))) 
    attr(result,'cars') <- attr(x,'cars') 
    result 
} 

其實,我覺得更好的默認方法也只是假設有一個屬性,它的名字是一樣的類:

reclass.default <- function(x, result) { 
    class(result) <- unique(c(class(x)[[1]], class(result))) 
    attr(result, class(x)[[1]]) <- attr(x, class(x)[[1]]) 
    result 
} 

注意,對於dplyr 0.7,動詞的下劃線版本已被棄用。如果您的'汽車'類繼承自tbl_df,則需要爲非下劃線動詞編寫一個方法。但是爲了向後兼容,您可能需要保留下劃線版本。

考慮到所有這些複製,我挺喜歡這裏的副詞的想法。

preservatively <- function(fun) { 
    function(x, ...) { 
    result <- NextMethod() 
    reclass(x, result) 
    } 
} 

然後,事情都在你的包不錯,簡潔:

filter_.cars <- preservatively(filter_) 
filter.cars <- preservatively(filter) 
mutate_.cars <- preservatively(mutate_) 
mutate.cars <- preservatively(mutate) 


編輯:

不要使用preservatively。如果有人打電話跟一個名爲第一個參數dplyr動詞,因爲該名稱一般爲.data,不x這將打破。

filter.cars <- preservatively(filter) 
filter(my_data, condition) # good 
filter(.data = my_data, condition) # oh no 

我會更新這個答案,如果它證明一個副詞畢竟可以工作。否則,我想這真的不是更詳細:

filter.cars <- function(.data, ...) reclass(.data, NextMethod()) 
+1

我喜歡你的最後一個例子,雖然我認爲'reclass(data,NextMethod())'需要改爲'reclass(.data,NextMethod())' – Eric

+0

雖然這是一個很好的解決方法,但它看起來不太合理爲** dplyr **刪除開始的類。這種解決方法意味着每個使用自定義數據框類的軟件包現在都必須爲每個** dplyr **動詞添加方法... – Deleet

+0

Eric,我用'.data'和'data'修復了錯字(我沒有直到我試圖自己實現代碼時纔會注意到它。) – Deleet

8

您的新filter_方法試圖定義內,向新的類,因此遞歸。

the advice in the issue you linked,請嘗試刪除您的更新方法之前filter_新類。

class(out) <- class(out)[-1] 
+0

這真的很有趣。當我寫這篇文章時,我認爲dplyr :: filter_ *保證*內部調用得到dplyr風味的調度,但這還不夠!遞歸現在有意義。 – Andrew