كيفية استبدال القيم المفقودة (NA) في R: na.omit & na.rm

⚡ ملخص ذكي

يتناول هذا الشرح كيفية استبدال القيم المفقودة في لغة R، بدءًا من اكتشاف القيم المفقودة (NA)، مرورًا بإزالة الصفوف غير المكتملة باستخدام دالة na.omit()، وصولًا إلى تعويضها بالمتوسط ​​أو الوسيط باستخدام دالة mutate(). ويستخدم هذا الشرح مجموعة بيانات تايتانيك، حيث تحتوي متغيرات العمر والسعر على بيانات مفقودة.

  • 🔎 الكشف أولاً: تقوم الدالة colSums(is.na(df)) بحساب القيم المفقودة لكل عمود قبل اتخاذ أي قرار بشأن كيفية التعامل معها.
  • 🗑️ إزالة الصفوف: تقوم الدالة na.omit() بحذف كل صف يحتوي على قيمة NA، مما يؤدي إلى تقليص بيانات تيتانيك من 1,309 صف إلى 1,045 صف.
  • 📐 متوسط ​​الإسناد: تقوم الدالة apply() مع na.rm = TRUE بحساب متوسط ​​العمود، وتقوم الدالة mutate() مع ifelse() بكتابته في عمود جديد.
  • 📊 البديل الوسيط: يقاوم الوسيط القيم المتطرفة، مما يجعله الخيار الأكثر أمانًا للمتغيرات المنحرفة مثل الأجرة.
  • الإسناد الجماعي: تقوم الدالة sapply() أو across() بتطبيق نفس القاعدة على كل عمود رقمي في عبارة واحدة.
  • ⚠️ المقايضة المعروفة: يؤدي استخدام المتوسطات إلى تقليص التباين وإضعاف الارتباطات، لذلك لا ينبغي تطبيقه بشكل أعمى.

استبدال القيم المفقودة NA في R

ما هي القيم المفقودة في لغة البرمجة R؟

تظهر القيم المفقودة عندما لا توجد قيمة مسجلة في عمود ما، أو عندما يحل عنصر نائب غير رقمي محل رقم. يجب إزالة هذه القيم أو استبدالها قبل إجراء أي عملية حسابية، لأن معظم دوال لغة R تُرجع قيمة NA بمجرد وجودها.

يوضح هذا البرنامج التعليمي كيفية التعامل مع القيم المفقودة باستخدام مكتبة dplyr، وهي جزء من نظام tidyverse البيئي لتحليل البيانات.

استبدال القيم المفقودة في R

تتمثل الخطوة الأولى دائمًا في معرفة عدد القيم المفقودة ومكانها.

كيفية اكتشاف القيم المفقودة وحسابها في لغة R

قبل اتخاذ قرار بشأن كيفية التعامل مع القيم المفقودة، عليك معرفة عددها ومواقعها. يوفر برنامج R أربعة خيارات للتحقق، بدءًا من إجابة بنعم أو لا وصولًا إلى إحصاء كامل لكل عمود.

# 1. Is there any NA at all?
anyNA(df_titanic)

# 2. How many in total?
sum(is.na(df_titanic))

# 3. How many per column, the most useful view
colSums(is.na(df_titanic))

# 4. Percentage missing per column
round(colMeans(is.na(df_titanic)) * 100, 1)

تُعد دالة colSums() هي الأنسب للاستخدام العملي. فهي تُرجع متجهًا مُسمىً يحتوي على عدد واحد لكل عمود، مما يُظهر على الفور ما إذا كان المتغير يفتقر إلى عدد قليل من القيم أو أنه فارغ في الغالب.

حساب الصفوف الكاملة. تُرجع الدالة complete.cases() القيمة TRUE للصفوف التي لا تحتوي على أي قيمة مفقودة في أي مكان، مما يخبرك مسبقًا بكمية البيانات التي ستتجاهلها الدالة na.omit():

sum(complete.cases(df_titanic))       # rows that would survive
sum(!complete.cases(df_titanic))      # rows that would be dropped

تحذير بشأن العناصر النائبة. لا يتعرف برنامج R إلا على القيم المفقودة (NA). غالبًا ما تُشفّر مجموعات البيانات القيم المفقودة كسلسلة نصية فارغة، أو مسافة، أو "N/A"، أو "-"، أو رقم مميز مثل -99 أو 999. تجتاز هذه القيم جميع الفحوصات المذكورة أعلاه دون أن تُلاحظ. لذا، يُنصح بتحويلها عند الاستيراد لضمان عمل باقي خطوات سير العمل بشكل صحيح.

df <- read.csv(PATH, na.strings = c("", " ", "NA", "N/A", "-", "-99"))

احرص دائمًا على فحص نطاق كل عمود رقمي بعد الاستيراد. إن قيمة عمر -99 أو أجرة 9999 أخطر بكثير من قيمة NA الصحيحة، لأنه لن يتم تنبيهك إليها.

أنواع البيانات المفقودة: MCAR وMAR وMNAR

يحدد سبب غياب قيمة ما ما إذا كان استكمالها آمناً. ويعترف الإحصائيون بثلاث آليات.

  • MCAR، مفقود تمامًا بشكل عشوائي. إن احتمال الفقدان لا يرتبط بأي شيء، سواء كان مرصودًا أم لا، على سبيل المثال مستشعر يتعطل في أوقات عشوائية.ping أو أن إدخال هذه الصفوف لا يُدخل أي تحيز، بل يؤدي فقط إلى فقدان الدقة.
  • مارس، مفقود بشكل عشوائي. يعتمد الاحتمال على المتغيرات الأخرى المرصودة، وليس على القيمة المفقودة نفسها. فإذا كان من غير المرجح تسجيل أعمار الركاب الأكبر سنًا، فإن العمر يُعتبر قيمة مفقودة عشوائيًا (MAR) بالنظر إلى الأعمدة الأخرى. وتُعالج عملية الإسناد التي تستخدم تلك الأعمدة هذه الحالة بكفاءة.
  • MNAR، أي مفقود ليس عشوائياً. يعتمد الاحتمال على القيمة غير المرصودة نفسها، كأن يرفض أصحاب الدخل المرتفع الإفصاح عن دخلهم. لا يمكن لأي طريقة استكمال البيانات معالجة هذا الأمر بالاعتماد على البيانات وحدها، ويحمل النقص في البيانات معلومات قيّمة تستحق التسجيل في خانة خاصة.

لا يُمكن تبرير استخدام طريقة تعويض المتوسط، كما هو مُستخدم في هذا الشرح، إلا في حالات MCAR و MAR البسيطة. وحتى في هذه الحالة، لها ثمن معروف: ملء كل فراغ بنفس الرقم يُقلل من تباين العمود ويُضعف ارتباطه ببقية البيانات. وللعمل الجاد، يُنصح باستخدام طريقة تعتمد على نموذج، مثل حزمة mice، مع الحرص دائمًا على الاحتفاظ بعمود مُعلّم يُشير إلى القيم التي تم تعويضها.

متحور ()

mutate() هو com.dplyr الفعل الذي ينشئ متغيرًا جديدًا أو يستبدل متغيرًا موجودًا، مما يجعله الأداة الطبيعية لإنشاء نسخة نظيفة من عمود.

سنمضي في جزأين. وسوف نتعلم كيفية:

  • استبعاد القيم المفقودة من إطار البيانات
  • حساب القيم المفقودة مع المتوسط ​​والوسيط

إن الفعل mutate() سهل الاستخدام للغاية. يمكننا إنشاء متغير جديد باتباع الصيغة التالية:

mutate(df, name_variable_1 = condition, ...)
arguments:
-df: Data frame used to create a new variable
-name_variable_1: Name and the formula to create the new variable
-...: No limit constraint. Possibility to create more than one variable inside mutate()

استبعاد القيم المفقودة (NA)

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

لمعالجة مشكلة الملاحظات المفقودة، سوف نستخدم مجموعة البيانات العملاقة. في مجموعة البيانات هذه، يمكننا الوصول إلى معلومات الركاب الذين كانوا على متن الطائرة أثناء المأساة. تحتوي مجموعة البيانات هذه على العديد من NA التي تحتاج إلى الاهتمام بها.

قم بتحميل ملف CSV من الإنترنت، ثم قم بإدراج الأعمدة التي تحتوي على NA:

PATH <- "https://raw.githubusercontent.com/guru99-edu/R-Programming/master/test.csv"
df_titanic <- read.csv(PATH, sep = ",")
# Return the column names containing missing observations
list_na <- colnames(df_titanic)[ apply(df_titanic, 2, anyNA) ]
list_na

الإخراج:

## [1] "age"  "fare"

هنا،

colnames(df_titanic)[apply(df_titanic, 2, anyNA)]

تُعيد أسماء الأعمدة التي تحتوي على قيمة مفقودة واحدة على الأقل.

عمر الأعمدة والأجرة لها قيم مفقودة.

يمكننا إسقاطها باستخدام na.omit().

library(dplyr)
# Exclude the missing observations
df_titanic_drop <-df_titanic %>%
na.omit()		
dim(df_titanic_drop)

الإخراج:

## [1] 1045   13

تحتوي مجموعة البيانات الجديدة على 1045 صفًا مقارنة بـ 1309 صفًا مع مجموعة البيانات الأصلية.

استبعاد القيم المفقودة

قم بتعويض البيانات المفقودة باستخدام المتوسط ​​والوسيط

يمكنك أيضًا استكمال القيم المفقودة، أي ملء الفراغات، بالمتوسط ​​أو الوسيط. من الممارسات الجيدة إنشاء متغيرين منفصلين، أحدهما للمتوسط ​​والآخر للوسيط. بعد إنشائهما، يمكننا استبدال القيم المفقودة بالمتغيرين الجديدين.

سوف نستخدم طريقة التطبيق لحساب متوسط ​​العمود مع NA. دعونا نرى مثالا

الخطوة 1) في وقت سابق من البرنامج التعليمي، قمنا بتخزين اسم الأعمدة مع القيم المفقودة في القائمة المسماة list_na. سوف نستخدم هذه القائمة

الخطوة 2) احسب المتوسط ​​باستخدام الوسيط na.rm = TRUE. هذا الوسيط إلزامي لأن الأعمدة تحتوي على بيانات مفقودة، وهذا يُخبر R بتجاهلها.

# Create mean
average_missing <- apply(df_titanic[,colnames(df_titanic) %in% list_na],
      2,
      mean,
      na.rm =  TRUE)
average_missing

Code التفسير:

نقوم بتمرير 4 وسيطات في طريقة التطبيق.

  • df: df_titanic[,colnames(df_titanic) %in% list_na]. سيُرجع هذا الرمز اسم الأعمدة من كائن list_na (أي "العمر" و"الأجرة")
  • 2: حساب الدالة على الأعمدة
  • يعني: حساب المتوسط
  • na.rm = TRUE: تجاهل القيم المفقودة

الإخراج:

##      age     fare 
## 29.88113 33.29548

لقد نجحنا في إنشاء متوسط ​​الأعمدة التي تحتوي على ملاحظات مفقودة. سيتم استخدام هاتين القيمتين لاستبدال الملاحظات المفقودة.

الخطوة 3) استبدل قيم NA

يعد الفعل mutate من مكتبة dplyr مفيدًا في إنشاء متغير جديد. لا نريد بالضرورة تغيير العمود الأصلي حتى نتمكن من إنشاء متغير جديد بدون NA. mutate سهل الاستخدام، كل ما علينا فعله هو اختيار اسم المتغير وتحديد كيفية إنشاء هذا المتغير. هنا هو الرمز الكامل

# Create a new variable with the mean and median
df_titanic_replace <- df_titanic %>%
   mutate(replace_mean_age  = ifelse(is.na(age), average_missing[1], age),
   replace_mean_fare = ifelse(is.na(fare), average_missing[2], fare))

Code التفسير:

نقوم بإنشاء متغيرين،replace_mean_age وreplace_mean_fare كما يلي:

  • استبدال_متوسط_العمر = ifelse(is.na(age), Average_missing[1], age)
  • استبدال_متوسط_السعر = ifelse(is.na(أجرة)، متوسط_مفقود[2]، أجرة)

إذا كان عمر العمود يحتوي على قيم مفقودة، فاستبدله بالعنصر الأول Average_missing (متوسط ​​العمر)، وإلا احتفظ بالقيم الأصلية. نفس المنطق للأجرة

sum(is.na(df_titanic_replace$age))

الإخراج:

## [1] 263

بعد الاستبدال، لا يحتوي العمود الجديد على أي قيم مفقودة على الإطلاق:

sum(is.na(df_titanic_replace$replace_mean_age))

الإخراج:

## [1] 0

يحتوي عمود العمر الأصلي على 263 قيمة مفقودة، بينما قام عمود استبدال متوسط ​​العمر الجديد بملء كل واحدة منها بمتوسط ​​العمر.

الخطوة 4) يمكننا أيضًا التعويض عن الملاحظات المفقودة بالوسيط.

median_missing <- apply(df_titanic[,colnames(df_titanic) %in% list_na],
      2,
      median,
      na.rm =  TRUE)
df_titanic_replace <- df_titanic %>%
            mutate(replace_median_age  = ifelse(is.na(age), median_missing[1], age), 
            replace_median_fare = ifelse(is.na(fare), median_missing[2], fare))
head(df_titanic_replace)

الإخراج:

حساب البيانات المفقودة باستخدام المتوسط ​​والوسيط

الخطوة 5) في مجموعات البيانات الكبيرة، تصبح الطريقة التدريجية مملة. تقوم الدالة sapply() بدمج الإجراء بأكمله في عبارة واحدة، ولكن على حساب عدم رؤية القيم المُدخلة.

sapply لا يقوم بإنشاء ملف إطار البيانات، حتى نتمكن من تغليف الدالة sapply() داخل data.frame() لإنشاء كائن إطار بيانات.

# Quick code to replace missing values with the mean
df_titanic_impute_mean <- data.frame(
    sapply(
        df_titanic,
        function(x) ifelse(is.na(x),
            mean(x, na.rm = TRUE),
            x)))

الإسناد الحديث باستخدام replace_na() و across()

لا تزال طرق apply() و sapply() المذكورة أعلاه تعمل، ولكن tidyr و dplyr تعبران الآن عن نفس العمليات بشكل أكثر أمانًا وأكثر قابلية للقراءة.

استبدال القيم المفقودة (na) بالقيم الثابتة. تأخذ الدالة tidyr::replace_na() قائمة مُسماة من الأعمدة وقيمها البديلة:

library(tidyr)

df_titanic %>%
    replace_na(list(age = 29.88, fare = 33.30))

قم بتطبيق الدالة across() على كل عمود رقمي. هذا هو البديل المباشر والآمن من حيث النوع لسطر واحد من sapply()، وعلى عكس sapply()، فإنه لا يمس أعمدة الأحرف أو العوامل أبدًا:

library(dplyr)

# Mean-impute every numeric column
df_titanic %>%
    mutate(across(where(is.numeric),
                  ~ ifelse(is.na(.x), mean(.x, na.rm = TRUE), .x)))

# Median-impute instead
df_titanic %>%
    mutate(across(where(is.numeric),
                  ~ ifelse(is.na(.x), median(.x, na.rm = TRUE), .x)))

⚠️ لماذا يُعد اختصار sapply() محفوفًا بالمخاطر: يتم تشغيل مثال sapply() المكون من سطر واحد على كل العمود، بما في ذلك الأعمدة النصية والفئوية. استدعاء دالة mean() عليها يُرجع قيمة NA مع تحذير، ثم تقوم دالة sapply() بتحويل النتيجة بأكملها إلى نص. أما الصيغة across(where(is.numeric), …) المذكورة أعلاه فتتجنب هذا الأمر تمامًا.

استخدم الدالة coalesce() لأعمدة الاحتياط. عندما يمكن لعمود ثانٍ أن يوفر القيمة المفقودة، فإن الدالة coalesce() تُرجع أول إدخال غير مفقود عبر وسائطها:

df %>% mutate(age_final = coalesce(age, age_estimated, 0))

احتفظ بسجل لما قمت بتغييره. أضف علامة قبل عملية الإسناد، حتى يتمكن أي نموذج لاحق من التعلم من حقيقة أن قيمة ما كانت مفقودة:

df_titanic %>%
    mutate(age_was_missing = is.na(age),
           age = ifelse(is.na(age), mean(age, na.rm = TRUE), age))

معالجة القيم المفقودة في لغة R: مرجع الأساليب

يتناول هذا الدرس التعليمي ثلاثة مناهج:

  • استبعاد كافة الملاحظات المفقودة
  • إسناد مع المتوسط
  • إسناد مع الوسيط

يلخص الجدول أدناه عملية الكشف والإزالة:

المكتبة الهدف Code
قاعدة قائمة الملاحظات المفقودة
colnames(df)[apply(df, 2, anyNA)]
قاعدة قم بإزالة كل صف يحتوي على قيمة NA
na.omit(df)

يمكن أن يتم التضمين بالمتوسط ​​أو الوسيط بطريقتين

  • باستخدام تطبيق
  • باستخدام سابلي
الأسلوب تفاصيل المزايا عيوب
خطوة بخطوة مع تطبيق التحقق من الأعمدة المفقودة، وحساب المتوسط/الوسيط، وتخزين القيمة، واستبدالها بـ mutate() يمكنك رؤية المتوسط ​​أو الوسيط المحسوب المزيد من وقت التنفيذ. يمكن أن يكون بطيئًا مع مجموعة البيانات الكبيرة
طريقة سريعة مع sapply استخدم sapply() وdata.frame() للبحث تلقائيًا عن القيم المفقودة واستبدالها بالمتوسط/الوسيط رمز قصير وسريع لا يتم عرض القيم المحسوبة مطلقًا

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

يشير الرمز NA إلى قيمة مفقودة داخل متجه. أما الرمز NULL فيمثل غياب عنصر ما، ويكون طوله صفرًا. بينما يمثل NaN نتيجة عملية حسابية غير معرفة، مثل القسمة على صفر.

استخدم الوسيط للمتغيرات الملتوية مثل الأجرة أو الدخل، لأن القيم المتطرفة ترفع المتوسط. استخدم المتوسط ​​فقط عندما يكون العمود متماثلاً تقريباً وخالياً من القيم المتطرفة.

يؤدي ملء كل فراغ بنفس القيمة إلى تقليص تباين العمود وإضعاف ارتباطه بالمتغيرات الأخرى. أما الطرق القائمة على النماذج، مثل حزمة mice، فتحافظ على هذه العلاقات بشكل أفضل بكثير.

لا تقبل معظم الخوارزميات القيم المفقودة (NA)، وستُظهر خطأً أو تحذف الصفوف دون تنبيه. يؤدي الإسناد غير الدقيق إلى تسريب المعلومات بين مجموعتي التدريب والاختبار، لذا احرص دائمًا على حساب قيم الإسناد على مجموعة التدريب فقط.

نعم. يمكن للمساعدين المدعومين بالذكاء الاصطناعي اقتراح طرق بناءً على نمط البيانات المفقودة وكتابة كود dplyr. تحقق من الاختيار بمقارنته بمخرجات دالة colSums(is.na()) الخاصة بك ومعرفتك بمجال البيانات المفقودة.

تلخيص هذه التدوينة بـ: