2015-01-15 12 views
39

voglio parametrizzare il seguente calcolo utilizzando dplyr che trova che i valori di Sepal.Length sono associati con più di un valore di Sepal.Width:argomenti Passo a dplyr funzioni

library(dplyr) 

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

Normalmente vorrei scrivere qualcosa di simile:

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

Tuttavia, questo approccio genera errori poiché dplyr utilizza non-standard evaluation. Come dovrebbe essere scritta questa funzione?

+3

Per una questione di stile, sconsiglio di usare il punto nei nomi nella moderna R, tranne che nei generici S3. È terribilmente confuso. La convenzione di denominazione usata (tra l'altro) da 'dplyr' è molto più bella:' names_with_underscores'. –

+1

Sono consapevole che [la guida allo stile di Hadley Wickham] (http://adv-r.had.co.nz/Style.html) raccomanda la notazione del trattino basso, ma la [Guida allo stile di Google R] (https: // google -styleguide.googlecode.com/svn/trunk/Rguide.xml) promuove il periodo (anche se non per le funzioni, che ho fatto qui). In altre lingue il periodo è utilizzato per l'accesso ai membri (ad esempio 'myArray.length' in javascript), c'è un altro conflitto in R? – asnr

+7

Le guide di stile di Google sono spesso terribili. In questo caso particolare, il problema è che conduce le ambiguità con i metodi S3: è 'some.class.method' un metodo' some' di classe 'class.method' o è un metodo' some.class' di classe ' method'? Inoltre, porta a nomi incoerenti quando parti del codice sono implementate in C (++), dal momento che non supporta i punti nei nomi, rendendo necessaria la mappatura dei nomi delle funzioni di backend a diversi nomi R. –

risposta

37

È necessario utilizzare le versioni di valutazione standard delle funzioni dplyr (basta aggiungere '_' per i nomi delle funzioni, vale a dire. group_by_ & summarise_) e passare le stringhe alla funzione, che è quindi necessario trasformare in simboli. Per parametrizzare l'argomento di riepilogo_, è necessario utilizzare interp(), che è definito nel pacchetto lazyeval. In concreto:

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

Vedi l'dplyrvignette per la valutazione non standard per ulteriori dettagli.

+1

In realtà non hai bisogno di 'as.name' qui, vero? –

+2

Sembra che ti serva nella chiamata "interp'. –

+0

Grazie a @Konrad, ho rimosso la prima chiamata a 'as.name', la seconda (come sottolineato da @BondedDust) sembra necessaria. – asnr

2

Ho scritto una funzione in passato che fa qualcosa di simile a quello che stai facendo, tranne che esplora tutte le colonne al di fuori della chiave primaria e cerca più valori univoci per gruppo.

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. 
} 

Questa funzione opera anche con l'operatore tubazioni:

dat %>% find_dups(key1, key2) 
2

si può evitare lazyeval utilizzando do per chiamare una funzione anonima e quindi utilizzando get. Questa soluzione può essere utilizzata più in generale per utilizzare più aggregazioni. Di solito scrivo la funzione separatamente.

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

Nella versione devel di dplyr (di prossima uscita 0.6.0), possiamo anche fare uso di sintassi leggermente diversa per il passaggio delle variabili.

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 

Qui enquo prende gli argomenti e restituisce il valore quosure (simile a sostituire nella base di R) valutando gli argomenti della funzione pigramente e all'interno riassumono, si chiede a unquote (!! o UQ) così che viene valutato

+0

se ottengo 'Sepal.Length' e' Sepal.Width' da una lista non funziona come sarà nel formato di '" Sepal.Length "' e '" Sepal.Width "' cosa può Lo faccio allora? – KillerSnail

+0

@KillerSnail Dovresti pubblicare una nuova domanda in quanto questa soluzione è specifica per il problema menzionato nel post dell'OP – akrun

+0

La domanda sopra di @KillerSnail è essenzialmente la domanda che ho appena posto qui: https://stackoverflow.com/questions/46310123/ r-dplyr-funzionare-su-un-colonna noto solo-by-its-stringa-nome – bmosov01

7

Come le versioni precedenti di dplyr fino a 0,5, il nuovo dplyr dispone di funzionalità sia per la valutazione standard (SE) che per la valutazione non standard (NSE). Ma sono espressi in modo diverso rispetto a prima.

Se si desidera una funzione NSE, si pass bare expressions and use enquo to capture them as quosures. Se si desidera una funzione SE, si omette lo enquo e si passano direttamente le quosures (oi simboli). Ecco la soluzione SE alla domanda:

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 

Si noti come la versione SE consente di lavorare con argomenti stringa - appena li trasformano in simboli prima utilizzando sym(). Per ulteriori informazioni, vedere la vignetta programming with dplyr.