Menu

apply, lapply, sapply in R: la famiglia apply spiegata

La famiglia apply (apply, lapply, sapply, vapply, mapply e tapply) esegue una funzione su ogni elemento dei tuoi dati. Ecco cosa fa ciascuna e quando usarla.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

La famiglia apply in una frase

Ogni membro della famiglia apply fa lo stesso lavoro: prende una funzione, la esegue su ogni elemento di una struttura e raccoglie i risultati. Ciò che cambia è la forma dell'input e la forma dell'output. La mappa:

  • apply(m, MARGIN, FUN): righe o colonne di una matrice.
  • lapply(x, FUN): ogni elemento di una lista o di un vettore; restituisce sempre una lista.
  • sapply(x, FUN): come lapply, ma poi semplifica in un vettore o in una matrice quando può.
  • vapply(x, FUN, FUN.VALUE): sapply con un tipo di ritorno dichiarato e imposto.
  • mapply(FUN, x, y, ...): percorre più input in parallelo, elemento per elemento.
  • tapply(values, groups, FUN): applica FUN dentro ogni gruppo, con un risultato per gruppo.

Accettano tutte le funzioni che scrivi tu, comprese quelle anonime, ed è da qui che la famiglia trae la sua forza.

apply(): righe e colonne di una matrice

apply prende una matrice, un MARGIN (1 per le righe, 2 per le colonne) e una funzione:

La matrice si riempie colonna per colonna, quindi le righe sono 1 3 5 e 2 4 6: le somme per riga vengono stampate come 9 12 e quelle per colonna come 3 7 11. Per i due casi più comuni R base offre helper dedicati e più veloci, rowSums, colSums, rowMeans, colMeans, quindi riserva apply alle funzioni che non ne hanno uno, come apply(m, 2, max).

Una trappola: apply su un data frame lo converte prima in silenzio in una matrice, forzando tutte le colonne a un unico tipo comune. Per i data frame, tratta le colonne come una lista e usa invece lapply/sapply.

lapply() restituisce sempre una lista, sapply() semplifica

lapply applica una funzione a ogni elemento e restituisce una lista della stessa lunghezza, qualunque cosa succeda:

Stesso calcolo, due forme: lapply ti restituisce una lista che contiene 85 e 80; sapply si accorge che ogni risultato ha lunghezza 1 e semplifica in un vettore numerico con nomi, molto più comodo da leggere e da usare in altri calcoli.

La comodità però ha un lato tagliente: il tipo di ritorno di sapply dipende dai dati. Se anche un solo elemento produce una lunghezza diversa, la semplificazione fallisce e ottieni di nuovo, in silenzio, una lista:

In console è una scrollata di spalle; dentro uno script significa codice che ha funzionato tutto l'anno e si rompe il giorno in cui i dati cambiano forma. È proprio per questo che esiste vapply.

vapply(): la sapply con controllo dei tipi

vapply aggiunge un terzo argomento, FUN.VALUE: un modello che dichiara come deve essere fatto un singolo risultato. integer(1) significa "ogni chiamata restituisce esattamente un intero":

Ottieni un vettore di interi con nomi, 5 6 6, e soprattutto una garanzia: se nchar restituisse due valori o un carattere, vapply si fermerebbe con un errore nel punto della chiamata invece di lasciare che un risultato malformato scivoli più avanti. La dichiarazione fa anche da documentazione:

vapply(words, nchar, character(1))
# Error in vapply(words, nchar, character(1)) : values must be type 'character'

Regola pratica: sapply in console, vapply nelle funzioni e negli script che devono girare senza supervisione.

mapply() e tapply(): input paralleli e gruppi

lapply percorre una sola struttura. Quando ogni chiamata ha bisogno di un elemento da più strutture nelle stesse posizioni, usa mapply; nota che la funzione viene per prima:

Ogni elemento dell'output accoppia i primi valori, poi i secondi, e così via: 10 200 3000 40000.

tapply è il membro che lavora per gruppi: divide values secondo groups e applica la funzione dentro ogni gruppo, restituendo un risultato per gruppo:

Il gruppo a ha media 30, il gruppo b ha media 40. È la risposta di R base a "media per categoria", la stessa domanda a cui group_by + summarize risponde per i data frame: se sei già nel mondo dplyr usa quello; tapply dà il meglio quando hai due vettori semplici e vuoi una riga sola.

Due comodità valgono per tutta la famiglia. Gli argomenti aggiuntivi dopo la funzione le vengono passati a ogni chiamata:

E le funzioni anonime entrano in gioco ogni volta che nessuna funzione già pronta va bene: sapply(x, \(v) max(v) - min(v)) calcola l'intervallo di ogni elemento senza dover prima dare un nome a un helper.

Famiglia apply contro cicli for

Sentirai dire che i cicli for in R sono lenti e che la famiglia apply è veloce. È in gran parte un mito. Ciò che è davvero lento è far crescere un risultato dentro un ciclo: result <- c(result, new_value) copia l'intero vettore a ogni passaggio. Un ciclo che prealloca il suo output va benissimo:

Quindi scegli in base alla leggibilità, non alle prestazioni. La versione apply dice cosa fare in una riga, "eleva al quadrato ogni elemento", e gestisce l'allocazione al posto tuo: per questo è l'idioma per il lavoro semplice elemento per elemento. Un ciclo for si guadagna il posto quando le iterazioni dipendono dai risultati precedenti, quando ti servono uscite anticipate con break, o quando il corpo è così lungo che una lambda complicherebbe le cose invece di aiutare. Entrambi sono R legittimo.

Cosa ti porti a casa

  • Tutta la famiglia è un'unica idea: esegui una funzione su ogni elemento e raccogli i risultati.
  • apply serve per le righe (MARGIN = 1) e le colonne (MARGIN = 2) di una matrice; preferisci rowSums/colMeans quando esistono.
  • lapply restituisce sempre una lista; sapply semplifica quando può, il che significa che il suo tipo di ritorno può cambiare con i dati.
  • vapply blocca il tipo di ritorno; usala nel codice che non deve riservarti sorprese.
  • mapply combina più input insieme; tapply aggrega i valori dentro i gruppi.
  • I cicli for non sono lenti di per sé: lo è far crescere i vettori. Scegli la forma che si legge meglio.

Prossimo passo: le pipe, l'operatore che concatena queste chiamate in pipeline leggibili, passo dopo passo.

Domande frequenti

Qual è la differenza tra lapply e sapply in R?

lapply restituisce sempre una lista, senza eccezioni. sapply esegue lo stesso calcolo e poi prova a semplificare il risultato: in un vettore se ogni elemento ha lunghezza 1, in una matrice se hanno tutti la stessa lunghezza, e di nuovo in una lista se non ci riesce. sapply è più comoda in console; lapply (o vapply) è più sicura negli script perché il suo tipo di ritorno non cambia mai.

Cosa fa apply() in R?

apply(m, MARGIN, FUN) esegue FUN su una matrice: MARGIN = 1 la applica a ogni riga, MARGIN = 2 a ogni colonna. apply(m, 1, sum) dà le somme per riga, apply(m, 2, mean) le medie per colonna. È pensata per matrici e array: per liste e vettori usa invece lapply/sapply.

La famiglia apply è più veloce di un ciclo for in R?

Di solito non di molto: è un mito. Un ciclo for scritto bene, con il vettore dei risultati preallocato, ha prestazioni paragonabili. I cicli che hanno fatto la cattiva fama del for fanno crescere il risultato un elemento alla volta, copiando tutto a ogni passaggio. Scegli le funzioni della famiglia apply per concisione e chiarezza, non per velocità.

A cosa serve vapply in R?

vapply è sapply con un contratto: dichiari il tipo e la lunghezza di ogni risultato, per esempio vapply(x, nchar, integer(1)). Se la funzione restituisce qualcos'altro, R solleva subito un errore invece di consegnarti in silenzio una struttura inattesa. Preferiscila nel codice che deve continuare a funzionare senza supervisione.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA