2015-06-10 14 views
21

In riferimento a this question, stavo cercando di capire il modo più semplice per applicare un elenco di funzioni a un elenco di valori. Fondamentalmente, un nidificato lapply. Ad esempio, ecco applichiamo sd e mean di costruire nel set di dati trees:Applica l'elenco di funzioni all'elenco di valori

funs <- list(sd=sd, mean=mean) 
sapply(funs, function(x) sapply(trees, x)) 

per ottenere:

   sd  mean 
Girth 3.138139 13.24839 
Height 6.371813 76.00000 
Volume 16.437846 30.17097 

ma speravo di evitare l'interno function e avere qualcosa di simile:

sapply(funs, sapply, X=trees) 

che non funziona perché X corrisponde al primo sapply invece del secondo. Possiamo farlo con functional::Curry:

sapply(funs, Curry(sapply, X=trees)) 

ma speravo forse c'era un modo intelligente per fare questo con posizionale e il nome di corrispondenza che mi manca.

+5

hadley ha scritto un intero capitolo su questo argomento: http://adv-r.had.co.nz/Functional-programming.html#lists-of-functions, dal momento che io non sono più intelligente di quanto io sappia di un modo migliore per farlo – grrgrrbla

+0

Non più semplice, ma carino se si desidera un ordinato data.frame alla fine: 'library (purrr); map_df (funs, ~ map_df (trees, .x), .id = 'statistic') ' – alistaire

risposta

18

Dal mapply uso puntini di sospensione ... di passare vettori (atomiche o liste) e non un argomento di nome (X) come in sapply, lapply, etc ... non è necessario dare un nome al parametro di X = trees se si utilizza invece mapply di sapply:

funs <- list(sd = sd, mean = mean) 

x <- sapply(funs, function(x) sapply(trees, x)) 

y <- sapply(funs, mapply, trees) 

> y 
       sd  mean 
Girth 3.138139 13.24839 
Height 6.371813 76.00000 
Volume 16.437846 30.17097 
> identical(x, y) 
[1] TRUE 

Eri una lettera vicina per ottenere quello che stavi cercando! :)

Nota che ho utilizzato un elenco per funs perché non riesco a creare un dataframe di funzioni, ho ricevuto un errore.

> R.version.string 
[1] "R version 3.1.3 (2015-03-09)" 
+3

Molto intelligente, userà sicuramente questo in futuro; Penso che la caratteristica chiave sia che "mapply" accetti l'argomento della funzione come primo argomento, quindi tutto funziona. – BrodieG

13

Fondamentalmente avremo bisogno di una funzione anonima di qualche tipo perché non ci sarebbe un altro modo per distinguere i parametri denominati per le due diverse chiamate sapply. Hai già mostrato una funzione anonima esplicita e il metodo Curry. Si potrebbe anche usare magrittr

library(magrittr) 
sapply(funs, . %>% sapply(trees, .)) 
# or .. funs %>% sapply(. %>% sapply(trees, .)) 

ma il punto è necessario qualcosa lì per fare la scissione. Il "problema" è che il numero sapply viene inviato a lapply che è un internal function che sembra determinato a collocare i valori di modifica come l'inizio della chiamata di funzione. Hai bisogno di qualcosa per riordinare i parametri e grazie agli identici set di nomi dei parametri non è possibile metterli a parte senza una funzione di supporto per occuparsi della disambiguazione.

La funzione mapply consente di passare un elenco a "MoreArgs" che consente di aggirare il conflitto dei parametri denominato. Questo è destinato a dividere tra gli elementi che si devono vettorializzare e quelli che sono fissi. Così si può fare

mapply(sapply, funs, MoreArgs=list(X=trees)) 
#    sd  mean 
# Girth 3.138139 13.24839 
# Height 6.371813 76.00000 
# Volume 16.437846 30.17097 
+1

Bello con' MoreArgs'. Immagino che 'magrittr' potrebbe essere' funs%>% sapply (.%>% Sapply (X = trees)) '? Decisamente ha avuto un po 'di un doppio prendere vedere il '.' come il primo elemento nel tubo. – BrodieG

+1

Sì, l'ho aggiunto anche se penso che la prima versione sia più chiara. A dire il vero, penso che il modo migliore sia semplicemente usare la funzione anonima esplicita come hai fatto la prima volta: 'sapply (funs, function (x) sapply (trees, x))' – MrFlick

+0

concordato; Ho modificato di nuovo per rimuovere un extra '.', ma non sicuro al 100% seguo la mia logica ... – BrodieG

0

Anche se non così edificanti né elegante come la soluzione presentata da @ Floo0, qui è ancora un altro prendere utilizzando tidyr e dplyr:

library(dplyr) 
library(tidyr) 

fns <- funs(sd = sd, mean = mean) 
trees %>% 
    gather(property, value, everything()) %>% 
    group_by(property) %>% 
    summarise_all(fns) 

# A tibble: 3 x 3 
# property  sd  mean 
#  <chr>  <dbl> <dbl> 
# 1 Girth 3.138139 13.24839 
# 2 Height 6.371813 76.00000 
# 3 Volume 16.437846 30.17097 

Questa sequenza di operazioni fa un lavoro decente di segnalazione intenti , a costo di verbosità extra.

5

altro approccio utilizza purrr sarebbe:

require(purrr) 

funs <- list(sd=sd, mean=mean) 
trees %>% map_df(~invoke_map(funs, ,.), .id="id") 

Importante: Notare il secondo argomento vuoto invoke_map per abbinare per posizione. Vedi esempi ?purrr::invoke_map.

che ti dà:

Source: local data frame [3 x 3] 

     id  sd  mean 
    <chr>  <dbl> <dbl> 
1 Girth 3.138139 13.24839 
2 Height 6.371813 76.00000 
3 Volume 16.437846 30.17097 

Invece di rownames questo approccio si dà una colonna id contenente le colonne originali.

+0

Quando si usa purrr 0.2.2 (e possibilmente anche versioni precedenti, non ho controllato), è necessario usare 'invoke_map_df()', piuttosto che 'invoke_map()', per ottenere il risultato mostrato. – egnha

+0

@egnha, questo è strano. Per me funziona bene con 'purrr_0.2.2'. L'uso di 'invoke_map_df' porta a' Errore: impossibile convertire l'oggetto in un frame di dati' ... Quale versione di R stai usando? – Rentrop

+0

Questo è sconcertante. Sto usando R 3.3.0; ha eseguito il codice in una nuova sessione con solo purrr (e nessun file di init caricato da R). Moralmente, 'invoke_map_df' è il corretto' invoke_map * 'da applicare (e funziona correttamente sulla mia macchina), dal momento che' map_df' crea un frame dati vincolando le righe (a meno che non abbia frainteso qualcosa). – egnha

Problemi correlati