2017-01-31 9 views
8

Ich habe ein Paket mit benutzerdefinierten summary(), print() Methoden für Objekte, die eine bestimmte Klasse haben. Dieses Paket verwendet auch das wunderbare Paket dplyr zur Datenbearbeitung - und ich erwarte, dass meine Benutzer Skripte schreiben, die sowohl mein Paket als auch dplyr verwenden.definieren benutzerdefinierte dplyr Methoden in R Paket

Eine Straßensperre, die von anderen bemerkt wurde here und here ist, dass dplyr Verben keine benutzerdefinierten Klassen erhalten - was bedeutet, dass ein ungroup Befehl meine data.frames ihrer benutzerdefinierten Klassen abstreifen können und somit Methode Dispatch vermasseln für summary usw.

Hadley sagt „dies richtig tun, ist bis zu Ihnen - Sie benötigen ein Verfahren für Ihre Klasse für jede dplyr Methode zu definieren, die alle Klassen korrekt wieder her und Attribute“ und ich versuche, die advice zu nehmen - aber ich kann nicht herausfinden, wie man die dplyr-Verben richtig einpackt.

Hier ist ein einfaches Spielzeugbeispiel. Lassen Sie uns sagen, ich habe eine cars Klasse definiert, und ich habe eine benutzerdefinierte summary dafür.

das funktioniert

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) 

hier ist das Problem

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

dass summary Ruf nach small_cars gibt mir nur die allgemeine Zusammenfassung, nicht meine eigene Methode, weil small_cars nicht mehr behält die cars Klasse nach dplyr Filterung.

was habe ich versucht,

Zuerst habe ich versucht, eine benutzerdefinierte Methode um filter Schreiben (filter.cars). Das hat nicht funktioniert, weil filter tatsächlich ein Wrapper um filter_, die nicht standardmäßige Auswertung ermöglicht.

Also schrieb ich eine benutzerdefinierte filter_ Methode für cars Objekte, versucht ‚zu implementieren @jwdink s 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 
} 

Das funktioniert nicht - ich eine unendliche Rekursion Fehler:

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

Alles, was ich tun möchte, ist, die Klassen auf dem eingehenden df zu packen, an dplyr zu übergeben und dann das Objekt mit den gleichen Klassennamen wie vor dem dplyr-Aufruf zurückzugeben. Wie ändere ich meinen filter_ Wrapper, um das zu erreichen? Danke!

Antwort

7

Weitere Vorschläge wurden in the thread angeboten, so dachte ich, ich würde mit aktualisieren, was scheint am beste Praxis zu sein, die NextMethod() zu verwenden ist.

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

Wo reclass ein Generikum ist, dass zumindest die Klasse fügt zurück auf:

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

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

Aber Sie könnten eine eigene Methode für die Klasse definieren, die auch wieder Attribute Kopien:

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

Ich glaube eigentlich, eine bessere Standardmethode würde einfach davon ausgehen, dass es ein Attribut gibt, dessen Name mit dem der Klasse übereinstimmt:


Beachten Sie, dass für dplyr 0,7, die Unterstreichungs Version der Verben sind veraltet. Wenn Ihre Klasse "Autos" von tbl_df erbt, müssen Sie eine Methode für die Nicht-Unterstrich-Verben schreiben. Aber dann sollten Sie die Unterstreichungsversion aus Gründen der Abwärtskompatibilität beibehalten.

Angesichts all dieser Replikation mag ich die Idee eines Adverbs hier.

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

Dann sind die Dinge schön und prägnant in Ihrem Paket:

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

usw.


EDIT:

nicht preservatively anwenden. Es wird unterbrochen, wenn jemand das Verb dplyr mit einem benannten ersten Argument aufruft, da der Name normalerweise .data, nicht x ist.

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

Ich werde diese Antwort aktualisieren, wenn sich herausstellt, dass ein Adverb doch funktionieren kann. Ansonsten schätze ich das ist wirklich nicht mehr wortreich:

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

Ich mag Ihr letztes Beispiel, obwohl ich denke, 'reclass (data, NextMethod())' muss in 'reclass (.data, NextMethod()) geändert werden.' – Eric

+0

Während es eine nette Abhilfe ist, scheint es nicht sehr vernünftig für ** dplyr ** um die Klassen zu löschen. Diese Problemumgehung bedeutet, dass jedes Paket, das benutzerdefinierte Datenrahmenklassen verwendet, nun Methoden für jedes ** dplyr ** Verb hinzufügen muss ... – Deleet

+0

Eric, Ich habe den Tippfehler mit '.data' gegenüber' data' behoben (I didn merke es erst, wenn ich versucht habe, den Code selbst zu implementieren. – Deleet

8

Ihre neue filter_ Methode versucht, die neue Klasse in der Definition, damit die Rekursion anzuwenden.

Folgen Sie the advice in the issue you linked, versuchen Sie, diese neue Klasse vor filter_ in Ihrer aktualisierten Methode zu entfernen.

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

das war wirklich interessant. Als ich das schrieb, dachte ich dplyr :: filter_ * garantiert *, dass der interne Anruf Displyr-aromatisierte Dispatch bekam, aber das ist nicht genug! die Rekursion macht jetzt Sinn. – Andrew