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

القيمة NA في R: التعامل مع القيم المفقودة (‏is.na وna.rm وna.omit)

تعني NA "مجهول" - وهي تنتشر عبر كل حساب تلمسه. كيف تكتشفها بـ is.na()، وتتخطاها بـ na.rm = TRUE، وتحذفها أو تستبدلها عن قصد.

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

القيمة NA تعني "مجهول" - وهي تنتشر

مجموعات البيانات الواقعية بها ثقوب: سؤال الاستبيان الذي تُرك فارغًا، والمستشعر الذي أسقط قراءة. وتمثل R كل ثقب بالقيمة NA - اختصارًا لـ غير متاح. والنموذج الذهني الحاسم: أن NA ليست صفرًا، ولا سلسلة نصية فارغة، ولا رقمًا خاصًا. بل تعني "هناك قيمة هنا، لكننا لا نعرف ما هي."

خذ ذلك على محمل الجد فيصير سلوك R منطقيًا. فما ناتج جمع رقم مجهول مع واحد؟ مجهول. وما متوسط 4 و8 وشيء مجهول؟ مجهول - فالقيمة المفقودة قد تكون أي شيء، والمتوسط قد يكون أي شيء:

تطبع الثلاثة كلها NA. وهذه العدوى ميزة لا علة: فـ R ترفض أن تتظاهر بهدوء بمعرفة جواب لا تعرفه. فجدول البيانات الذي يتخطى الفراغات بصمت يمكنه إخفاء حقيقة أن نصف عمود مفقود؛ أما R فتجعلك تلاحظ وتقرر ما ينبغي أن تعنيه القيم المفقودة لتحليلك. وبقية هذا المقال عن اتخاذ ذلك القرار عن قصد.

فحص وجود NA: ‏is.na() لا == أبدًا

إليك أول فخ يقع فيه الجميع. تريد أن تجد القيم المفقودة فتكتب مقارنة - ولا تعمل:

يعيد x == NA القيم NA NA NA - ولا قيمة TRUE واحدة. ولماذا؟ اتبع منطق "المجهول": هل يساوي 4 قيمة مجهولة ما؟ لا يمكن الجزم - فالمجهول قد يكون 4. وحتى خانة NA تُقارن بالنتيجة NA: هل يساوي مجهول مجهولًا آخر؟ مجهول. فالمقارنة مع NA لا يمكن أن تعيد TRUE ولا FALSE أبدًا، وبذلك تكون بلا فائدة اختبارًا - والأسوأ أن قناعًا كله NA داخل [ ] لا يختار ما تتوقعه.

والأداة الصحيحة هي is.na() المبنية للإجابة عن هذا السؤال بالضبط، وتعيد قيمًا منطقية صادقة: ‏FALSE TRUE FALSE. ومن هناك، ثمة صيغتان ستستخدمهما باستمرار:

تعدّ sum(is.na(x)) القيم المفقودة (إذ تُجمع TRUE بوصفها 1) - نفّذها على كل عمود في مجموعة بيانات جديدة قبل أي شيء آخر. وتحدد which(is.na(x)) مواضعها. ويبقي x[!is.na(x)] على القيم المرصودة وحدها - أي حذف يدوي عبر قناع منطقي، بنمط ترشيح المتجهات المعتاد نفسه.

تخطي NA في الملخصات: ‏na.rm = TRUE

لا تحتاج عادة إلى حذف قيم NA يدويًا، لأن دوال التلخيص في R تملك مخرج نجاة مدمجًا - وهو الوسيط na.rm (أي حذف NA):

مع na.rm = TRUE، تسقط الدالة القيم المفقودة وتحسب على ما تبقى: فمتوسط 4 و8 هو 6. والدوال sum() وsd() وmedian() وmin() وmax() وvar() - أي عائلة الإحصاء الوصفي كلها - تقبل هذا الوسيط.

ولاحظ أن الافتراضي هو FALSE. وتلك R تنحاز لصالحك: فهي تريد أن يكون تجاهل البيانات المفقودة اختيارًا صريحًا تدوّنه، لا افتراضًا صامتًا. فحين تكتب na.rm = TRUE، فأنت تؤكد أن "القيم المفقودة يمكن تجاهلها بأمان هنا" - وهو صحيح حين يفوّت مستشعر بضع قراءات عشوائيًا، وخاطئ خطأً خطرًا حين يتخطى أصحاب الدخل الأدنى سؤال الدخل مثلًا. فالوسيط يجعلك تتحمل مسؤولية ذلك القرار.

إسقاط الصفوف غير المكتملة: ‏na.omit() وcomplete.cases()

في إطار البيانات، يسكن الفقدان في الخلايا، لكن التحليل يعمل غالبًا صفًا صفًا - ولذا فالعملية الشائعة هي إسقاط الصفوف التي تحتوي أي قيمة NA:

تعيد na.omit(df) إطار البيانات ناقصًا كل صف يحتوي قيمة NA واحدة على الأقل - وهنا لا ينجو سوى Rosa. وتعيد complete.cases(df) قناع الصفوف المنطقي (TRUE FALSE FALSE) الكامن خلف الفكرة نفسها، وتعطي الفهرسة به نتائج متطابقة مع ميزتين: إذ يمكنك عدّ ما أنت على وشك خسارته أولًا (sum(!complete.cases(df)))، ويمكنك تقييد الأعمدة المهمة - فالتعبير df[complete.cases(df[, "age"]), ] لا يسقط سوى الصفوف التي ينقصها age، فتبقى Mia رغم أن درجتها مجهولة.

وتلك الصيغة المقيدة بالأعمدة أهم مما تبدو: فاستخدام na.omit() على إطار بيانات عريض قد يتخلص بصمت من معظم صفوفك بسبب قيم NA في أعمدة لم تخطط لتحليلها أصلًا. اعرف كم صفًا تسقط، ولماذا، قبل أن تسقطه.

استبدال NA

أحيانًا تكون الحركة الصحيحة سد الثقوب لا حذف الصفوف. وتجمع الصيغة بين is.na() والإسناد:

اقرأها هكذا: "في الخانات التي يكون فيها visits مفقودًا، ضع 0". وهذا مشروع بالضبط حين ترمّز NA قيمة معروفة - فالعميل الذي لا سجل زيارات له كان عدد زياراته صفرًا فعلًا.

لكن كن صادقًا بشأن متى يصح ذلك. فإذا كانت NA تعني "أخفقنا في القياس"، فإن وضع 0 مكانها يفبرك بيانات: إذ إن استبدال درجات اختبار مفقودة بالصفر يجر المتوسط نحو الأسفل كأن أولئك الطلاب حصلوا على صفر، بينما أنت في الحقيقة لا تعرف درجاتهم. قارن المتوسط الناتج (2.4) بمتوسط الأصل مع na.rm (وهو 4): البيانات نفسها وادعاءان مختلفان. والاستبدال بالمتوسط أو الوسيط يشوّه أقل لكنه ما يزال يبخس التباين. والقاعدة: لا تُسنِد قيمة إلا حين تستطيع أن تقول بكلمات واضحة لماذا كانت تلك القيمة هي ما احتوته المدخلة المفقودة فعلًا. وإلا فأبقِ على NA واستخدم na.rm - فـ "مجهول" هي غالبًا أصدق قيمة في مجموعة البيانات.

‏NA مقابل NULL مقابل NaN مقابل Inf

تملك R أربع قيم خاصة متشابهة المظهر تعني أشياء مختلفة فعلًا:

القيمةالمعنىالطولالمصدر النمطي
NAقيمة موجودة لكنها مجهولة1 (تشغل خانة)البيانات المفقودة
NULLلا كائن على الإطلاق0 (بلا خانة)عنصر قائمة محذوف، أو نتيجة فارغة
NaNعملية حسابية بجواب غير معرّف10 / 0 وlog(-1)
Infعدد يتجاوز حدود التمثيل11 / 0 والطفح

والفروق المهمة عمليًا: أن NULL يختفي داخل المتجهات (فالتعبير c(1, NULL, 3) يحوي عنصرين - إذ لا يستطيع تمثيل مشاهدة مفقودة)، بينما تحتفظ NA بمكانها. وتُعد NaN مفقودة (فـ is.na(NaN) تساوي TRUE، ولذا يزيلها na.rm أيضًا)، لكن العكس غير صحيح - فـ is.nan(NA) تساوي FALSE. والقيمة Inf ليست مفقودة - بل هي عدد حقيقي قابل للمقارنة (فـ Inf > 1e300 تساوي TRUE)، ولذا لن يزيلها na.rm؛ استخدم is.finite() للترشيح إلى الأعداد الاعتيادية.

وللإكمال: تأتي NA بهدوء في نكهات مصنّفة (NA_integer_ وNA_real_ وNA_character_) كي تستطيع الجلوس في أي متجه دون كسر قاعدة النوع الواحد. ونادرًا ما ستكتبها، لكنك ستراها في شيفرة الحزم ورسائل خطأ dplyr.

ما تخرج به

  • تعني NA "مجهول"، والمجاهيل معدية: فأي حساب يلمس NA يعيد NA - بحكم التصميم.
  • افحص بـ is.na() لا بـ == NA أبدًا؛ وعُدّ الثقوب بـ sum(is.na(x)).
  • يجعل na.rm = TRUE دوال التلخيص تتخطى قيم NA - وهو قرار صريح لكل استدعاء بأن القيم المفقودة قابلة للتجاهل.
  • تسقط na.omit() الصفوف غير المكتملة جملةً؛ وتمنحك complete.cases() القناع، قابلًا للعد وللتقييد بالأعمدة المهمة.
  • لا تستبدل NA (x[is.na(x)] <- value) إلا حين تستطيع تبرير ما كانت عليه القيمة المفقودة فعلًا.
  • ‏NA ≠ NULL ≠ NaN ≠ Inf: قيمة مفقودة، وكائن غائب، وعملية حسابية غير معرّفة، وعدد بلا حدود.

التالي: الدوال - تغليف منطقك في قطع مسماة قابلة لإعادة الاستخدام.

الأسئلة الشائعة

لماذا تعيد mean() القيمة NA في R؟

لأن قيمة واحدة على الأقل في المتجه تساوي NA، وNA معدية: فإذا كان أي مدخل مجهولًا، قالت R إن الجواب مجهول كذلك. مرر na.rm = TRUE - أي mean(x, na.rm = TRUE) - لحساب متوسط القيم الموجودة. ومعظم دوال التلخيص (sum وsd وmedian وmin وmax) تقبل الوسيط نفسه.

كيف تفحص وجود NA في R؟

بالدالة is.na(x) التي تعيد TRUE أينما كانت القيمة مفقودة. ولا تختبر أبدًا بالصيغة x == NA - فمقارنة أي شيء بمجهول تعطي NA لا TRUE ولا FALSE، فيفشل الاختبار بصمت. وتعدّ sum(is.na(x)) القيم المفقودة؛ وتجد which(is.na(x)) مواضعها.

كيف تحذف الصفوف التي تحتوي NA من إطار بيانات في R؟

تسقط na.omit(df) كل صف يحتوي على قيمة NA واحدة على الأقل. ولمزيد من التحكم، تعيد complete.cases(df) متجهًا منطقيًا يحدد الصفوف المرصودة كاملةً، فيؤدي df[complete.cases(df), ] الأمر نفسه صراحة - ويمكنك تطبيقه على الأعمدة المهمة وحدها، مثل df[complete.cases(df[, c("age", "score")]), ].

ما الفرق بين NA وNULL في R؟

القيمة NA هي قيمة مفقودة - فهي تشغل خانة في متجه وطولها 1. أما NULL فهو غياب الكائن - طوله 0 ويتلاشى حين يوضع في متجه: فالتعبير c(1, NULL, 3) لا يحتوي إلا على عنصرين. استخدم NA لنقطة بيانات مفقودة؛ ويظهر NULL عند حذف عناصر القوائم أو بوصفه ناتجًا فارغًا.

Coddy programming languages illustration

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

ابدأ الآن