Menu
flag Ar iconالعربيةdown icon

أخطاء R الشائعة وكيفية تنقيحها

أداة لفك شفرة رسائل الخطأ الكلاسيكية في R، من object not found وcould not find function وnon-numeric argument وأخواتها، إضافة إلى tryCatch وtraceback() والتنقيح الصادق بالطباعة.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

قراءة رسالة خطأ في 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" اللاحقة التي تختفي كلها حين تصلح الأصل. وبقية هذه الصفحة مفكاك شفرة للرسائل التي ستصادفها أكثر، ثم الأدوات التي تلزم حين لا تكفي القراءة.

أخطاء الأسماء: غير موجود

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: أجريت عملية حسابية على شيء ليس رقمًا، وعادةً على رقم وصل نصًا (والاستيرادات هي المصدر الكلاسيكي، فالعمود الذي يحوي كلمة شاردة واحدة يصل نصيًا كما يغطيه مستند أنواع البيانات). والنسخة المعطوبة:

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: تنتمي $ إلى القوائم وأطر البيانات. وعلى متجه مسمّى استخدم الأقواس:

(تعطي [[ ]] القيمة المجردة، وتُبقي [ ] الاسم ملتصقًا.) وهذا الخطأ يعني غالبًا أن شيئًا سابقًا أرجع متجهًا بينما توقعت إطار بيانات، فاذهب وتحقق من ذلك الافتراض بدل مجرد تبديل المعامل.

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 ربما، وهو "ما هذا الكائن فعلًا؟":

قراءة مضغوطة واحدة: إنه قائمة من عنصرين، أحدهما متجه أعداد صحيحة والآخر إطار بيانات بهذه الأعمدة وهذه الأنواع. وحين تفشل $ أو يسيء الحساب التصرف، طبّق str() على الكائن قبل التنظير، فالجواب موجود هناك عادةً ("...آه، إنه قائمة طولها 1 تحتوي على إطار بياناتي").

الخلاصة

  • اقرأ الرسالة: الجزء بعد Error in يقول أين، والجزء بعد النقطتين يقول لماذا. وأصلح الخطأ الأول لا الأعلى صوتًا.
  • object not found = خطأ إملائي، أو شيفرة لم تُشغَّل بعد، أو متغير لم يوجد إلا داخل دالة. وcould not find function = استدعاء library() مفقود في الغالب الأعم.
  • تعني unexpected symbol نحوًا غير قابل للتحليل، والخطأ الحقيقي غالبًا علامة اقتباس أو قوس غير مغلق قبل الموضع المؤشَّر.
  • كلاسيكيات الأنواع والفهرسة، أي non-numeric argument وsubscript out of bounds و$ على المتجهات الذرية و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() سلسلة استدعاءات الدوال التي كانت نشطة لحظة وقوع الخطأ، باستدعائك في طرف والاستدعاء الفاشل في الطرف الآخر. وهي لا تصلح شيئًا، لكنها تخبرك أين تنظر، وذلك معظم المعركة حين يأتي الخطأ من أعماق دوال متداخلة.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن