2015-01-15 20 views
39

ich die folgende Berechnung unter Verwendung von dplyr parametrisieren möchten, die mit mehr als einem Wert von Sepal.Width zugeordnet, die Werte von Sepal.Length findet:Pass Argumente dplyr Funktionen

library(dplyr) 

iris %>% 
    group_by(Sepal.Length) %>% 
    summarise(n.uniq=n_distinct(Sepal.Width)) %>% 
    filter(n.uniq > 1) 

Normalerweise so etwas wie dieses würde ich schreiben:

not.uniq.per.group <- function(data, group.var, uniq.var) { 
    iris %>% 
     group_by(group.var) %>% 
     summarise(n.uniq=n_distinct(uniq.var)) %>% 
     filter(n.uniq > 1) 
} 

Dieser Ansatz löst jedoch Fehler aus, da dplyrnon-standard evaluation verwendet. Wie sollte diese Funktion geschrieben werden?

+3

Aus Gründen der Stil, würde ich davon abraten, den Punkt in Namen in modernen R, außer in S3-Generika verwenden. Es ist furchtbar verwirrend. Die Namenskonvention, die unter anderem von 'dplyr' verwendet wird, ist viel schöner:' names_with_undscores'. –

+1

Ich bin mir bewusst, dass [Hadley Wickhams Styleguide] (http://adv-r.had.co.nz/Style.html) die Unterstrichnotation empfiehlt, aber der [Google R Style Guide] (https: // google -styleguide.googlecode.com/svn/trunk/Rguide.xml) fördert den Zeitraum (obwohl nicht für Funktionen, die ich hier getan habe). In anderen Sprachen wird die Periode für den Mitgliederzugriff verwendet (zB 'myArray.length' in Javascript), gibt es einen weiteren Konflikt in R? – asnr

+7

Google Style Guides sind oft schrecklich. In diesem speziellen Fall besteht das Problem darin, dass es zu Mehrdeutigkeiten mit S3-Methoden führt: ist 'some.class.method' eine Methode' some' der Klasse 'class.method' oder ist es eine Methode' some.class' der Klasse ' Methode? Außerdem führt dies zu inkonsistenten Namen, wenn Teile Ihres Codes in C (++) implementiert werden, da dies keine Punkte in Namen unterstützt, was eine Zuordnung der Backend-Funktionsnamen zu verschiedenen R-Namen erfordert. –

Antwort

37

Sie müssen die Standard-Testversionen der dplyr Funktionen (nur anhängen ‚_‘ zu den Funktionsnamen, dh. group_by_ & summarise_) und Streicher auf Ihre Funktion übergeben, die Sie dann in Symbole drehen müssen. Um das Argument von summarise_ zu parametrieren, müssen Sie interp() verwenden, das im -Paket definiert ist. Konkret:

library(dplyr) 
library(lazyeval) 

not.uniq.per.group <- function(df, grp.var, uniq.var) { 
    df %>% 
     group_by_(grp.var) %>% 
     summarise_(n_uniq=interp(~n_distinct(v), v=as.name(uniq.var))) %>% 
     filter(n_uniq > 1) 
} 

not.uniq.per.group(iris, "Sepal.Length", "Sepal.Width") 

Siehe dplyrvignette für Nicht-Standard-Auswertung für weitere Details.

+1

Sie brauchen eigentlich 'as.name' hier nicht, oder? –

+2

Sie scheinen es im 'interp'-Aufruf zu brauchen. –

+0

Danke @Konrad, ich habe den ersten Aufruf von 'as.name' entfernt, der zweite (wie @BondedDust darauf hinweist) scheint notwendig. – asnr

2

Ich habe eine Funktion in der Vergangenheit geschrieben, die etwas Ähnliches tut, was Sie tun, außer dass es alle Spalten außerhalb des Primärschlüssels untersucht und nach mehreren eindeutigen Werten pro Gruppe sucht.

find_dups = function(.table, ...) { 
    require(dplyr) 
    require(tidyr) 
    # get column names of primary key 
    pk <- .table %>% select(...) %>% names 
    other <- names(.table)[!(names(.table) %in% pk)] 
    # group by primary key, 
    # get number of rows per unique combo, 
    # filter for duplicates, 
    # get number of distinct values in each column, 
    # gather to get df of 1 row per primary key, other column, 
    # filter for where a columns have more than 1 unique value, 
    # order table by primary key 
    .table %>% 
    group_by(...) %>% 
    mutate(cnt = n()) %>% 
    filter(cnt > 1) %>% 
    select(-cnt) %>% 
    summarise_each(funs(n_distinct)) %>% 
    gather_('column', 'unique_vals', other) %>% 
    filter(unique_vals > 1) %>% 
    arrange(...) %>% 
    return 
    # Final dataframe: 
    ## One row per primary key and column that creates duplicates. 
    ## Last column indicates how many unique values of 
    ## the given column exist for each primary key. 
} 

Diese Funktion arbeitet auch mit dem Rohrhalter:

dat %>% find_dups(key1, key2) 
2

Sie lazyevaldo unter Verwendung vermeiden kann eine anonyme Funktion aufrufen und dann get verwenden. Diese Lösung kann allgemeiner verwendet werden, um mehrere Aggregationen zu verwenden. Normalerweise schreibe ich die Funktion separat.

library(dplyr) 

not.uniq.per.group <- function(df, grp.var, uniq.var) { 
    df %>% 
    group_by_(grp.var) %>% 
    do((function(., uniq.var) { 
     with(., data.frame(n_uniq = n_distinct(get(uniq.var)))) 
    }  
)(., uniq.var)) %>% 
    filter(n_uniq > 1) 
} 

not.uniq.per.group(iris, "Sepal.Length", "Sepal.Width") 
7

In der Entwick-Version von dplyr (in Kürze veröffentlicht 0.6.0 werden), können wir auch für die Weitergabe der Variablen Verwendung von etwas anderen Syntax machen.

f1 <- function(df, grp.var, uniq.var) { 
    grp.var <- enquo(grp.var) 
    uniq.var <- enquo(uniq.var) 

    df %>% 
     group_by(!!grp.var) %>% 
     summarise(n_uniq = n_distinct(!!uniq.var)) %>% 
     filter(n_uniq >1) 


} 

res2 <- f1(iris, Sepal.Length, Sepal.Width) 
res1 <- not.uniq.per.group(iris, "Sepal.Length", "Sepal.Width") 
identical(res1, res2) 
#[1] TRUE 

enquo Hier nimmt die Argumente und gibt den Wert als quosure (ähnlich in der Basis R ersetzen), indem die Funktionsargumente lazily Auswerte- und innerhalb des Summarize, wir sie bitten, unquote (!! oder UQ) so dass es ausgewertet wird.

+0

Wenn ich die "Sepal.Length" und "Sepal.Width" von einer Liste bekomme, funktioniert es nicht, da es im Format von "Sepal.Length" und "Sepal.Width" sein wird Das tue ich dann? – KillerSnail

+0

@KillerSnail Sie sollten eine neue Frage stellen, da diese Lösung spezifisch für das Problem ist, das im OP-Beitrag erwähnt wird. – akrun

+0

Die Frage von @KillerSnail ist im Wesentlichen die Frage, die ich gerade hier gestellt habe: https://StackOverflow.com/Questions/46310123/ r-dplyr-operate-on-a-column-Nur-nach-seinem-String-Name bekannt – bmosov01

7

Wie die alten dplyr-Versionen bis 0,5 bietet der neue dplyr sowohl die Standardauswertung (SE) als auch die Nichtstandardauswertung (NSE). Aber sie sind anders ausgedrückt als zuvor.

Wenn Sie eine NSE-Funktion wünschen, haben Sie pass bare expressions and use enquo to capture them as quosures. Wenn Sie eine SE-Funktion wünschen, lassen Sie enquo weg und übergeben Sie einfach Quorements (oder Symbole) direkt. Hier ist die SE-Lösung auf die Frage:

library(tidyverse) 
library(rlang) 
f1 <- function(df, grp.var, uniq.var) { 

    df %>% 
     group_by(!!grp.var) %>% 
     summarise(n_uniq = n_distinct(!!uniq.var)) %>% 
     filter(n_uniq > 1) 
} 

a <- f1(iris, quo(Sepal.Length), quo(Sepal.Width)) 
b <- f1(iris, sym("Sepal.Length"), sym("Sepal.Width")) 
identical(a, b) 
#> [1] TRUE 

Hinweis, wie die SE-Version ermöglicht es Ihnen, mit String-Argumenten zu arbeiten - gerade biegen sie in Symbole erste sym() verwenden. Weitere Informationen finden Sie in der Vignette programming with dplyr.

Verwandte Themen