Problem, który rozwiązuje pipe
Pipe bierze wartość przed sobą i przekazuje ją do wywołania funkcji po sobie: x |> f() oznacza f(x). Brzmi to jak banalne przepisanie, dopóki nie połączysz trzech albo czterech kroków. Bez pipe'a wieloetapowe przekształcenia się zagnieżdżają, a zagnieżdżone wywołania czyta się od tyłu: to, co dzieje się najpierw, stoi na pozycji najbardziej wewnętrznej.
Żeby przeczytać round(sqrt(x), 1), zaczynasz od środka (x), idziesz na zewnątrz (sqrt, potem round) i pilnujesz, które końcowe , 1) należy do którego wywołania. Przy trzech poziomach to irytujące; przy pięciu, z czasownikami na ramkach danych i kilkoma argumentami każdy, naprawdę trudno za tym nadążyć. Alternatywa, po którą ludzie sięgają, czyli zapisywanie każdego kroku pośredniego w tmp1, tmp2, tmp3, zaśmieca przestrzeń roboczą nazwami, których nikt nie potrzebuje.
Pipe przepisuje łańcuch w kolejności, w jakiej kroki faktycznie się dzieją: weź x, potem wyciągnij pierwiastek, potem zaokrąglij.
Natywny pipe |>
Od R 4.1 pipe jest wbudowany w sam język, bez żadnego pakietu. |> wstawia poprzednią wartość jako pierwszy argument kolejnego wywołania:
c(1, 4, 9, 16) |> sqrt() |> round(1) to dokładnie round(sqrt(c(1, 4, 9, 16)), 1): R dosłownie przepisuje jedno na drugie podczas parsowania, więc narzut w czasie działania jest zerowy. Teraz jednak kod czyta się jak przepis, od góry do dołu: dane, a potem każde przekształcenie po kolei. Działa to z każdą funkcją, nie tylko z narzędziami do danych: mtcars |> head(3) to od początku do końca zwykły bazowy R.
Zauważ, jak dobrze pipe pasuje do funkcji, których pierwszym parametrem są "dane": head, sort, unique, summary i każdy czasownik dplyr są zaprojektowane w ten sposób, i dlatego potoki są w R tak naturalne.
Zasady |>
Natywny pipe jest celowo surowy. Trzy zasady obejmują prawie wszystko:
1. Kolejny krok musi być wywołaniem, z nawiasami. x |> sqrt to błąd składni; musisz napisać x |> sqrt():
c(1, 4, 9) |> sqrt # Error: The pipe operator requires a function call as RHS
c(1, 4, 9) |> sqrt() # correct
2. Wartość trafia na pozycję pierwszego argumentu. Dodatkowe argumenty, które piszesz, zostają na swoich miejscach po niej: x |> round(1) to round(x, 1).
3. Żeby trafić gdzie indziej, użyj symbolu zastępczego _ z nazwanym argumentem (R 4.2+). Klasyczny przypadek: lm przyjmuje najpierw formułę, a dane jako drugie, więc przekazanie do niego ramki danych wymaga data = _:
_ może wystąpić raz i tylko jako nazwany argument. Gdy to zbyt duże ograniczenie, przekaż wartość do funkcji anonimowej, która umieści ją, gdzie zechce:
Lambda jest owinięta w nawiasy, a potem wywołana przez (), dzięki czemu zasada 1 jest spełniona. Jeśli często robisz to w jednym potoku, ten krok prawdopodobnie powinien być nazwaną funkcją.
Pipe %>% z magrittr
Zanim język miał własny pipe, dostarczał go pakiet magrittr, a %>% stał się znakiem rozpoznawczym kodu tidyverse. Wczytanie dplyr daje ci go automatycznie. Zobaczysz go w prawie każdym poradniku i skrypcie z dplyr napisanym w ostatniej dekadzie:
library(dplyr)
mtcars %>%
filter(mpg > 30) %>%
select(mpg, wt)
Ponieważ %>% pochodzi z pakietu, tych przykładów nie da się uruchomić w czystej sesji R: Error: could not find function "%>%" to dokładnie to, co dostajesz, gdy zapomnisz o library(dplyr) (albo library(magrittr)).
W typowym przypadku zachowanie jest identyczne jak w |>: poprzednia wartość trafia do pierwszego argumentu kolejnego wywołania. Magrittr jest jednak bardziej pobłażliwy:
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
|> kontra %>%: różnice, które mają znaczenie
W potoku z pierwszym argumentem, czyli w ogromnej większości prawdziwego kodu, oba są wymienne. Różnice leżą na krawędziach:
- Dostępność:
|>jest w bazowym R 4.1+;%>%wymaga magrittr (zwykle przez dplyr). - Nawiasy:
|>wymagasqrt();%>%przyjmuje samosqrt. - Symbol zastępczy:
|>używa_, raz, tylko jako nazwanego argumentu (4.2+);%>%używa., wszędzie i dowolną liczbę razy, również w zagnieżdżonych wywołaniach. - Szybkość:
|>jest przepisywany podczas parsowania na zagnieżdżone wywołanie, bez żadnego kosztu.%>%to zwykłe wywołanie funkcji z małym (w praktyce pomijalnym) narzutem.
Nic z tego nie czyni %>% złym wyborem: jest sprawdzony w boju i nieco bardziej elastyczny. Ale wszystko, co robi ponad |>, da się zrobić lambdą, a pipe z bazowego R nie wymaga żadnej zależności.
Którego używać?
W nowym kodzie: używaj |>. Jest częścią języka, działa wszędzie, gdzie działa R 4.1+, nie wymaga pakietu, a jego surowość to zaleta: potoki pozostają proste albo są refaktoryzowane do nazwanych funkcji.
Musisz jednak płynnie czytać %>%, bo pełno go w bazach kodu tidyverse, odpowiedziach na Stack Overflow i większości książek o R napisanych od 2014 roku. Zespoły z istniejącym stylem dplyr często zostają przy %>% dla spójności i to rozsądna decyzja. Najgorszym wyborem jest mieszanie obu operatorów w jednym pliku. Niezależnie od tego, którego używasz, jedna konwencja się przenosi: w długich potokach umieszczaj każdy krok w osobnej linii z pipe'em na końcu linii, żeby przepis czytał się jak lista kroków.
Ten sam gust obowiązuje co przy rodzinie apply: pipe służy do liniowych przekształceń krok po kroku. Gdy obliczenie się rozgałęzia albo potrzebuje wyników pośrednich dwa razy, przypisz dobrze nazwaną zmienną, zamiast przepychać wszystko przez jeden łańcuch.
Co warto zapamiętać
- Pipe przekazuje poprzednią wartość do kolejnego wywołania:
x |> f() |> g()tog(f(x))zapisane w kolejności, w jakiej się dzieje. |>jest w bazowym R (4.1+): wymaga nawiasów, trafia w pierwszy argument, symbol zastępczy_z nazwanym argumentem (4.2+).%>%pochodzi z magrittr/tidyverse: dozwolone same nazwy, symbol zastępczy.wszędzie, ale wymaga pakietu.- W nowym kodzie wybieraj
|>;%>%czytaj wszędzie indziej bez mrugnięcia okiem. - Pipe pasuje do liniowych przepisów; rozgałęziona logika zasługuje na nazwane zmienne.
Dalej: pakiety, czyli skąd naprawdę pochodzą %>%, dplyr i pozostałe 20 000 rozszerzeń R.
Najczęściej zadawane pytania
Co oznacza %>% w R?
%>% to pipe z magrittr: bierze wartość przed nim i przekazuje ją jako pierwszy argument wywołania po nim, więc x %>% f() %>% g() oznacza g(f(x)). Pochodzi z pakietu magrittr i wczytuje się automatycznie razem z dplyr i tidyverse; nie jest częścią bazowego R.
Czy potrzebuję pakietu, żeby używać |> w R?
Nie. |> to natywny pipe, wbudowany w bazowy R od wersji 4.1: działa w zwykłej sesji R bez instalowania czegokolwiek. Pakietu wymaga %>% (magrittr albo coś, co go reeksportuje, np. dplyr).
Czym różni się |> od %>% w R?
Oba przekazują poprzednią wartość do kolejnego wywołania. |> jest w bazowym R, wymaga jawnych nawiasów (x |> sqrt()) i używa symbolu zastępczego _ tylko z nazwanym argumentem. %>% wymaga magrittr, przyjmuje same nazwy funkcji (x %>% sqrt), a jego symbol zastępczy . może stać wszędzie i dowolną liczbę razy. W typowym przypadku z pierwszym argumentem zachowanie jest identyczne.