איך קוראים הודעת שגיאה של R
לשגיאה של R יש שני חלקים, ושניהם שימושיים:
Error in "10" + 5 : non-numeric argument to binary operator
אחרי Error in מגיעה הקריאה: קטע הקוד המדויק שנכשל ("10" + 5). אחרי הנקודתיים מגיע התנאי: מה השתבש (non-numeric argument to binary operator). קראו קודם את הקריאה: היא אומרת לכם איפה, ולעיתים קרובות מאוד כבר אפשר לראות את הבעיה ממש שם בקוד המצוטט. אחר כך קראו את התנאי כדי להבין למה.
שני הרגלים מבדילים בין מי שמדבג מהר לבין מי שסובל. ראשית, קראו את ההודעה באמת: ההודעות של R בדרך כלל מדויקות, רק מנוסחות בקיצור. שנית, דבגו את השגיאה הראשונה, לא את האחרונה: כישלון אחד מוקדם בסקריפט מתגלגל לערימה של שגיאות "object not found" בהמשך, וכולן נעלמות כשמתקנים את המקורית. שאר העמוד הזה הוא מפענח להודעות שתפגשו הכי הרבה, ואחריו הכלים למקרים שבהם קריאה לא מספיקה.
שגיאות השם: not found
Error: object 'total' not found: R חיפשה בכל סביבה שהיא מכירה ואין משתנה בשם הזה. שלוש סיבות מכסות כמעט כל מקרה:
- שגיאת הקלדה, כולל אותיות גדולות וקטנות. R רגישה לאותיות גדולות וקטנות:
Total,totalו-TOTALהם שלושה שמות שונים, ו-R לא תנחש לאיזה מהם התכוונתם. - השורה שמגדירה אותו לא רצה. כתבתם
total <- sum(x)בסקריפט אבל אף פעם לא הרצתם אותה בסשן הזה. זה נפוץ אחרי הפעלה מחדש של R, כשקובץ הסקריפט עדיין מציג את השורה אבל הסשן מעולם לא ראה אותה. הריצו את הסקריפט מההתחלה. - סביבה שגויה. משתנים שנוצרים בתוך פונקציה חיים ומתים בתוך הקריאה הזו. שימוש באחד מהם מחוץ לפונקציה הוא בקשה למשהו שכבר לא קיים: החזירו את הערך במקום.
Error: could not find function "read_excel": אותו רעיון, אבל לשם של פונקציה. בתשעה מקרים מתוך עשרה הפונקציה נמצאת בחבילה שהתקנתם אבל לא טענתם בסשן הזה:
library(readxl) # the fix: loading is per-session, installing is per-machine
df <- read_excel("data.xlsx")
אם library(readxl) עצמה נכשלת, החבילה לא מותקנת: קודם install.packages("readxl"). ואם הפונקציה היא מ-base R, הקלדתם אותה לא נכון (lenght() הוא טקס חניכה של כולם).
שגיאות תחביר: unexpected symbol
Error: unexpected symbol in "..." (ובני הדודים שלה unexpected ')', unexpected string constant) פירושה ש-R אפילו לא הצליחה לפענח את הקוד. ההודעה מצביעה על המקום שבו R שמה לב, ולעיתים קרובות זה אחרי המקום שבו נמצאת הטעות האמיתית. החשודים הרגילים:
mean(x na.rm = TRUE) # missing comma - should be mean(x, na.rm = TRUE)
name <- "Ada # unclosed quote - swallows the following lines
total <- sum(c(1, 2, 3) # unclosed paren - the error fires lines later
כשהשורה המסומנת נראית תמימה, הטעות כמעט תמיד מעליה: מירכאה, סוגריים או סוגריים מסולסלים שלא נסגרו מוקדם יותר בקובץ. עורך קוד שמדגיש זוגות תואמים מוצא אותן בתוך שניות.
שגיאות סוג ואינדקס, בפענוח
non-numeric argument to binary operator: ביצעתם חישוב על משהו שאינו מספר, בדרך כלל מספר שהגיע כטקסט (ייבוא נתונים הוא המקור הקלאסי: עמודה עם מילה תועה אחת נכנסת כ-character, כפי שמוסבר בסוגי נתונים). הגרסה השבורה:
x <- "10"
x + 5
# Error in x + 5 : non-numeric argument to binary operator
והתיקון: המירו, ואז חשבו:
subscript out of bounds: ביקשתם את המיקום n במשהו שיש בו פחות מ-n איברים, עם [[ ]]:
scores <- list(ada = 92, grace = 88)
scores[[3]]
# Error in scores[[3]] : subscript out of bounds
בדקו את length() לפני גישה לפי אינדקס, או עדיף, בקשו לפי שם (scores[["grace"]]) כך ששינוי סדר לא ישבור לכם כלום. שימו לב לחוסר הסימטריה: סוגריים בודדים סלחניים יותר: [ ] מחוץ לטווח על וקטור מחזיר בשקט NA במקום שגיאה, וזה מחליף באג רועש בבאג שקט.
$ operator is invalid for atomic vectors: $ שייך לרשימות ול-data frames. על וקטור עם שמות, השתמשו בסוגריים:
([[ ]] נותן את הערך החשוף; [ ] משאיר את השם מחובר.) השגיאה הזו אומרת לעיתים קרובות שמשהו בשלב מוקדם יותר החזיר וקטור כשציפיתם ל-data frame: בדקו את ההנחה הזו במקום רק להחליף את האופרטור.
argument is of length zero: if () קיבל תנאי שאין בו כלום, כמעט תמיד NULL שהתגנב מאיבר רשימה חסר או מפונקציה שלא החזירה כלום:
threshold <- NULL
if (threshold > 5) print("big")
# Error in if (threshold > 5) print("big") : argument is of length zero
הגנו על הבדיקה, ושימו לב ש-&& מפסיק להעריך ברגע שהתשובה ידועה, כך שההשוואה לעולם לא רצה על NULL:
(שגיאה קרובה, missing value where TRUE/FALSE needed, היא אותו כישלון עם NA במקום NULL: ההגנה שם היא is.na(), שמוסברת בערכים חסרים.)
replacement has length zero: גרסת ההשמה של אותה מחלה: x[2] <- numeric(0) מנסה למלא משבצת אחת באפס ערכים. מה שייצר את הצד הימני של ההשמה חזר ריק; דבגו אותו, לא את ההשמה.
אזהרות אינן שגיאות, וזו בדיוק הסכנה
שגיאה עוצרת את הריצה; אזהרה לא. R מסיימת את החישוב, מגישה לכם תוצאה ומזכירה את ההסתייגויות שלה אחר כך. התוצאה הזו לפעמים בסדר ולפעמים שגויה בשקט:
שתי השורות מסתיימות. הראשונה ממחזרת את הווקטור הקצר יותר ומזהירה longer object length is not a multiple of shorter object length, ומחזור של וקטור באורך 2 מול וקטור באורך 3 כמעט אף פעם אינו מה שמישהו התכוון אליו. השנייה מזהירה NAs introduced by coercion ומגישה וקטור עם חור, שיגרום לכל mean() בהמשך להחזיר NA. התייחסו לשתי האזהרות כבאגים שצריך לחקור, לא כרעש שגוללים הלאה. בסקריפטים אפשר לאכוף את הגישה הזו עם options(warn = 2), שהופכת כל אזהרה לשגיאה כך ששום דבר לא חומק.
טיפול בכישלונות עם tryCatch()
לפעמים שגיאה היא צפויה: קובץ פגום אחד בתיקייה של מאות, שורה רעה אחת, ואתם רוצים לטפל בה ולהמשיך במקום למות. tryCatch() עוטפת ביטוי מסוכן במטפלים:
המנגנון: אם הבלוק הראשי מצליח, הערך שלו הוא התוצאה. אם הוא נכשל, המטפל error = רץ במקום, וערך ההחזרה שלו (כאן NA) הופך לתוצאה: הסקריפט ממשיך. conditionMessage(e) משחזרת את ההודעה המקורית לרישום בלוג. finally = רץ בכל מקרה, הצלחה או כישלון, ושם המקום לניקוי כמו סגירת חיבורים: אפשר לראות אותו מודפס לפני כל תוצאה למעלה.
יש גם מטפל warning =: tryCatch(as.numeric(x), warning = function(w) NA) תופס את אזהרת כפיית הסוג מהסעיף הקודם במקום לתת לה לעבור. אזהרה אחת: מטפל שמחזיר ערך חלופי בלי לרשום שום דבר הוא דרך להסתיר כישלונות, לא לטפל בהם. תמיד רשמו את conditionMessage(): אתם של העתיד תצטרכו את זה.
איתור הכישלון: traceback(), browser() והדפסה כנה
כשהשגיאה מגיעה מעומק קריאות מקוננות לפונקציות, ההודעה לבדה לא אומרת איזו שרשרת קריאות הביאה אתכם לשם. הריצו traceback() מיד אחרי השגיאה:
f <- function(x) g(x)
g <- function(x) stop("boom")
f(1)
# Error in g(x) : boom
traceback()
# 2: g(x)
# 1: f(1)
היא מדפיסה את מחסנית הקריאות ברגע הכישלון: הקריאה שלכם בקצה אחד, הקריאה שנכשלה בקצה השני. זה חייב להיות הדבר הבא שאתם מריצים; המחסנית נמחקת ברגע ששגיאה אחרת מתרחשת.
למבט חי, browser() עוצרת את הריצה בכל מקום שבו שתלתם אותה ומכניסה אתכם לשורת פקודה אינטראקטיבית בתוך הפונקציה: בדקו משתנים, התקדמו צעד עם n, המשיכו עם c, צאו עם Q. debug(f) עושה את אותו דבר בלי לערוך קוד: היא מסמנת את f כך שהקריאה הבאה אליה נפתחת בדפדפן הדיבוג (בטלו עם undebug(f)).
ואז יש את הטכניקה שאף אחד לא שם בשקפים של כנסים אבל כולם משתמשים בה: הדפסה. פזרו print() או cat() בנקודות ביקורת, הריצו, וראו איפה המציאות מפסיקה להתאים לציפיות שלכם. זה לגיטימי, זה מהיר, ובסקריפטים זה לעיתים קרובות הכלי המעשי ביותר. החבר הכי טוב שלו הוא str(), שעונה על השאלה שעומדת מאחורי אולי חצי מכל השגיאות ב-R: "מה האובייקט הזה באמת?":
פלט קומפקטי אחד: זו רשימה, שני איברים, אחד וקטור של מספרים שלמים, אחד data frame עם העמודות והסוגים האלה. כש-$ נכשל או כשחישוב מתנהג מוזר, הפעילו str() על האובייקט לפני שאתם מפתחים תיאוריות: התשובה בדרך כלל נמצאת ממש שם ("...אה, זו רשימה באורך 1 שמכילה את ה-data frame שלי").
מה לקחת מכאן
- קראו את ההודעה: החלק שאחרי
Error inאומר איפה, החלק שאחרי הנקודתיים אומר למה. תקנו את השגיאה הראשונה, לא את הרועשת ביותר. object not found= שגיאת הקלדה, קוד שעוד לא רץ, או משתנה שהתקיים רק בתוך פונקציה.could not find function= קריאה חסרה ל-library(), כמעט תמיד.unexpected symbolפירושה דקדוק שאי אפשר לפענח, והטעות האמיתית היא לעיתים קרובות מירכאה או סוגריים שלא נסגרו לפני המקום המסומן.- הקלאסיקות של סוג ואינדקס,
non-numeric argument,subscript out of bounds,$ on atomic vectors,argument is of length zero, כל אחת מצביעה על הנחה שגויה לגבי מה אובייקט הוא;str()בודקת את ההנחה בקריאה אחת. - אזהרות לא עוצרות את הריצה, ובדיוק בגלל זה מגיעה להן תשומת לב: התוצאה עשויה להיות שגויה בשקט.
tryCatch(error =, warning =, finally =)מטפלת בכישלונות צפויים בלי למות (תמיד רשמו אתconditionMessage());traceback()מיד אחרי שגיאה מראה את שרשרת הקריאות;browser()/debug()עוצרות בתוכה; דיבוג עםprint()הוא עבודה כנה.
הבא בתור: הרבה "שגיאות" שאינן שגיאות בכלל מתחקות אחורה לערך אחד קטן: NA, ואיך ערכים חסרים זורמים דרך כל מה ש-R מחשבת.
שאלות נפוצות
מה המשמעות של "object 'x' not found" ב-R?
R חיפשה משתנה בשם x ואין שם כזה בשום סביבה שבה היא חיפשה. הסיבות, לפי סדר הסבירות: שגיאת הקלדה בשם (R רגישה לאותיות גדולות וקטנות: Total אינו total), השורה שיוצרת את x עוד לא הורצה בסשן הזה, או ש-x נוצר בתוך פונקציה ואתם מנסים להשתמש בו מחוץ לה.
מה המשמעות של "could not find function" ב-R?
הפונקציה קיימת בחבילה שלא טענתם בסשן הזה. מתקינים חבילה פעם אחת לכל מחשב; library() היא פעם אחת לכל סשן, ושכחת הקריאה ל-library() היא הסיבה הרגילה. אם library() עצמה נכשלת, החבילה לא מותקנת. שגיאת הקלדה בשם הפונקציה מייצרת את אותה שגיאה.
איך מטפלים בשגיאות ב-R עם tryCatch?
עוטפים את הביטוי המסוכן: tryCatch(expr, error = function(e) fallback, warning = function(w) fallback, finally = cleanup). אם expr נכשל, המטפל המתאים רץ במקום שהסקריפט ימות, ומה שהמטפל מחזיר הופך לתוצאה. conditionMessage(e) בתוך מטפל נותנת לכם את ההודעה המקורית לרישום בלוג.
מה עושה traceback() ב-R?
כשמריצים אותה מיד אחרי שגיאה, traceback() מדפיסה את שרשרת הקריאות לפונקציות שהייתה פעילה כשהשגיאה התרחשה: הקריאה שלכם בקצה אחד, הקריאה שנכשלה בקצה השני. היא לא מתקנת כלום; היא אומרת לכם איפה לחפש, וזה רוב הקרב כשהשגיאה הגיעה מעומק פונקציות מקוננות.