Il problema che le pipe risolvono
Una pipe prende il valore che la precede e lo passa alla chiamata di funzione che la segue: x |> f() significa f(x). Sembra una riscrittura banale finché non concateni tre o quattro passaggi. Senza pipe, le trasformazioni in più passaggi si annidano, e le chiamate annidate si leggono al contrario: la prima cosa che succede sta nella posizione più interna.
Per leggere round(sqrt(x), 1) parti dal centro (x), vai verso l'esterno (sqrt, poi round) e tieni traccia di quale , 1) finale appartiene a quale chiamata. Con tre livelli è fastidioso; con cinque livelli, con verbi per data frame e più argomenti ciascuno, diventa davvero difficile da seguire. L'alternativa a cui si ricorre, salvare ogni passaggio intermedio in tmp1, tmp2, tmp3, riempie lo spazio di lavoro di nomi che non servono a nessuno.
Una pipe riscrive la catena nell'ordine in cui i passaggi avvengono davvero: prendi x, poi calcolane la radice quadrata, poi arrotondala.
La pipe nativa |>
Da R 4.1 la pipe è integrata nel linguaggio stesso, senza bisogno di pacchetti. |> inserisce il valore precedente come primo argomento della chiamata successiva:
c(1, 4, 9, 16) |> sqrt() |> round(1) è esattamente round(sqrt(c(1, 4, 9, 16)), 1): R riscrive letteralmente l'una nell'altra durante l'analisi del codice, quindi il costo in esecuzione è zero. Ma ora il codice si legge come una ricetta, dall'alto in basso: i dati, poi ogni trasformazione in ordine. Funziona con qualsiasi funzione, non solo con gli strumenti per i dati: mtcars |> head(3) è R base dall'inizio alla fine.
Nota quanto le pipe si adattano bene alle funzioni il cui primo parametro è "i dati": head, sort, unique, summary e ogni verbo di dplyr sono progettati così, ed è per questo che le pipeline sembrano così naturali in R.
Le regole di |>
La pipe nativa è volutamente severa. Tre regole coprono quasi tutto:
1. Il passaggio successivo deve essere una chiamata, con le parentesi. x |> sqrt è un errore di sintassi; devi scrivere x |> sqrt():
c(1, 4, 9) |> sqrt # Error: The pipe operator requires a function call as RHS
c(1, 4, 9) |> sqrt() # correct
2. Il valore finisce nella posizione del primo argomento. Gli argomenti aggiuntivi che scrivi restano al loro posto dopo di esso: x |> round(1) è round(x, 1).
3. Per farlo finire altrove, usa il segnaposto _ con un argomento con nome (R 4.2+). Caso classico: lm prende prima una formula e poi i dati, quindi passargli un data frame con la pipe richiede data = _:
Il _ può comparire una sola volta, e solo come argomento con nome. Quando è troppo restrittivo, passa il valore a una funzione anonima, che può metterlo dove vuole:
La lambda è racchiusa tra parentesi e poi chiamata con (), così la regola 1 resta rispettata. Se ti ritrovi a farlo spesso in una stessa pipeline, probabilmente quel passaggio vuole diventare una funzione con un nome.
La pipe di magrittr %>%
Prima che il linguaggio avesse una pipe, ne forniva una il pacchetto magrittr, e %>% è diventato il marchio del codice tidyverse: caricare dplyr te lo dà automaticamente. Lo vedrai in quasi ogni tutorial e script dplyr scritto nell'ultimo decennio:
library(dplyr)
mtcars %>%
filter(mpg > 30) %>%
select(mpg, wt)
Dato che %>% viene da un pacchetto, questi esempi non si possono eseguire in una sessione R base: Error: could not find function "%>%" è esattamente ciò che ottieni se dimentichi library(dplyr) (o library(magrittr)).
Nel caso comune il comportamento è identico a |>: passare il valore precedente al primo argomento della chiamata successiva. Ma magrittr è più permissivo:
c(1, 4, 9) %>% sqrt # bare function name - no parentheses needed
mtcars %>% lm(mpg ~ wt, data = .) # . placeholder, usable anywhere
c(-2, 3) %>% { max(abs(.)) } # . can even appear several times, in nested calls
|> contro %>%: le differenze che contano
In una pipeline sul primo argomento, cioè la stragrande maggioranza del codice reale, le due sono intercambiabili. Le differenze stanno ai margini:
- Disponibilità:
|>è R base 4.1+;%>%ha bisogno di magrittr (di solito tramite dplyr). - Parentesi:
|>pretendesqrt();%>%accettasqrtda solo. - Segnaposto:
|>usa_, una volta, solo come argomento con nome (4.2+);%>%usa., ovunque, quante volte vuoi, anche dentro chiamate annidate. - Velocità:
|>viene riscritto durante l'analisi nella chiamata annidata, a costo zero.%>%è una normale chiamata di funzione con un piccolo costo aggiuntivo (in pratica trascurabile).
Niente di tutto questo rende %>% sbagliato: è collaudato e leggermente più flessibile. Ma tutto ciò che fa in più rispetto a |> si può fare con una lambda, e la pipe di base non ha bisogno di dipendenze.
Quale usare?
Per il codice nuovo: usa |>. Fa parte del linguaggio, funziona ovunque giri R 4.1+, non ha bisogno di pacchetti, e la sua severità è un pregio: le pipeline restano semplici oppure vengono riorganizzate in funzioni con un nome.
Ma devi saper leggere %>% con scioltezza, perché le basi di codice tidyverse, le risposte su Stack Overflow e la maggior parte dei libri su R scritti dal 2014 ne sono pieni. I team con uno stile dplyr già consolidato spesso tengono %>% per coerenza, ed è una scelta ragionevole: la scelta peggiore è mescolare i due operatori nello stesso file. Qualunque dei due scrivi, una convenzione vale per entrambi: nelle pipeline lunghe, metti ogni passaggio sulla sua riga con la pipe a fine riga, così la ricetta si legge come un elenco di passaggi.
Lo stesso gusto vale qui come per la famiglia apply: le pipe servono per trasformazioni lineari, un passaggio dopo l'altro. Quando un calcolo si ramifica o ha bisogno due volte di risultati intermedi, assegna una variabile con un buon nome invece di forzare tutto in un'unica catena.
Cosa ti porti a casa
- Una pipe passa il valore precedente alla chiamata successiva:
x |> f() |> g()èg(f(x)), scritto nell'ordine in cui avviene. |>è R base (4.1+): richiede le parentesi, punta al primo argomento, segnaposto_con un argomento con nome (4.2+).%>%è magrittr/tidyverse: nomi senza parentesi ammessi, segnaposto.ovunque, ma ha bisogno di un pacchetto.- Preferisci
|>nel codice nuovo; leggi%>%ovunque senza battere ciglio. - Le pipe sono adatte alle ricette lineari; la logica che si ramifica merita variabili con un nome.
Prossimo passo: i pacchetti, da dove vengono davvero %>%, dplyr e le altre 20,000 estensioni di R.
Domande frequenti
Cosa significa %>% in R?
%>% è la pipe di magrittr: prende il valore che la precede e lo passa come primo argomento della chiamata che la segue, quindi x %>% f() %>% g() significa g(f(x)). Viene dal pacchetto magrittr e si carica automaticamente con dplyr e il tidyverse: non fa parte di R base.
Serve un pacchetto per usare |> in R?
No. |> è la pipe nativa, integrata in R base dalla versione 4.1: funziona in una normale sessione R senza installare niente. È %>% che ha bisogno di un pacchetto (magrittr, o qualsiasi pacchetto che lo riesporta, come dplyr).
Qual è la differenza tra |> e %>% in R?
Entrambe passano il valore precedente alla chiamata successiva. |> è di R base, richiede parentesi esplicite (x |> sqrt()) e usa il segnaposto _ solo con un argomento con nome. %>% ha bisogno di magrittr, accetta nomi di funzione senza parentesi (x %>% sqrt) e il suo segnaposto . può andare ovunque, quante volte vuoi. Nel caso comune del primo argomento il comportamento è identico.