הבעיה ש-pipes פותרים
pipe לוקח את הערך שלפניו ומעביר אותו לקריאת הפונקציה שאחריו: x |> f() פירושו f(x). זה נשמע כמו שכתוב טריוויאלי עד ששרשרים שלושה או ארבעה שלבים. בלי pipe, טרנספורמציות מרובות שלבים מתקננות, וקריאות מקוננות נקראות מהסוף להתחלה: הדבר הראשון שקורה יושב במיקום הפנימי ביותר.
כדי לקרוא את round(sqrt(x), 1) מתחילים באמצע (x), מתקדמים החוצה (sqrt, ואז round), ועוקבים איזה , 1) בסוף שייך לאיזו קריאה. בשלוש רמות עומק זה מעצבן; בחמש, עם פעלים של data frames וכמה ארגומנטים לכל אחד, באמת קשה לעקוב. החלופה שאנשים פונים אליה, לשמור כל שלב ביניים ב-tmp1, tmp2, tmp3, ממלאת את סביבת העבודה בשמות שאף אחד לא צריך.
pipe כותב מחדש את השרשרת בסדר שבו השלבים באמת קורים: קחו את x, ואז הוציאו לו שורש, ואז עגלו.
ה-pipe המובנה |>
מאז R 4.1, ה-pipe בנוי לתוך השפה עצמה, בלי צורך בחבילה. |> מכניס את הערך הקודם כארגומנט הראשון של הקריאה הבאה:
c(1, 4, 9, 16) |> sqrt() |> round(1) הוא בדיוק round(sqrt(c(1, 4, 9, 16)), 1): R ממש כותב אחד מהם מחדש לשני בזמן הניתוח, כך שאין שום תקורה בזמן ריצה. אבל עכשיו הקוד נקרא כמו מתכון, מלמעלה למטה: הנתונים, ואז כל טרנספורמציה לפי הסדר. זה עובד עם כל פונקציה, לא רק עם כלי נתונים: mtcars |> head(3) הוא R בסיסי מתחילתו ועד סופו.
שימו לב כמה טוב pipes מתאימים לפונקציות שהפרמטר הראשון שלהן הוא "הנתונים": head, sort, unique, summary וכל פועל של dplyr מתוכננים כך, ולכן pipelines מרגישים כל כך טבעיים ב-R.
הכללים של |>
ה-pipe המובנה נוקשה בכוונה. שלושה כללים מכסים כמעט הכול:
1. השלב הבא חייב להיות קריאה, עם סוגריים. x |> sqrt היא שגיאת תחביר; חייבים לכתוב x |> sqrt():
c(1, 4, 9) |> sqrt # Error: The pipe operator requires a function call as RHS
c(1, 4, 9) |> sqrt() # correct
2. הערך נוחת במיקום הארגומנט הראשון. ארגומנטים נוספים שאתם כותבים נשארים במקומם אחריו: x |> round(1) הוא round(x, 1).
3. כדי לנחות במקום אחר, השתמשו בממלא המקום _ עם ארגומנט בעל שם (R 4.2+). המקרה הקלאסי: lm מקבלת קודם נוסחה ואחר כך את הנתונים, ולכן העברת data frame אליה ב-pipe צריכה data = _:
ה-_ יכול להופיע פעם אחת, ורק כארגומנט בעל שם. כשזה מגביל מדי, העבירו ב-pipe לפונקציה אנונימית, שיכולה לשים את הערך איפה שהיא רוצה:
ה-lambda עטופה בסוגריים ואז נקראת עם (), מה שממשיך לקיים את כלל 1. אם אתם מוצאים את עצמכם עושים את זה הרבה ב-pipeline אחד, השלב הזה כנראה רוצה להיות פונקציה בעלת שם.
ה-pipe של magrittr %>%
לפני שלשפה היה pipe, החבילה magrittr סיפקה אחד, ו-%>% הפך לחתימה של קוד tidyverse: טעינת dplyr נותנת לכם אותו אוטומטית. תראו אותו כמעט בכל מדריך ובכל סקריפט של dplyr שנכתבו בעשור האחרון:
library(dplyr)
mtcars %>%
filter(mpg > 30) %>%
select(mpg, wt)
מכיוון ש-%>% מגיע מחבילה, אי אפשר להריץ את הדוגמאות האלה בסשן R חשוף: Error: could not find function "%>%" הוא בדיוק מה שמקבלים אם שוכחים את library(dplyr) (או את library(magrittr)).
ההתנהגות במקרה הנפוץ זהה ל-|>: להעביר את הערך הקודם לארגומנט הראשון של הקריאה הבאה. אבל magrittr מתירני יותר:
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
|> מול %>%: ההבדלים שחשובים
ב-pipeline שעובד על הארגומנט הראשון, שהוא הרוב המכריע של קוד אמיתי, השניים ניתנים להחלפה. ההבדלים חיים בשוליים:
- זמינות:
|>הוא R בסיסי 4.1+;%>%צריך את magrittr (בדרך כלל דרך dplyr). - סוגריים:
|>דורשsqrt();%>%מקבלsqrtחשוף. - ממלא מקום:
|>משתמש ב-_, פעם אחת, רק כארגומנט בעל שם (4.2+);%>%משתמש ב-., בכל מקום, כמה פעמים שרוצים, כולל בתוך קריאות מקוננות. - מהירות:
|>נכתב מחדש בזמן הניתוח לקריאה המקוננת, בלי שום מחיר.%>%הוא קריאת פונקציה רגילה עם תקורה קטנה (זניחה בפועל).
שום דבר כאן לא הופך את %>% לשגוי: הוא נבדק היטב בשטח וגמיש מעט יותר. אבל כל מה שהוא עושה מעבר ל-|> אפשר לעשות עם lambda, וה-pipe הבסיסי לא צריך שום תלות.
באיזה מהם להשתמש?
לקוד חדש: השתמשו ב-|>. הוא חלק מהשפה, עובד בכל מקום שבו R 4.1+ רץ, לא צריך חבילה, והנוקשות שלו היא תכונה: pipelines נשארים פשוטים או עוברים ארגון מחדש לפונקציות בעלות שם.
אבל חייבים להיות מסוגלים לקרוא %>% בשטף, כי בסיסי קוד של tidyverse, תשובות ב-Stack Overflow ורוב ספרי ה-R שנכתבו מאז 2014 מלאים בו. צוותים עם סגנון dplyr קיים משאירים לעיתים קרובות את %>% לשם עקביות, וזו החלטה סבירה. הבחירה הגרועה ביותר היא לערבב את שני האופרטורים בקובץ אחד. בכל אחד שתכתבו, מוסכמה אחת עוברת: ב-pipelines ארוכים, שימו כל שלב בשורה משלו עם ה-pipe בסוף השורה, כך שהמתכון נקרא כרשימה של שלבים.
אותו טעם חל כאן כמו עם משפחת apply: pipes מיועדים לטרנספורמציות לינאריות, שלב אחרי שלב. כשחישוב מתפצל או צריך תוצאות ביניים פעמיים, הציבו משתנה עם שם טוב במקום לדחוס הכול לשרשרת אחת.
מה לקחת מכאן
- pipe מעביר את הערך הקודם לקריאה הבאה:
x |> f() |> g()הואg(f(x)), כתוב בסדר שבו זה קורה. |>הוא R בסיסי (4.1+): דורש סוגריים, מכוון לארגומנט הראשון, ממלא המקום_עם ארגומנט בעל שם (4.2+).%>%הוא magrittr/tidyverse: שמות חשופים מותרים, ממלא המקום.בכל מקום, אבל הוא צריך חבילה.- העדיפו
|>בקוד חדש; קראו%>%בכל מקום אחר בלי למצמץ. - pipes מתאימים למתכונים לינאריים; לוגיקה מתפצלת ראויה למשתנים בעלי שם.
בהמשך: חבילות, המקור של %>%, של dplyr ושל 20,000 ההרחבות האחרות של R.
שאלות נפוצות
מה פירוש %>% ב-R?
%>% הוא ה-pipe של magrittr: הוא לוקח את הערך שלפניו ומעביר אותו כארגומנט הראשון של הקריאה שאחריו, כך ש-x %>% f() %>% g() פירושו g(f(x)). הוא מגיע מהחבילה magrittr ונטען אוטומטית עם dplyr וה-tidyverse; הוא אינו חלק מ-R הבסיסי.
האם צריך חבילה כדי להשתמש ב-|> ב-R?
לא. |> הוא ה-pipe המובנה, שנבנה לתוך R הבסיסי מאז גרסה 4.1: הוא עובד בסשן R רגיל בלי שום דבר מותקן. %>% הוא זה שצריך חבילה (magrittr, או כל חבילה שמייצאת אותו מחדש, כמו dplyr).
מה ההבדל בין |> ל-%>% ב-R?
שניהם מעבירים את הערך הקודם לקריאה הבאה. |> הוא R בסיסי, דורש סוגריים מפורשים (x |> sqrt()), ומשתמש בממלא המקום _ רק עם ארגומנט בעל שם. %>% צריך את magrittr, מקבל שמות פונקציות חשופים (x %>% sqrt), וממלא המקום . שלו יכול להופיע בכל מקום, כמה פעמים שרוצים. ההתנהגות זהה במקרה הנפוץ של הארגומנט הראשון.