לולאת for הבסיסית
לולאת for ב-R מריצה בלוק קוד פעם אחת לכל איבר של וקטור או רשימה. משתנה הלולאה מקבל כל ערך בתורו:
שלושה איברים, שלושה סבבים. אין מונה להגדיר ואין תנאי לתחזק: for (fruit in fruits) נקרא "לכל fruit ב-fruits", וזה בדיוק מה שהוא עושה. אותה צורה עוברת על איברי רשימה, על רצף של מספרים (for (i in 1:10)), או על השמות של כל דבר בעזרת names().
הדפסה בתוך לולאה דורשת print() או cat()
בקונסולה, הקלדת x מדפיסה את הערך שלו. בתוך גוף לולאה זה לא קורה: הדפסה אוטומטית קורית רק ברמה העליונה, וביטוי חשוף בתוך לולאה מוערך ונזרק:
אז כשלולאה "לא עושה כלום", זה הדבר הראשון שצריך לבדוק. השתמשו ב-print() למבט מהיר על כל ערך, או ב-cat() כשאתם מרכיבים שורת פלט בעצמכם (זכרו את ה-"\n": cat() לא מוסיפה אחד).
מעבר על אינדקסים עם seq_along()
לפעמים צריך את המיקום ולא רק את הערך: כדי לכתוב לווקטור אחר באותו אינדקס, או כדי להדפיס רשימה ממוספרת. עברו על אינדקסים עם seq_along():
תראו for (i in 1:length(prices)) בקוד ישן יותר, ומסתתר בו באג אמיתי. כשהווקטור ריק, length() הוא 0, ו-1:0 לא אומר "אפס סבבים": הנקודתיים סופרות למטה:
לולאה על 1:length(empty) רצה פעמיים וניגשת לאיברים שלא קיימים (empty[1] הוא NA, והשמות יוצרות ערכי רפאים). seq_along() מחזירה רצף ריק לווקטור ריק, כך שגוף הלולאה פשוט לא רץ אף פעם. הפכו את seq_along() להרגל; אחותה seq_len(n) עושה את אותה עבודה כשיש לכם מספר ולא וקטור.
איסוף תוצאות: להקצות מראש, לא להגדיל
האינסטינקט הטבעי הראשון לבניית תוצאות נראה כך:
squares <- c()
for (i in 1:10000) {
squares <- c(squares, i^2) # copies the ENTIRE vector every pass
}
זה עובד, אבל כל c(squares, ...) מקצה וקטור חדש לגמרי ומעתיק אליו את כל האיברים הישנים. עד הסוף העתקתם בערך 50 מיליון מספרים כדי לייצר 10,000. דפוס האנטי הזה של וקטור שגדל הוא הסיבה הגדולה ביותר שאנשים אומרים שלולאות ב-R איטיות.
התיקון: צרו את התוצאה בגודל המלא קודם, ואז בצעו השמה לפי אינדקס:
לתוצאות שאינן מספרים, אותו רעיון חל עם character(n), logical(n), או vector("list", n) לרשימה של דברים כלשהם. לולאות עם הקצאה מראש מהירות לגמרי.
next ו-break
שתי מילות מפתח מכוונות לולאה מתוך הגוף שלה: next נוטשת את הסבב הנוכחי וקופצת לאיבר הבא; break יוצאת מהלולאה לגמרי.
זה מדפיס 2 4 6 8: ערכים אי-זוגיים מדולגים על ידי next, וכשמגיע 10, break מסיימת את הלולאה לפני ההדפסה. השתמשו ב-next כדי לסנן מקרים מוקדם (זה שומר את הגוף הראשי בלי הזחה נוספת), וב-break כשלולאה מצאה את מה שחיפשה ואין לה סיבה להמשיך.
לולאות מקוננות
גוף לולאה יכול להכיל לולאה אחרת. הלולאה הפנימית רצה עד הסוף בכל סבב של החיצונית; ההדגמה הקלאסית היא לוח הכפל:
לכל שורה i, הלולאה הפנימית עוברת על כל העמודות j, ושורה חדשה מודפסת אחרי שהלולאה הפנימית מסתיימת. לולאות מקוננות בסדר בשתי רמות; בשלוש ומעלה, הגוף בדרך כלל מבקש להפוך לפונקציה, או שכל החישוב מבקש להיות outer() או פעולת מטריצה.
קינון מכסה גם מעבר על רשימה של וקטורים: לולאה חיצונית על הרשימה, לולאה פנימית (או עדיף, קריאה וקטורית) על כל איבר:
שימו לב שבסוף אין לולאה פנימית: mean() מטפלת בכל הווקטור הפנימי בקריאה אחת. התצפית הזו נכונה באופן כללי, וזה מביא אותנו לחלק הכן.
מתי לא להשתמש בלולאה
R היא שפה וקטורית: הפעולות הבסיסיות שלה כבר עובדות על וקטורים שלמים בבת אחת. לולאה שמשנה כל איבר אחד בכל פעם היא לעיתים קרובות דרך ארוכה יותר לכתוב שורה אחת:
העדיפו את הצורה הווקטורית בכל פעם שהיא קיימת: היא קצרה יותר, קשה יותר לטעות בה, ומהירה יותר. להפעלת פונקציה על כל איבר ברשימה, משפחת apply (sapply, lapply, vapply) ממלאת את אותו תפקיד.
אבל אל תהפכו את זה בעיוורון ל"לולאות הן רעות". לולאה היא הכלי הנכון כשסבבים תלויים בסבבים קודמים (מצב מצטבר, סימולציות), כשאתם מבצעים תופעות לוואי כמו כתיבת קבצים, או פשוט כשהלולאה היא הגרסה שאתם והקוראים שלכם מבינים במבט אחד. לולאה ברורה עדיפה על שורה מתוחכמת שאף אחד לא מצליח לפענח.
מה לקחת מכאן
for (x in v) { ... }מריצה את הגוף פעם אחת לכל איבר, בלי הנהלת חשבונות של מונה.- בתוך לולאה, הדפיסו במפורש עם
print()אוcat(); ביטויים חשופים נזרקים. - עברו על מיקומים עם
seq_along(v), אף פעם לא1:length(v): האחרון רץ פעמיים על וקטור ריק. - הקצו תוצאות מראש עם
numeric(n)/vector("list", n)ובצעו השמה לפי אינדקס; הגדלה עםc()מעתיקה הכול בכל סבב. nextמדלגת על סבב,breakיוצאת; העדיפו פעולות וקטוריות כשהן קיימות, אבל אל תפחדו מלולאה קריאה.
הבא בתור: לולאות שרצות עד שמשהו קורה ולא פעם אחת לכל איבר: while ו-repeat.
שאלות נפוצות
איך כותבים לולאת for ב-R?
for (x in v) { ... }: משתנה הלולאה x מקבל בתורו כל איבר של הווקטור (או הרשימה) v, והגוף רץ פעם אחת לכל איבר. כדי לעבור על מיקומים במקום על ערכים, השתמשו ב-for (i in seq_along(v)) וגשו עם v[i].
למה להשתמש ב-seq_along במקום 1:length(v) ב-R?
כש-v ריק, length(v) הוא 0, ולכן 1:length(v) הופך ל-1:0, הווקטור בן שני האיברים c(1, 0), וגוף הלולאה רץ פעמיים על איברים שלא קיימים. seq_along(v) מחזירה רצף ריק לווקטור ריק, כך שהלולאה רצה באופן נכון אפס פעמים.
למה לולאת ה-for שלי ב-R לא מדפיסה כלום?
ההדפסה האוטומטית של R קורית רק לביטויים שמוקלדים ברמה העליונה של הקונסולה. בתוך גוף לולאה, ביטוי חשוף כמו x מוערך ונזרק. עטפו אותו ב-print(x), או השתמשו ב-cat(...) כשאתם רוצים לעצב את הפלט בעצמכם.
האם לולאות for איטיות ב-R?
הלולאה עצמה בסדר: מה שאיטי הוא הגדלת וקטור בתוכה עם result <- c(result, ...), שמעתיקה את כל הווקטור בכל סבב. הקצו מראש עם numeric(n) או vector("list", n) ובצעו השמה לפי אינדקס, ולולאה מתפקדת מצוין. עם זאת, כשפעולה וקטורית או קריאה ממשפחת apply מבטאת את אותו רעיון, היא בדרך כלל גם מהירה יותר וגם ברורה יותר.