تنظيف البيانات (Data Cleaning): اكتشاف ومعالجة القيم المفقودة (Missing Values)
مقدمة
تُعد القيم المفقودة من أكثر المشكلات شيوعاً في مشاريع تحليل البيانات وهندسة البيانات الضخمة، لأنها تؤثر مباشرة في دقة الاستنتاجات، موثوقية النماذج، وسلامة القرارات المبنية على البيانات. وفي أي نظام يعتمد على ETL أو منصات Big Data، فإن تجاهل هذه القيم يعني غالباً تسرب أخطاء صامتة إلى التقارير ولوحات المؤشرات وخوارزميات التنبؤ.
فهم القيم المفقودة لا يبدأ من حذف الصفوف فقط، بل من تحليل سبب فقدانها، ونمط توزعها، وتأثيرها على الأعمدة الأخرى. لذلك يُعد هذا الموضوع امتداداً عملياً لما يُناقش عادة في مدخل إلى علوم البيانات: كيف تحول الأرقام العشوائية إلى قرارات استراتيجية؟، لأن جودة القرار تبدأ من جودة البيانات نفسها.
في هذا المقال سنناقش كيف نكتشف القيم المفقودة، وكيف نختار بين الحذف والتعويض، ومتى نستخدم المتوسط أو الوسيط أو النماذج التنبؤية، مع أمثلة عملية باستخدام Pandas وNumPy وSQL وPySpark، بما يناسب البيئات الصغيرة والأنظمة الموزعة واسعة النطاق.
ما المقصود بالقيم المفقودة؟
القيم المفقودة هي بيانات غير متوفرة في موضع كان يفترض أن يحتوي على قيمة صالحة. قد تظهر على شكل NULL في قواعد البيانات، أو NaN في مكتبات التحليل، أو حتى كسلسلة نصية فارغة مثل "" بسبب أخطاء الإدخال أو التحويل.
المشكلة الحقيقية ليست في غياب القيمة فقط، بل في سبب الغياب. فقد تكون البيانات غير متاحة عشوائياً، أو قد يكون غيابها مرتبطاً بسلوك المستخدم، أو بخطأ في واجهة إدخال، أو بانقطاع في نظام التجميع. لهذا السبب، فإن أول خطوة احترافية هي التمييز بين القيمة المفقودة الحقيقية والقيمة غير المسجلة عمداً.
الأنواع الأساسية لفقدان البيانات
- فقدان عشوائي بالكامل: لا يرتبط بأي متغير آخر داخل البيانات.
- فقدان عشوائي مشروط: يرتبط بمتغيرات أخرى معروفة داخل الجدول.
- فقدان غير عشوائي: يحدث بسبب سبب جوهري في الظاهرة نفسها أو سلوك المصدر.
في بيئات
Data Architectureالحديثة، يجب توثيق معنى القيمة المفقودة داخلData Dictionary. أحياناً يكون الحقل الفارغ دلالة أعمال مهمة، مثل “العميل لم يفصح عن دخله”، وليس مجرد خطأ تقني يجب إخفاؤه بالتعويض.
كيف نكتشف القيم المفقودة بدقة؟
قبل المعالجة، نحتاج إلى استكشاف البنية العامة للبيانات. وإذا كنت قد قرأت مقال مكتبة Pandas (2): استكشاف هيكل البيانات وفهم DataFrame و Series فستعرف أن فهم نوع العمود وعدد القيم الفريدة وتوزيعها خطوة أساسية قبل أي تنظيف.
الاكتشاف المهني يشمل فحص القيم المفقودة الصريحة والضمنية. فبعض الأنظمة تخزن القيم المفقودة على هيئة 0 أو unknown أو N/A، وهي لا تُكتشف تلقائياً ما لم يتم توحيدها أثناء القراءة.
اكتشاف القيم المفقودة باستخدام Pandas
يمكنك تحميل البيانات بعد تجهيز البيئة كما في إعداد مختبر البيانات: تثبيت بيئة Jupyter Notebook ومكتبات التحليل الأساسية، ثم فحص الأعمدة الحساسة مباشرة. كما أن الاستفادة من إمكانات مكتبة Pandas (1): قراءة واستدعاء البيانات من ملفات CSV و Excel برمجياً تساعد على تعريف القيم الناقصة منذ لحظة الإدخال.
import pandas as pd
import numpy as np
df = pd.read_csv(
"customers.csv",
na_values=["", "N/A", "unknown", "null"]
)
missing_count = df.isna().sum()
missing_ratio = (df.isna().mean() * 100).round(2)
print(missing_count)
print(missing_ratio)
هذا الفحص يوضح عدد القيم المفقودة ونسبتها في كل عمود. وعندما تتجاوز النسبة حداً معيناً مثل 30% أو 40%، يجب إعادة تقييم فائدة العمود ذاته، خصوصاً إذا كان غير جوهري في التحليل أو النموذج.
اكتشافها باستخدام SQL
في مستودعات البيانات وأنظمة التقارير، يُفضل تنفيذ فحص دوري على مستوى الجداول الحرجة. هذا النهج مفيد داخل خطوط ETL Pipeline قبل ترحيل البيانات إلى طبقات التحليل.
SELECT
COUNT(*) AS total_rows,
SUM(CASE WHEN email IS NULL THEN 1 ELSE 0 END) AS missing_email,
SUM(CASE WHEN age IS NULL THEN 1 ELSE 0 END) AS missing_age,
SUM(CASE WHEN salary IS NULL THEN 1 ELSE 0 END) AS missing_salary
FROM customers;
استراتيجيات معالجة القيم المفقودة
لا توجد طريقة واحدة تصلح لكل الحالات. القرار الصحيح يعتمد على نوع المتغير، نسبة الفقدان، هدف التحليل، وحجم البيانات. في مشاريع Machine Learning مثلاً، قد يؤدي التعويض غير المدروس إلى انحياز شديد في النموذج.
1) حذف الصفوف أو الأعمدة
يُستخدم الحذف عندما تكون نسبة القيم المفقودة منخفضة، أو عندما يكون العمود عديم القيمة التحليلية. لكنه يصبح خطيراً إذا أدى إلى تقليص العينة أو إلغاء شريحة كاملة من السلوك الحقيقي للمستخدمين.
df_rows_dropped = df.dropna(subset=["age", "salary"])
df_cols_dropped = df.dropna(axis=1, thresh=len(df) * 0.7)
2) التعويض الإحصائي
هذه الطريقة مناسبة عندما تكون البيانات العددية مستقرة نسبياً. عادة يُستخدم المتوسط مع التوزيعات المتقاربة، بينما يكون الوسيط أفضل عند وجود قيم شاذة. أما الأعمدة الفئوية فيمكن تعويضها بالمنوال أو بقيمة تصنيفية مثل "Unknown".
df["age"] = df["age"].fillna(df["age"].median())
df["salary"] = df["salary"].fillna(df["salary"].mean())
df["city"] = df["city"].fillna("Unknown")
عند التعامل مع المصفوفات والتحويلات العددية، تظهر أهمية مكتبة NumPy: القوة الضاربة في معالجة المصفوفات والعمليات الرياضية المعقدة، خاصة في تسريع العمليات المتجهية ومعالجة البيانات واسعة الحجم داخل الذاكرة.
3) التعويض المعتمد على السياق
أحياناً يكون من الأفضل التعويض بناءً على مجموعة فرعية لها خصائص مشابهة. على سبيل المثال، تعويض الراتب بمتوسط الرواتب داخل نفس المدينة أو نفس الفئة الوظيفية يعطي منطقاً أفضل من متوسط عام لكل المؤسسة.
df["salary"] = df.groupby("department")["salary"] \
.transform(lambda column: column.fillna(column.median()))
4) التعويض في البيانات الزمنية
في السلاسل الزمنية، يمكن استخدام أساليب مثل forward fill أو interpolation عندما يكون تسلسل القيم مهماً. هذه الطرق شائعة في بيانات الحساسات، التداول، وسجلات الاستهلاك.
df = df.sort_values("timestamp")
df["temperature"] = df["temperature"].interpolate(method="linear")
df["status"] = df["status"].fillna(method="ffill")
معالجة القيم المفقودة في البيئات الضخمة باستخدام PySpark
عندما تتجاوز البيانات قدرة المعالجة المحلية، ننتقل إلى أطر موزعة مثل Apache Spark. في هذه الحالة، لا يكفي التفكير في منطق التعويض فقط، بل يجب مراعاة كلفة النقل بين العقد، وإعادة التقسيم، وعدد العمليات المتكررة على DataFrame.
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, when, avg
spark = SparkSession.builder.appName("MissingValues").getOrCreate()
df = spark.read.option("header", True).csv("customers.csv", inferSchema=True)
df = df.withColumn(
"city",
when(col("city").isNull(), "Unknown").otherwise(col("city"))
)
mean_salary = df.select(avg(col("salary"))).collect()[0][0]
df = df.fillna({"salary": mean_salary})
في مشاريع
Sparkواسعة النطاق، يفضَّل تقليل عملياتcollect()لأنها تنقل النتائج إلى السائقDriver. الأفضل غالباً هو تنفيذ المعالجة بشكل موزع قدر الإمكان، خاصة مع الجداول التي تضم عشرات الملايين من الصفوف.
كيف نختار الطريقة المناسبة؟
يمكن اتخاذ القرار بشكل منهجي عبر مجموعة من الأسئلة العملية:
- ما نسبة القيم المفقودة في العمود أو الجدول؟
- هل العمود حرج للأعمال أو للنموذج الإحصائي؟
- هل فقدان البيانات عشوائي أم له نمط واضح؟
- هل التعويض سيشوّه التوزيع الأصلي للمتغير؟
- هل البيئة تشغيلية صغيرة أم موزعة على
Cluster؟
في كثير من الحالات، يكون الحل الأفضل هو الدمج بين أكثر من أسلوب: حذف محدود، وتعويض إحصائي لبعض الأعمدة، وإنشاء عمود إضافي يشير إلى أن القيمة كانت مفقودة أصلاً. هذا الأسلوب يحافظ على الإشارة التحليلية ويمنع إخفاء المشكلة بالكامل.
حالة استخدام واقعية في خط ETL
لنفترض وجود نظام مبيعات يجمع البيانات من فروع متعددة. بعض الفروع ترسل حقل المدينة بشكل ناقص، بينما ترسل فروع أخرى الدخل الشهري بصيغة مختلفة أو ناقصة. داخل خط ETL الاحترافي، تتم المعالجة عادة وفق الخطوات التالية:
- توحيد تمثيل القيم المفقودة أثناء الاستخراج.
- قياس نسبة الفقدان لكل عمود قبل التحويل.
- تطبيق قواعد تعويض مختلفة حسب نوع الحقل.
- تسجيل الأعمدة المتأثرة في سجلات جودة البيانات.
- إرسال تنبيه إذا تجاوزت النسبة الحد المتفق عليه.
أفضل الممارسات في هندسة البيانات لا تعتبر تنظيف القيم المفقودة خطوة تجميلية، بل طبقة حوكمة أساسية ضمن
Data Quality Framework. كل قاعدة تعويض يجب أن تكون قابلة للتتبع، وقابلة للقياس، ومفهومة من فرق التحليل والأعمال معاً.
خاتمة
تنظيف البيانات ومعالجة القيم المفقودة ليسا إجراءين ثانويين، بل حجر الأساس في أي تحليل موثوق أو بنية بيانات قابلة للتوسع. فكل قيمة مفقودة قد تحمل خطأ تقنياً، أو إشارة سلوكية، أو مشكلة في تصميم النظام، ولذلك يجب التعامل معها بوعي تحليلي لا برد فعل آلي.
كلما ارتفع نضجك في فهم هيكل البيانات، واستخدام أدوات مثل Pandas وSQL وPySpark، أصبحت قراراتك في التعويض والحذف أكثر دقة وأقرب إلى الواقع. والنتيجة النهائية ليست فقط بيانات أنظف، بل نماذج أفضل، وتقارير أصدق، وقرارات أعمال أكثر موثوقية.