PySpark: كيفية استخدام fillna() مع أعمدة محددة

دليل أكاديمي شامل يشرح كيفية استخدام دالة fillna() في PySpark لمعالجة القيم المفقودة وتخصيص التعويض في أعمدة معينة بدقة وكفاءة حسابية عالية.

Mohammed looti أكاديمي وباحث متخصص في علم النفس
تاريخ النشر
تمت المراجعة العلمية · د. مروة عبد العظيم · 24 أكتوبر، 2026
مراجعة وتدقيق علمي معتمد تاريخ التدقيق: 24 أكتوبر، 2026
د. مروة عبد العظيم دكتوراه في علم النفس
عضو هيئة التدريس • جامعة كربلاء
معايير التدقيق والاعتماد السريري

يخضع هذا المحتوى لمعايير ضبط الجودة والتدقيق العلمي والأكاديمي الصارمة في شبكة علم النفس العربي، لضمان صحة المعلومات ودقتها السريرية ومطابقتها لأحدث الأدلة والبراهين الصادرة عن الجمعيات النفسية والطبية المعتمدة (APA / WHO).

تُمثّل إدارة البيانات المفقودة (Missing Data Management) إحدى أكثر المعضلات تعقيداً وإلحاحاً في هندسة البيانات الحديثة والتحليلات المتقدمة، لا سيما عند الانتقال من بيئات الحوسبة الأحادية إلى بيئات الحوسبة الموزعة ذات المقاييس الفائقة (Hyperscale Distributed Environments). ومع تزايد الاعتماد على محرك Apache Spark بوصفه المعيار الفعلي لمعالجة البيانات الضخمة (Big Data Processing)، أضحى توظيف واجهته البرمجية للغة بايثون، المعروفة بـ PySpark، ركيزة أساسية لبناء خطوط معالجة مستقرة وقابلة للتوسع. تبرز دالة fillna() كأداة حاسمة في ترسانة مهندس البيانات لمعالجة الفجوات الناتجة عن أخطاء الجمع أو انقطاع قنوات الاتصال، إلا أن تطبيقها الساذج على كامل إطار البيانات دون تخصيص غالباً ما يقود إلى تشوهات إحصائية وانحرافات دلالية لا تُحمد عقباها.

إن المعالجة الرشيدة للبيانات الخالية (Null Values) تتطلب فهماً عميقاً لطبقات التنفيذ التحتية للمحرك، وآليات التقييم الكسول (Lazy Evaluation)، ومخططات التنفيذ الفيزيائية والمنطقية التي يولدها محرك التحسين التحفيزي Catalyst Optimizer. فالهدف ليس مجرد ملء الخلايا الفارغة بقيم اعتباطية للتخلص من الأخطاء البرمجية أثناء تغذية خوارزميات تعلم الآلة، بل توجيه عملية التعويض (Imputation) بدقة جراحية نحو أعمدة بعينها، مع مراعاة الطبيعة الإحصائية لكل متغير، وأنماط التوزيع، ونوع البيانات المجرد، والتفاعل التبادلي بين المتغيرات داخل النموذج الإحصائي الشامل.

يتناول هذا الدليل الأكاديمي الشامل التشريح الدقيق لكيفية استخدام دالة fillna() مع أعمدة محددة في PySpark. سنستعرض الأسس النظرية والرياضية لمعالجة الفقدان، والبنية البرمجية التحتية للدالة عبر واجهة DataFrameNaFunctions، والتطبيقات الإجرائية لحالات الأعمدة المفردة والمتعددة، والتعويض المتقدم عبر القواميس البرمجية، وصولاً إلى قياس الأثر الحسابي على إدارة الذاكرة وتفادي عمليات خلط البيانات المكلفة (Data Shuffling). يقدم هذا المقال مرجعاً تطبيقياً مدعوماً بالرموز البرمجية والممارسات المعيارية الموجهة لمهندسي وعقلاء البيانات العاملين على المنظومات الإنتاجية المعقدة.

1. مقدمة نظرية حول معالجة القيم المفقودة في أطر البيانات الضخمة عبر PySpark

1.1 إشكالية البيانات المفقودة في بيئات الحوسبة الموزعة

يُعد وجود القيم غير المعرفة (Null أو None في بايثون) عائقاً بنيوياً يهدد متانة النماذج التحليلية والحسابية. فمن منظور إحصائي رياضي، يؤدي التعامل الخاطئ مع الفقدان إلى انحياز منهجي (Systematic Bias) في تقدير مقاييس النزعة المركزية مثل المتوسط الحسابي والوسيط، بالإضافة إلى تشويه مقاييس التشتت كالانحراف المعياري والتباين. تتضاعف هذه المشكلة خطورة في بيئات الحوسبة السحابية والموزعة، حيث تتوزع أطر البيانات على هيئة أقسام تجزئة (Partitions) عبر عشرات أو مئات العقد الحاسوبية (Worker Nodes). في هذه البيئات، قد يتسبب وجود القيم الفارغة في فشل العمليات الرياضية التجميعية (Aggregation Operations) أو فرض عمليات نقل بيانات غير ضرورية بين العقد، ما يولد اختناقات حادة في شبكة الاتصال الداخلي للعنقود (Cluster Interconnect).

من الناحية المنهجية، يرتكز تأطير الفقدان على التصنيف المعياري الذي صاغه الإحصائي دونالد روبين (Donald B. Rubin عام 1976)، والذي يميز بدقة بين ثلاثة آليات رئيسية لفقدان البيانات: الفقدان التام العشوائي (Missing Completely at Random – MCAR)، والفقدان العشوائي (Missing at Random – MAR)، والفقدان غير العشوائي (Missing Not at Random – MNAR). في حالة MCAR، لا ترتبط احتمالية فقدان القيمة بالمتغير نفسه أو بأي متغيرات أخرى مرصودة، مما يتيح هامشاً أوسع للتعويض دون المساس بنزاهة التوزيع. أما في حالتي MAR و MNAR، فإن الفقدان يرتبط بنيوياً بخصائص الظاهرة المدروسة، مما يجعل التعويض الشامل غير الواعي بمثابة تزوير للحقائق الإحصائية وتوليد لبيانات زائفة تضلل خوارزميات الاستدلال الإحصائي والتعلم الآلي في المراحل اللاحقة.

1.2 دور دالة fillna() في إطار العمل Apache Spark

تحتل دالة fillna() موقعاً محورياً ضمن منظومة معالجة البيانات في Apache Spark، حيث تندرج هيكلياً ضمن الواجهة البرمجية المتخصصة DataFrameNaFunctions. تُدار هذه الواجهة عبر كائن مساعد يتيح الوصول إلى مجموعة من الدوال المصممة خصيصاً للتعامل مع البيانات الناقصة مثل drop() و replace() و fill(). تتميز هذه العمليات بانضباطها الصارم لمبدأ الثبات المعماري (Immutability) الذي يحكم أطر البيانات في Spark؛ إذ إن استدعاء دالة fillna() لا يقوم بأي تعديل موضعي (In-place Modification) على بنية الإطار الأصلي المقيم في الذاكرة، بل يُنتج إطار بيانات جديداً تماماً (New DataFrame) يُسجل التحويل المطلوب ضمن مخطط المسار غير الدوري الموجه (Directed Acyclic Graph – DAG).

يفتح هذا التمييز الباب أمام استيعاب الفارق الجوهري بين التعويض الشامل (Global Imputation) والتعويض الموجه (Targeted Imputation). فالتعويض الشامل، الذي يستبدل كافة القيم المفقودة في جميع أعمدة الإطار بقيمة قياسية واحدة (كاستبدال كل فراغ بصفر)، يتجاهل التباين النوعي بين الأعمدة ويخرق اتساق البيانات. في المقابل، يُمكّن التعويض الموجه المهندس من تحديد سلوك المعالجة لكل عمود أو مجموعة أعمدة على حدة، مما يدمج عملية التطهير ضمن دورة حياة التحويلات الكسولة التي يشرف عليها Spark، ويضمن الحفاظ على منطق العمل وأمن العمليات الحسابية دون إهدار الموارد الحاسوبية.

1.3 مبررات حصر التعويض في أعمدة محددة بدلاً من الإطار بأكمله

إن المبرر الأول والأساسي لتقييد التعويض بأعمدة محددة يكمن في تجنب “الإفساد الدلالي” (Semantic Corruption) للبيانات. فعلى سبيل المثال، إذا كان إطار البيانات يحتوي على متغيرات رقمية تمثل “عدد مرات الشراء”، وأعمدة نصية تمثل “المدينة”، وأعمدة منطقية تمثل “حالة الاشتراك”، فإن تمرير قيمة عددية مثل الصفر للتعويض الشامل سيؤدي حتماً إما إلى أخطاء تحويل برمجية أو إلى تشويه معنى الحقول النصية إذا تم تحويلها قسراً. حتى ضمن النطاق العددي الخالص، فإن استبدال القيم الفارغة في عمود يمثل “عمر العميل” بالصفر يختلف جذرياً عن استبدال القيم الفارغة في عمود يمثل “الرصيد المالي الإضافي”، فالأول ينشئ قيماً غير منطقية تشوه التحليل، بينما الثاني قد يمثل حقيقة واقعة تعبر عن انعدام الرصيد.

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

2. البنية المعمارية لدالة fillna() والمعاملات البرمجية الأساسية

2.1 تحليل توقيع الدالة والمعاملات المدخلة

تتمتع دالة fillna() في PySpark بتوقيع برمجي مرن ودقيق صُمم ليستوعب احتياجات التطهير المتنوعة. يظهر التوقيع البرمجي للدالة في التوثيق الرسمي على النحو التالي:

DataFrame.fillna(value, subset=None)

يتألف التوقيع من معاملين جوهريين؛ المعامل الأول هو value، وهو يمثل القيمة البديلة المراد إحلالها محل القيم الفارغة. يُشترط في هذا المعامل أن يكون قيمة قياسية بدائية (Scalar Value) كالأعداد الصحيحة (int)، أو الأعداد العشرية (float)، أو السلاسل النصية (string)، أو القيم المنطقية (bool)، أو أن يأتي في صيغة قاموس برمجي (dict) يربط أسماء الأعمدة بقيمها الخاصة. يحدد نوع هذه القيمة نطاق التطبيق المتوافق مع مخطط الجدول (Schema).

أما المعامل الثاني فهو subset، وهو الأداة المحورية لحصر التعويض وتوجيهه نحو نطاق جزئي محدد من الأعمدة. يقبل هذا المعامل مدخلين بنيويين أساسيين: إما سلسلة نصية مفردة (String) تمثل اسم عمود وحيد، أو قائمة من السلاسل النصية (List of Strings) تحتوي على مصفوفة من أسماء الأعمدة المراد استهدافها بالتعديل. إذا تُرك المعامل subset بقيمته الافتراضية None وكان المعامل value قيمة قياسية، فإن محرك Spark سيعمل تلقائياً على تطبيق التعويض على كافة الأعمدة التي يتطابق نوع بياناتها مع نوع القيمة المدخلة، متجاهلاً الأعمدة الأخرى دون توقف مفاجئ للبرنامج.

2.2 العلاقة التكافؤية بين fillna() و na.fill()

يطرح مطورو PySpark تساؤلاً متكرراً حول الفارق بين استدعاء df.fillna() واستدعاء df.na.fill(). بالرجوع إلى الشفرة المصدرية (Source Code) لمشروع Apache Spark مفتوح المصدر، نجد أن الصياغتين متطابقتان وظيفياً وتنفيذياً بنسبة مئة بالمئة. فدالة fillna() على مستوى DataFrame ليست سوى غلاف برمجي بديل ووسيلة تسهيل نحوية (Syntactic Sugar) تقوم داخلياً باستدعاء مباشر للكائن DataFrameNaFunctions عبر التعبير self.na.fill(value, subset)، دون أي تغيير في خطة التنفيذ أو استهلاك الموارد.

تخضع المفاضلة بين الصياغتين للمعايير المنهجية المعتمدة لدى فرق هندسة البرمجيات واتساق أسلوب كتابة الشيفرة (Code Consistency). يفضل قطاع عريض من المطورين استخدام df.fillna() نظراً لتطابقها البصري والنحوي مع مكتبة Pandas الشهيرة، مما يسهل انتقال المبرمجين بين بيئات التحليل الأحادي والموزع ويزيد من مقروئية الكود. في المقابل، يفضل المعماريون الملتزمون بنمط التصميم الكينوني التجريدي استخدام df.na.fill() لأنها تفرز بوضوح الدوال المتخصصة في التعامل مع القيم الخالية تحت نطاق محدد (Namespace)، مما يمنح الشفرة وضوحاً مفاهيمياً يبرز حدود المسؤولية البرمجية لكل مكون، لا سيما في المشروعات الضخمة التي يعمل عليها فرق متعددة التخصصات.

2.3 قواعد التوافق النمطي بين القيمة المدخلة ونوع بيانات العمود

يفرض Spark نظاماً صارماً للأنماط (Strongly Typed Schema) لحماية سلامة البيانات المحوسبة عبر الذاكرة التخزينية. وبناءً على ذلك، تحكم دالة fillna() قواعد صارمة للتوافق النمطي بين نوع القيمة التعويضية value ونوع البيانات المسند إلى العمود المستهدف في المخطط العام. إذا كانت القيمة المدخلة عبارة عن عدد صحيح (Integer)، فإن التعويض لن يسري إلا على الأعمدة المصنفة ضمن عائلة الأعداد الصحيحة (مثل IntegerType و LongType)، مع إمكانية الترقية التلقائية إلى DoubleType في بعض السياقات التحويلية، ولكنها لن تُطبق بأي حال من الأحوال على الأعمدة النصية (StringType).

من الأهمية بمكان إدراك السلوك الصامت (Silent Behavior) لمحرك Spark التحليلي عند حدوث عدم توافق؛ فعند تمرير قيمة قياسية عبر value مع تحديد عمود ضمن subset يختلف نوع بياناته جذرياً عن نوع القيمة (كتمرير نص لعمود رقمي أو العكس)، فإن المحرك لا يطلق استثناءً تحذيرياً ولا يوقف المعالجة، بل يتجاوز هذا العمود بصمت ويبقي على قيمه المفقودة كما هي دون تعديل. يهدف هذا التصميم المعماري إلى منع تعطل مهام المعالجة الدُفعية (Batch Jobs) التي تستغرق ساعات طويلة بسبب تفاوتات طفيفة في المخططات، إلا أنه يفرض في الوقت ذاته التزاماً صارماً على مهندس البيانات بضرورة تدقيق مخرجات التعويض والتأكد من مطابقة الأنواع لتفادي استمرار وجود الفراغات غير المكتشفة.

3. إعداد بيئة العمل وبناء إطار البيانات النموذجي للتطبيق

3.1 تهيئة جلسة SparkSession وضبط الخصائص

تبدأ كل خطوة تطبيقية في بيئة PySpark الحديثة بتهيئة جلسة العمل المركزية SparkSession، والتي تعمل كنقطة دخول موحدة لإدارة العمليات الحسابية، وتكوين الخصائص، وتنسيق تدفق البيانات عبر العقد. يتم استيراد الفئات الأساسية من مكتبة pyspark.sql لإنشاء بيئة معزولة تضمن تكرار النتائج بدقة عبر مختلف بيئات التطوير والاختبار، مع ضبط مخصص لمعلمات الذاكرة لضمان الاستقرار الحسابي أثناء عمليات المعالجة التفاعلية.

يتم بناء الجلسة محلياً عبر الشيفرة البرمجية القياسية التالية:

from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StructField, StringType, IntegerType, FloatType

# بناء جلسة العمل مع تحديد الموارد الأساسية
spark = SparkSession.builder 
    .appName("PySpark-Fillna-Advanced-Guide") 
    .master("local[*]") 
    .config("spark.driver.memory", "2g") 
    .config("spark.sql.shuffle.partitions", "4") 
    .getOrCreate()

# ضبط مستوى التسجيل لتقليل الضوضاء التشغيلية
spark.sparkContext.setLogLevel("ERROR")

يضمن ضبط معامل master("local[*]") استغلال كافة الأنوية المنطقية المتاحة على جهاز التطوير لتوسيع نطاق التوازي، في حين يسهم خفض عدد أقسام الخلط عبر spark.sql.shuffle.partitions في تقليص العبء الإداري لمجموعات البيانات الصغيرة والتجريبية، مما يوفر بيئة مثالية لرصد سلوك دوال التطهير وتتبع أثرها المباشر على البيانات.

3.2 هيكلة البيانات الأولية وتضمين القيم المفقودة

لبناء سيناريو تطبيقي واقعي يحاكي المشكلات الميدانية في خطوط معالجة البيانات، سنقوم بإنشاء مصفوفة بيانات تجريبية مستوحاة من الإحصاءات الرياضية للاعبي كرة السلة. تشتمل هذه البيانات على قيم مفقودة موزعة عمداً عبر مختلف الأعمدة، مع تنويع مقصود بين المتغيرات الفئوية (Categorical) والمتغيرات العددية (Numerical)، مما يتيح اختبار آليات الاستهداف الدقيق لدالة fillna().

يتم تعريف البيانات وبناء المخطط الهيكلي وفق الشيفرة التالية:

# تعريف مصفوفة البيانات الخام محتوية على قيم None
data_sample = [
    ("Lakers", "Western", 112, 24.0),
    ("Celtics", "Eastern", None, 28.5),
    ("Warriors", "Western", 105, None),
    ("Heat", None, 98, 20.0),
    ("Bulls", "Eastern", None, None),
    ("Nets", None, 101, 22.0),
    ("Suns", "Western", 115, 27.0)
]

# تعريف مخطط البيانات الصارم لضمان مطابقة الأنواع
schema = StructType([
    StructField("team", StringType(), False),
    StructField("conference", StringType(), True),
    StructField("points", IntegerType(), True),
    StructField("assists", FloatType(), True)
])

# إنشاء إطار البيانات التنفيذي
df_basketball = spark.createDataFrame(data=data_sample, schema=schema)

تتضمن هذه الهيكلية أربعة أعمدة متمايزة: عمود team كمتغير نصي لا يقبل الفراغ، وعمود conference كمتغير فئوي نصي يحتوي على قيم فارغة، بالإضافة إلى عمودي points و assists اللذين يمثلان قياسات عددية صحيحة وعشرية على التوالي، وتتخللهما قيم غير معرفة تحاكي الفقدان الحاصل أثناء توثيق المباريات.

3.3 فحص البيانات عبر العمليات الإجرائية المبدئية

قبل إجراء أي تدخل تحويلي على البيانات، يقضي المنهج الهندسي الصارم بفحص الحالة المرجعية (Baseline State) للتحقق من المخطط المستنتج ورصد الإحداثيات النسبية للقيم الفارغة. تُعد دوال show() و printSchema() الأدوات التشخيصية الأولى لتقييم صحة التراكيب البيانية في PySpark.

# معاينة البيانات الأولية
print("--- معاينة إطار البيانات الأولي ---")
df_basketball.show()

# تدقيق أنواع البيانات والخصائص البنيوية للمخطط
print("--- فحص مخطط البيانات (Schema) ---")
df_basketball.printSchema()

يُظهر تنفيذ الشيفرة السابقة جدولاً يوضح مواقع القيم المفقودة؛ حيث نلاحظ أن الصفين الثاني والخامس يفتقران إلى بيانات النقاط (points)، بينما يفتقر الصفان الثالث والخامس إلى بيانات المساعدات (assists)، ويحتوي الصفان الرابع والسادس على قيم فارغة في عمود المؤتمر (conference). يعكس استدعاء printSchema() صراحة قابلية كل حقل لاحتواء الفراغ (nullable = true)، مما يؤكد الحاجة إلى تطبيق معالجة انتقائية تضمن عدم اختلاط الفئات الدلالية أثناء التعويض.

4. تطبيق fillna() على عمود مفرد محدد: القواعد والأمثلة الإجرائية

4.1 صياغة الأمر باستخدام اسم العمود كقيمة نصية مفردة

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

يتم تنفيذ ذلك برمجياً بتمرير اسم العمود كسلسلة نصية إلى المعامل subset:

# تطبيق التعويض بقيمة صفرية على عمود النقاط فقط
df_filled_points = df_basketball.fillna(value=0, subset="points")

print("--- نتائج تعويض عمود النقاط منفرداً ---")
df_filled_points.show()

عند فحص الجدول الناتج، يتبين أن محرك PySpark قام بمسح عمود points واستبدال كل قيمة null بالرقم 0 (كما في حالتي Celtics و Bulls)، في حين ظلت خلايا null في عمودي conference و assists على حالتها الأصلية تماماً. يبرهن هذا السلوك على دقة التخصيص التي يوفرها وسيط subset مقارنة بالتعويض العام.

4.2 التحقق المنهجي من مناعة الأعمدة المجاورة للتعديل

يتطلب العمل في خطوط البيانات الحساسة إثباتاً إحصائياً وبرمجياً على عدم حدوث أي تسرب للآثار الجانبية (Side Effects) إلى الأعمدة غير المستهدفة. لا يكفي الاكتفاء بالمعاينة البصرية عبر show()، بل ينبغي إجراء فحص استعلامي منهجي يقيس استقرار مؤشرات الفقدان في سائر المتغيرات للتأكد من مناعتها ضد التعديل غير المقصود.

from pyspark.sql.functions import col, count, when

# دالة لحساب عدد القيم المفقودة في كل عمود
def count_nulls(df):
    return df.select([
        count(when(col(c).isNull(), c)).alias(c) 
        for c in df.columns
    ])

print("--- فحص القيم المفقودة قبل التعويض ---")
count_nulls(df_basketball).show()

print("--- فحص القيم المفقودة بعد تعويض عمود points ---")
count_nulls(df_filled_points).show()

يكشف جدول الإحصاء بعد التعويض أن عدد القيم المفقودة في عمود points انخفض من 2 إلى 0، في حين حافظ عمود conference على رصيده البالغ 2 من القيم الفارغة، وبقي عمود assists عند مستواه السابق بقيمتين مفقودتين. يثبت هذا التحليل الرياضي الصريح أن التعديل قد عُزل عزلاً تاماً داخل مسار الحوسبة المخصص للعمود المختار، دون المساس بتراكيب الذاكرة التابعة للأعمدة المجاورة.

4.3 البدائل التعبيرية لتحديد العمود المفرد

يوفر Spark مرونة نحوية تتيح للمهندس صياغة وسيط التحديد بطريقتين متكافئتين وظيفياً عند استهداف عمود واحد: تمرير اسم العمود كسلسلة نصية مباشرة subset="points"، أو تمريره كعنصر وحيد داخل مصفوفة قائمة subset=["points"]. تظهر هذه المرونة أيضاً عند استدعاء الدالة عبر الواجهة المباشرة أو عبر الواجهة الفرعية na:

# الصياغة التعبيرية الأولى: سلسلة نصية مباشرة
df_alt1 = df_basketball.fillna(0, subset="points")

# الصياغة التعبيرية الثانية: قائمة أحادية العنصر
df_alt2 = df_basketball.fillna(0, subset=["points"])

# الصياغة التعبيرية الثالثة: عبر واجهة na
df_alt3 = df_basketball.na.fill(0, subset=["points"])

على الرغم من تطابق المخرجات التنفيذية لهذه البدائل، إلا أن الممارسات البرمجية المستدامة توصي باعتماد صيغة القائمة subset=["points"] كمعيار قياسي في الشيفرات الإنتاجية. تكمن الميزة هنا في سهولة الصيانة والتطوير المستقبلي؛ إذ يمكن للمطورين لاحقاً إضافة أعمدة جديدة إلى القائمة ببساطة دون الحاجة إلى إعادة صياغة التعبير النمطي، مما يقلل احتمالات الخطأ عند توسيع وتعديل قواعد معالجة البيانات.

5. تطبيق fillna() على أعمدة متعددة محددة بالتوازي

5.1 بناء قوائم الاستهداف للأعمدة المتجانسة

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

يتيح المعامل subset استيعاب هذه المتطلبات المتزامنة من خلال تمرير قائمة تضم مصفوفة الأعمدة المستهدفة:

# تعريف قائمة الأعمدة العددية المراد تعويضها
numeric_targets = ["points", "assists"]

# تنفيذ التعويض المتزامن بقيمة صفرية
df_filled_multi = df_basketball.fillna(value=0.0, subset=numeric_targets)

print("--- نتائج التعويض المتوازي للأعمدة المتجانسة ---")
df_filled_multi.show()

من الناحية المعمارية، يتفوق هذا الاستدعاء الموحد تفوقاً ساحقاً على استدعاء الدالة بشكل متسلسل لكل عمود على حدة (مثل df.fillna(0, "points").fillna(0, "assists")). فالاستدعاء الموحد يُدمج التحويلين في خطوة منطقية واحدة (Single Project Operation) ضمن شجرة التعبير الحسابي لمحرّك Spark، مما يقلل عمق شجرة التحويل ويخفض التعقيد الحسابي المترتب على تتبع العمليات المتتالية.

5.2 مقارنة الحالات قبل وبعد المعالجة المتعددة

لتأكيد فاعلية المعالجة المتوازية للأعمدة المتعددة، نقوم بإجراء فحص تدقيقي يعقد مقارنة قطعية بين نسب الاكتمال قبل التدخل الإجرائي وبعده، وذلك لضمان تحقق الهدف التحويلي الشامل بدقة متناهية:

print("--- التقييم الإحصائي قبل المعالجة المتعددة ---")
df_basketball.select([
    count(when(col(c).isNull(), c)).alias(f"missing_{c}") 
    for c in numeric_targets
]).show()

print("--- التقييم الإحصائي بعد المعالجة المتعددة ---")
df_filled_multi.select([
    count(when(col(c).isNull(), c)).alias(f"missing_{c}") 
    for c in numeric_targets
]).show()

تثبت المخرجات الإحصائية تلاشي القيم الفارغة في كل من عمودي points و assists دفعة واحدة، حيث تراجعت مؤشرات الفقدان فيهما إلى الصفر التام. في الوقت نفسه، لو قمنا بفحص عمود conference لوجدناه لا يزال محتفظاً بالقيمتين الفارغتين دون أدنى مساس، ما يؤكد نجاح المعالجة في حصر التعديل بدقة متناهية داخل الحدود المحددة في مصفوفة الاستهداف.

5.3 إدارة التوسيع المستقبلي لقائمة الأعمدة المختارة

في بيئات الإنتاج الفعلية، نادراً ما يتعامل مهندس البيانات مع جداول تحتوي على أربعة أو خمسة أعمدة فحسب؛ بل تمتد الجداول التحليلية لتشمل مئات المتغيرات المستخرجة من مستودعات البيانات الكبرى (Data Warehouses). في مثل هذه الظروف، يصبح الإدراج اليدوي لأسماء الأعمدة ممارسة غير عملية وعرضة للأخطاء البشرية القاتلة. يبرز هنا الانتقاء الديناميكي البرمجي استناداً إلى أنواع البيانات التعريفية (Data Types Profiling) كحل احترافي عالي المرونة.

يمكن بناء تصفية استباقية تستخلص كافة الأعمدة الرقمية تلقائياً وتمررها للدالة:

# استخراج أسماء الأعمدة الرقمية ديناميكياً استناداً إلى خصائص dtypes
numeric_cols_dynamic = [
    field_name for field_name, field_type in df_basketball.dtypes 
    if field_type in ("int", "double", "float")
]

print(f"الأعمدة الرقمية المستخرجة تلقائياً: {numeric_cols_dynamic}")

# تمرير القائمة الديناميكية لوسيط subset
df_dynamic_imputed = df_basketball.fillna(0, subset=numeric_cols_dynamic)
df_dynamic_imputed.show()

يمنح هذا الأسلوب خطوط المعالجة مناعة كاملة ضد تغيرات المخطط المستقبلية (Schema Evolution)؛ فإذا تمت إضافة أعمدة رقمية جديدة إلى مصدر البيانات لاحقاً، فإن الكود سيتعرف عليها ويلحقها بعملية التعويض التلقائي دون الحاجة إلى أي تدخل يدوي لإعادة كتابة الأوامر، مما يرفع من قابلية التوسع والصيانة للمنظومة ككل.

6. التعامل المتقدم مع تنوع أنواع البيانات في الأعمدة المحددة

6.1 استبدال القيم المفقودة في الأعمدة الفئوية والنصية

تحظى المتغيرات الفئوية (Categorical Variables) بأهمية بالغة في التحليلات الاستكشافية وخوارزميات التصنيف؛ إلا أن معالجة فقدانها تتبع قواعد مفاهيمية مختلفة تماماً عن المتغيرات العددية. ففي الحقول النصية، لا معنى للتعويض بالصفر، بل يتمثل الإجراء النموذجي في إحلال فئة بديلة صريحة ترمز إلى انعدام المعلومة مثل “غير محدد” (Unknown) أو “غير متوفر” (Not Available)، وذلك لتحويل القيمة المفقودة إلى تصنيف قابل للقياس والمقارنة دون إفساد المخطط العام.

يتم تطبيق هذا التعويض الفئوي على عمود المؤتمر conference باستهداف نصي صريح:

# تعويض القيم المفقودة في العمود النصي بفئة دلالية واضحة
df_filled_cat = df_basketball.fillna(value="غير محدد", subset=["conference"])

print("--- تعويض المتغيرات الفئوية النصية ---")
df_filled_cat.show()

يجب التمييز هنا بدقة بين القيمة الخالية الحقيقية (null) والسلسلة النصية الفارغة (Empty String الممثلة بـ ""). فدالة fillna() مخصصة حصرياً لمعالجة مؤشرات الفقدان null والرمز الحسابي NaN في الأعداد العشرية، ولكنها تتجاهل تماماً النصوص الفارغة التي تحتوي على فراغات بيضاء أو سلاسل بطول صفر؛ إذ تُعامل هذه النصوص كبيانات موجودة فعلياً من منظور محرك قاعدة البيانات، وتتطلب دوال استبدال نصي مخصصة إذا رغب المهندس في معالجتها تزامناً مع الفراغات الخالية.

6.2 تقنية التعيين عبر القواميس البرمجية (Dictionary Mapping)

تبرز المعضلة الحقيقية حين يتطلب خط البيانات تعويض عدة أعمدة ذات أنواع بيانات متباينة في نفس الوقت؛ كأن نريد ملء القيم الفارغة في عمود النقاط بالرقم 0، وملء عمود المساعدات بالمتوسط العام 15.0، وملء عمود المؤتمر بالنص "Unknown". بالاعتماد على وسيط subset وحده، سنضطر إلى استدعاء دالة fillna() ثلاث مرات متعاقبة، نظراً لأن المعامل value القياسي لا يقبل إلا نوعاً بيانياً واحداً في كل دورة تنفيذ.

تقدم PySpark حلاً معمارياً أنيقاً وشاملاً لهذه المعضلة من خلال تقنية “التعيين عبر القواميس” (Dictionary Mapping). يتيح هذا النمط تمرير قاموس بايثون متكامل إلى المعامل value، حيث تمثل مفاتيح القاموس أسماء الأعمدة المستهدفة، بينما تمثل القيم المقابلة بدائل التعويض المحددة لكل عمود على حدة:

# بناء قاموس المعالجة الشامل لعدة أنواع متباينة
imputation_mapping = {
    "points": 0,
    "assists": 0.0,
    "conference": "Unknown"
}

# تنفيذ عملية التعويض الموحدة عبر القاموس
df_dict_filled = df_basketball.fillna(imputation_mapping)

print("--- نتيجة التعويض الموحد باستخدام القواميس ---")
df_dict_filled.show()

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

6.3 المفاضلة المعمارية بين وسيط subset ووسيط القاموس

تخضع المفاضلة بين الاعتماد على وسيط subset مقابل وسيط القاموس لعوامل معمارية وتشغيلية محددة. يوضح الجدول المفاهيمي التالي المعايير الأساسية لاختيار الأسلوب الأنسب:

  • وسيط subset: يُعد الخيار الأمثل والأنقى أداءً عندما تكون القيمة التعويضية متجانسة عبر مصفوفة من الأعمدة ذات نوع البيانات الموحد (كاستبدال كل الفراغات الرقمية بصفر). كما يمتاز بسهولة الربط الديناميكي مع نتائج التصفية المستندة إلى df.dtypes.
  • وسيط القاموس (Dictionary): يمثل الحل المعماري الأوحد لدمج استراتيجيات تعويض متباينة ومتعددة الأنواع ضمن عملية تحويلية فردية. يُنصح به بشدة في المراحل النهائية لإعداد البيانات لنماذج تعلم الآلة، حيث تتطلب كل سمة (Feature) قيمة ارتكاز محددة ومحسوبة سلفاً.

من زاوية الصيانة البرمجية، يتيح أسلوب القاموس حفظ مصفوفة التعويض كملف تكوين خارجي (بتنسيق JSON أو YAML)، مما يفصل منطق التطبيق الحسابي عن المعلمات المتغيرة، ويتيح تحديث قيم البدائل دورياً من قبل فرق التحليل دون الحاجة إلى المساس بالشفرة المصدرية لمحركات المعالجة.

7. تقييم الأداء والكفاءة الحاسوبية في مجموعات البيانات الضخمة

7.1 تأثير fillna() على شجرة التقييم الكسول وخطط التنفيذ

تعمل بيئة Apache Spark استناداً إلى مبدأ التقييم الكسول (Lazy Evaluation)؛ حيث لا يتم تنفيذ أي معالجة فعلية على البيانات عند استدعاء دالة fillna()، بل تُضاف العملية كعقدة جديدة ضمن خطة التنفيذ المنطقية (Logical Plan). يتولى محرك التحسين التحفيزي Catalyst Optimizer تحليل هذه الخطة ودمجها مع التحويلات السابقة واللاحقة للوصول إلى خطة تنفيذ فيزيائية (Physical Plan) تضمن أقصى درجات الكفاءة التشغيلية قبل انطلاق المهام الفعلية على أجهزة العنقود الحسابي.

يمكن فحص خطة التنفيذ وفهم كيف يترجم Spark أمر التعويض عبر استدعاء التابع explain():

print("--- الخطة الفيزيائية لتنفيذ fillna الموجهة ---")
df_filled_points.explain(True)

عند فحص المخرجات التحليلية للخطة، نلاحظ أن Spark يترجم استدعاء fillna(0, subset='points') داخلياً إلى تعبير إسقاط شرطي مكافئ: Project [team#0, conference#1, coalesce(points#2, 0) AS points#10, assists#3]. تظهر هذه الخطة بوضوح أن المحرك استعان بدالة coalesce منخفضة المستوى لاستبدال القيمة عند القراءة، مع الإبقاء على باقي الأعمدة في مساراتها المباشرة دون أي معالجات إضافية، مما يؤكد انتفاء أي كلفة تشغيلية زائدة نتيجة للتخصيص.

7.2 تأثير تحديد الأعمدة على استهلاك الذاكرة وتفادي خلط البيانات

تتمثل إحدى أعظم مزايا استخدام subset أو القواميس في تجنب أي احتمال لإثارة عمليات خلط البيانات بين العقد (Data Shuffling). فعملية ملء الفراغات الموجهة تُعد تحويلاً ضيق النطاق (Narrow Transformation)؛ حيث تُعالج كل تجزئة (Partition) داخل الذاكرة المحلية للعقدة العاملة بصورة مستقلة تماماً، ودون الحاجة إلى تبادل الصفوف أو التوزيع عبر الشبكة السلكية للعنقود.

علاوة على ذلك، يلعب محرك الذاكرة Tungsten دوراً حاسماً في تعظيم الكفاءة عند حصر التعويض في أعمدة معينة؛ حيث يُبقي الأعمدة غير المستهدفة بصيغتها الثنائية المضغوطة المقيمة خارج الذاكرة النظامية (Off-Heap Memory)، ولا يقوم بفك الضغط وتطبيق المقارنات الشرطية إلا على المتغيرات المحددة في وسيط subset. يقلل هذا السلوك بصورة دراماتيكية من استهلاك دورات المعالج المركزي (CPU Cycles) ويخفض العبء الواقع على جامع القمامة (Garbage Collector) في بيئة جافا الافتراضية (JVM)، وهو ما ينعكس مباشرة على استقرار وسرعة معالجة مجموعات البيانات المليارية.

7.3 المقارنة الأدائية بين fillna() والتحويلات الشرطية البديلة

يلجأ بعض المهندسين أحياناً إلى صياغة تعويض القيم الفارغة باستخدام الدوال الشرطية مثل when().otherwise() أو استدعاء دالة coalesce() الصريحة عبر مكتبة الوظائف. للمفاضلة بين هذه الأنماط، نستعرض المقارنة البرمجية التالية:

from pyspark.sql.functions import when, col, coalesce, lit

# الأسلوب الأول: استخدام fillna الموجهة
df_method1 = df_basketball.fillna(0, subset=["points"])

# الأسلوب الثاني: استخدام الشرط الصريح when/otherwise
df_method2 = df_basketball.withColumn(
    "points", 
    when(col("points").isNull(), lit(0)).otherwise(col("points"))
)

# الأسلوب الثالث: استخدام coalesce الصريحة
df_method3 = df_basketball.withColumn(
    "points", 
    coalesce(col("points"), lit(0))
)

من الناحية الأدائية، تؤول هذه الأساليب الثلاثة إلى خطط تنفيذ فيزيائية شديدة التقارب بفضل ذكاء محرك Catalyst؛ إلا أن fillna() تتفوق بصورة حاسمة في مؤشرات الإنتاجية البرمجية وصيانة الشفرة. فاستخدام withColumn المتكرر يؤدي إلى إعادة بناء إسقاط الإطار بالكامل مع كل عمود، مما يثقل كاهل المحلل المنطقي عند التعامل مع عشرات الأعمدة، في حين تنجز fillna() نفس المهمة بتعبير موجز، مقروء، وأقل عرضة لارتكاب الأخطاء النحوية في المشاريع الصناعية الضخمة.

8. معالجة الحالات الاستثنائية والأخطاء الشائعة أثناء الاستخدام

8.1 إدارة الأخطاء الناتجة عن مسميات الأعمدة غير الصحيحة

من الأخطاء البرمجية الشائعة تمرير اسم عمود غير متواجد فعلياً ضمن مخطط إطار البيانات داخل وسيط subset؛ إما نتيجة لخطأ مطبعي في التسمية أو نتيجة لتغير طرأ على مصادر التغذية دون تحديث الكود. في الإصدارات الحديثة من PySpark، يتعامل المحرك بصرامة مع هذه الحالة عبر إطلاق استثناء تحليلي (AnalysisException) يوقف تنفيذ البرنامج فوراً لحماية اتساق البيانات.

try:
    # محاولة استهداف عمود غير موجود في الجدول
    df_error = df_basketball.fillna(0, subset=["score_points"])
    df_error.show()
except Exception as e:
    print(f"تم التقاط الخطأ التحليلي المتوقع: {type(e).__name__}")
    # طباعة مختصرة للرسالة التحذيرية
    print("الرسالة: التحقق من وجود الأعمدة أظهر عدم مطابقة الاسم المدخل للمخطط.")

لتفادي هذا التوقف المفاجئ في بيئات التشغيل الذاتي والإنتاج المؤتمت، يتعين على المهندس اعتماد نمط البرمجة الدفاعية (Defensive Programming)؛ وذلك بإجراء تقاطع برمجي (Set Intersection) بين قائمة الأعمدة المستهدفة ومصفوفة الأعمدة المتوفرة فعلياً في الإطار قبل التمرير للدالة:

# فحص وقائي للتحقق من وجود الأعمدة قبل تنفيذ الاستبدال
target_columns = ["points", "invalid_column_name"]
existing_columns = set(df_basketball.columns)

# قصر المعالجة على الأعمدة الموجودة حقيقة
valid_targets = [c for c in target_columns if c in existing_columns]

df_safe = df_basketball.fillna(0, subset=valid_targets)
print("تم تنفيذ التعويض الآمن بنجاح على الأعمدة المحققة.")

8.2 التعامل مع تضارب أنواع البيانات غير المتوافقة

كما تم التوضيح في الباب الثاني، يتميز محرك Spark بسلوكه الصامت عند تمرير قيمة قياسية لا تتطابق مع نوع بيانات العمود المختار داخل subset. فإذا قمنا بتمرير قيمة نصية لتعويض عمود رقمي، فلن يطلق البرنامج خطأ، ولكنه سيتجاهل العملية بالكامل، مما يترك القيم المفقودة في مكانها دون معالجة.

توضح التجربة التالية هذا السلوك الحرج بدقة:

# تمرير قيمة نصية لاستهداف عمود رقمي
df_mismatch = df_basketball.fillna(value="فارغ", subset=["points"])

# معاينة النتائج للتأكد من استمرار وجود الفراغ
print("--- تجربة التضارب النمطي: استمرار وجود القيم المفقودة ---")
df_mismatch.select("team", "points").show()

يكشف الفحص الميداني أن عمود points ما زال محتفظاً بالقيم null. لتجنب الوقوع في هذا الفخ التشغيلي الصامت، يجب دائماً تعزيز مراحل التطهير بوحدات اختبارات جودة البيانات المؤتمتة (Data Quality Asserts)، والتي تقوم بالتحقق المباشر من انخفاض معدل الفقدان إلى القيمة المستهدفة بعد كل عملية تعويض، وإطلاق تنبيهات للمهندسين عند اكتشاف عدم تغير حالة الفقدان.

8.3 معالجة الحالات الحدية للقيم الصفرية والفارغة

تستدعي معالجة البيانات الانتباه إلى الفرق الإحصائي الدقيق بين “الصفر الحقيقي” (True Zero) و”الصفر التعويضي” (Imputed Zero). فالصفر الحقيقي يمثل نتيجة قياس فعلية تثبت حدوث الظاهرة بمقدار منعدم (كاللاعب الذي شارك بالفعل في المباراة ولم يسجل أي نقطة)، في حين يعبر الصفر التعويضي عن افتراض إداري لغياب المعلومة الأصلية (كاللاعب الذي لم تشمله استمارة الرصد من الأساس).

إن خلط هذين المفهومين يؤدي إلى مخاطر جسيمة عند تدريب نماذج الانحدار الإحصائي أو التحليل العنقودي؛ إذ يُعامل النموذج الرياضي كلا الصفرين كحقيقة تجريبية متكافئة، مما يشوه التقديرات التنبؤية. لمعالجة هذه المعضلة في النظم الحساسة، يُنصح بتطبيق تقنية “مؤشر الفقدان” (Missing Indicator Column)؛ حيث يتم إنشاء عمود إضافي من نوع منطقي يوثق ما إذا كانت القيمة أصلية أم معوضة قبل تنفيذ استدعاء fillna():

# توثيق الفقدان عبر عمود دلالي قبل تطبيق التعويض
df_flagged = df_basketball.withColumn(
    "points_was_missing", 
    col("points").isNull()
).fillna(0, subset=["points"])

print("--- الحفاظ على الأثر التفسيري عبر مؤشر الفقدان ---")
df_flagged.show()

تضمن هذه الاستراتيجية المتقدمة التخلص من القيم الفارغة لحماية استقرار الحسابات، مع الاحتفاظ في الوقت نفسه بكامل الشفافية التفسيرية التي تطلبها النماذج المتقدمة للتفريق بين البيانات الحقيقية والبيانات المقدرة صناعياً.

9. دمج fillna() المخصصة ضمن خطوط أنابيب معالجة البيانات المتقدمة

9.1 التوظيف ضمن مرحلة إعداد السمات لتعلم الآلة (MLlib)

تشترط معظم خوارزميات التعلم الآلي المضمنة في مكتبة Spark MLlib، مثل خوارزميات الانحدار اللوجستي والغابات العشوائية وشبكات التعلم العميق، خلو البيانات تماماً من أي قيم غير معرفة (null أو NaN). وتتضح هذه الضرورة بصورة قطعية عند استخدام محول السمات الشهير VectorAssembler، والذي يتوقف عن العمل فوراً ويطلق استثناءً تعطيلياً بمجرد مصادفة أي قيمة خالية في أحد الأعمدة الداخلة في تركيب مصفوفة السمات الموجهة للتدريب.

يمثل الاستخدام الموجه لدالة fillna() حجر الزاوية لإعداد السمات الرقمية والفئوية قبل إدخالها في خط الأنابيب (Pipeline). يوضح التطبيق البرمجي التالي كيفية دمج الدالة لتهيئة السمات المتجهة بنجاح تام:

from pyspark.ml.feature import VectorAssembler

# تطهير الأعمدة المحددة تحضيراً لإنشاء مصفوفة السمات
df_ready_for_ml = df_basketball.fillna({
    "points": 0,
    "assists": 0.0
})

# تجميع السمات المطهية في متجه أحادي موحد
assembler = VectorAssembler(
    inputCols=["points", "assists"],
    outputCol="features"
)

# تحويل البيانات بنجاح دون أي توقف لغياب القيم
ml_data = assembler.transform(df_ready_for_ml)
print("--- مصفوفة السمات بعد التطهير الموجه والدمج ---")
ml_data.select("team", "features").show(truncate=False)

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

9.2 استراتيجيات التسلسل العملياتي (Method Chaining)

يتميز أسلوب البرمجة الوظيفية في PySpark بدعم نمط “التسلسل العملياتي” (Fluent Method Chaining)، والذي يتيح تنفيذ سلسلة متتابعة من التحويلات المتشابكة بصياغة انسيابية متماسكة دون الحاجة إلى إنشاء متغيرات وسيطة تستهلك الذاكرة المرجعية للبرنامج. يمكن دمج التعويض الموجه للأعمدة ضمن خط تحويلي متكامل يجمع بين التطهير، والتصفية (Filtering)، والفرز (Sorting).

# بناء خط تحويلي متسلسل عالي الانسيابية
df_pipeline_result = (
    df_basketball
    # المرحلة الأولى: تطهير الحقول الرقمية
    .fillna(0, subset=["points"])
    .fillna(10.0, subset=["assists"])
    # المرحلة الثانية: تطهير الحقول الفئوية
    .fillna("Unknown", subset=["conference"])
    # المرحلة الثالثة: التصفية والفرز
    .filter(col("points") >= 100)
    .orderBy(col("points").desc())
)

print("--- مخرجات خط الأنابيب التحويلي المتسلسل ---")
df_pipeline_result.show()

يقوم محرك Catalyst بضغط هذا التسلسل الطويل ودمجه في مرحلة تقييم فيزيائية موحدة، حيث تُدمج عمليات التعويض الشرطية والتصفية ضمن ممر مسح واحد (Single Scan Pass) على مستوى الأقسام التخزينية، مما يضمن الحفاظ على نظافة الهيكل العام للشيفرة ومطابقتها لمعايير الجودة البرمجية العالمية دون التفريط في الأداء العالي.

9.3 حفظ البيانات المعالجة وتصديرها إلى صيغ التخزين الحديثة

تتمثل الخطوة الختامية في خطوط هندسة البيانات في تصدير أطر البيانات المعالجة إلى مستودعات التخزين الدائم. في بيئات البيانات الضخمة، يُعد تنسيق Apache Parquet وصيغ الجداول الحديثة مثل Delta Lake المعيار الذهبي المعتمد للتخزين نظراً لاعتمادها على الهيكلية العمودية (Columnar Storage) ومعدلات الضغط الفائقة وميزات التحقق من المعاملات (ACID Transactions).

يلعب غياب القيم المفقودة من الأعمدة المحددة دوراً حاسماً ومباشراً في رفع كفاءة تقنيات الضغط الرأسي (Columnar Compression Algorithms) مثل Run-Length Encoding و Dictionary Encoding المستخدمة داخل ملفات Parquet؛ حيث يؤدي استبدال الفراغات بقيم متماثلة متكررة إلى تمكين محرك الضغط من تقليص الحجم الفيزيائي للبيانات على أقراص التخزين بنسب هائلة، وتسريع استعلامات القراءة المستقبلية (Scan Speed) بفضل آليات تخطي البيانات المعتمدة على الإحصاءات الوصفية لكل قسم تخزيني.

# تصدير البيانات المطهية إلى صيغة Parquet العمودية
output_path = "/tmp/basketball_clean_parquet"

(
    df_pipeline_result.write
    .mode("overwrite")
    .format("parquet")
    .save(output_path)
)

print(f"تم تصدير البيانات بنجاح إلى المسار التخزيني: {output_path}")

يضمن التصدير إلى بيئات Delta Lake أيضاً التحقق الآلي الصارم من قيود المخطط (Schema Enforcement) ومنع تسرب أي قيم فارغة غير مقصودة في المستقبل إلى جداول الإنتاج المحمية، مما يرسخ استقرار خطوط التغذية التحليلية المستمرة.

10. أفضل الممارسات المنهجية لإدارة القيم المفقودة في النظم الإنتاجية

10.1 الموازنة الإحصائية بين استراتيجيات التعويض والحذف

يتطلب اتخاذ القرار الهندسي الحكيم موازنة دقيقة بين خياري التعويض الموجه عبر fillna() وحذف السجلات الناقصة كلياً عبر dropna(). يقدم الجدول التحليلي التالي مقارنة منهجية مبنية على أسس الإحصاء الاستدلالي لتوجيه هذا القرار في النظم الحساسة:

المعيار التحليلي التعويض الموجه via fillna() الحذف الموجه via dropna()
حجم العينة (Sample Size) يحافظ على كامل حجم العينة الأصلي دون تقليص قوة الاختبار الإحصائي. يؤدي إلى انكماش حجم العينة، مما يضعف القوة الإحصائية للنماذج.
الانحياز الرياضي (Statistical Bias) قد يُدخل انحيازاً صناعياً إذا كانت البدائل الثابتة غير مطابقة للواقع. يسبب انحيازاً جسيماً إذا لم تكن البيانات مفقودة عشوائياً بالكامل (MCAR).
التكلفة الحسابية (Computational Cost) منخفضة للغاية؛ عملية تحويل ضيقة تجري كلياً داخل الذاكرة المحلية. منخفضة إلى متوسطة؛ قد تتطلب عمليات إعادة موازنة الأقسام في الحالات المتطرفة.
حالات الاستخدام النموذجية السمات الحتمية لنماذج تعلم الآلة، والقياسات التراكمية، ومؤشرات الأداء. المتغيرات المستهدفة (Labels)، والبيانات ذات الفقدان الهائل الذي يتجاوز 70%.

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

10.2 التوثيق الصارم وتتبع مسار تدفق البيانات (Data Lineage)

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

يتحقق ذلك في بيئات البيانات الحديثة عبر تسجيل العمليات الإجرائية في منصات الحوكمة مثل DataHub أو OpenMetadata، بالإضافة إلى تضمين التوصيف التحويلي صراحة في بيانات الجدول الفوقية (Table Metadata). يجب أن يوضح التوثيق أسماء الأعمدة المعالجة، والنسبة المئوية للخلايا المعوضة، والقيم البديلة المختارة، والمسوغ المنطقي الإحصائي الذي بُني عليه القرار، مما يضمن الشفافية الكاملة ويمكّن فرق الرقابة والجودة من التحقق من سلامة النواتج في أي وقت.

10.3 دليل إرشادي لاختيار النمط البرمجي الأمثل لملء البيانات المفقودة

لتلخيص القواعد التوجيهية وتسهيل اتخاذ القرار اليومي لمهندسي وعقلاء البيانات، يقدم هذا الدليل مصفوفة القواعد المعيارية التالية لاختيار الأسلوب الأمثل لاستخدام fillna() في PySpark:

  • استخدم fillna(val, subset='col'): عندما يكون الهدف هو التدخل السريع في عمود وحيد محدد، مع الرغبة في الحفاظ على أقصى درجات الإيجاز النحوي أثناء التحليلات التفاعلية واستكشاف البيانات المبدئي.
  • استخدم fillna(val, subset=['col1', 'col2']): عندما تتعامل مع مصفوفة أعمدة تشترك في نفس النوع ونفس المعنى الإحصائي (مثل تحويل كافة العدادات الصفرية أو المؤشرات المترية)، وتستهدف الاستفادة القصوى من دمج الإسقاط داخل محرك Catalyst.
  • استخدم fillna(dict_mapping): في خطوط الأنابيب الإنتاجية المتقدمة التي تعالج جداول معقدة تحتوي على مزيج من المتغيرات العددية والنصية والفئوية، أو عند استيراد معايير التعويض من ملفات تكوين مفصولة لضمان سهولة الصيانة وقابلية التوسع.
  • استخدم الانتقاء الديناميكي عبر dtypes: عند بناء خطوط معالجة معيارية مخصصة للعمل فوق مجموعات بيانات متغيرة المخطط، لتفادي أخطاء تثبيت الأسماء (Hardcoding) وضمان استدامة الكود دون تدخل بشري مستمر.

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

خاتمة

تناول هذا المقال المعالجة التقنية والتنفيذية لدالة fillna() في PySpark، مع التركيز المنهجي على آليات حصر التعويض في أعمدة محددة وموجهة. بدأنا باستعراض الأسس الإحصائية لفقدان البيانات وفق تصنيفات دونالد روبين، ومخاطر التعويض الشامل غير المنضبط على التوزيعات التكرارية والمقاييس الوصفية. ثم انتقلنا إلى التشريح المعماري لتوقيع الدالة وعلاقتها التكافؤية بواجهة DataFrameNaFunctions، وأبرزنا الخصائص النمطية الصارمة والسلوك الصامت للمحرك عند عدم تطابق الأنواع.

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

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

المراجع

  • Armbrust, M., Xin, R. S., Lian, C., Huai, Y., Liu, D., Bradley, J. K., Meng, X., Kaftan, T., Franklin, M. J., Ghodsi, A., & Zaharia, M. (2015). Spark SQL: Relational data processing in Spark. In Proceedings of the 2015 ACM SIGMOD International Conference on Management of Data (pp. 1383–1394). Association for Computing Machinery. https://doi.org/10.1145/2723372.2742797
  • Chambers, B., & Zaharia, M. (2018). Spark: The definitive guide: Big data processing made simple (1st ed.). O’Reilly Media.
  • Little, R. J. A., & Rubin, D. B. (2019). Statistical analysis with missing data (3rd ed.). John Wiley & Sons. https://doi.org/10.1002/9781119013563
  • Rubin, D. B. (1976). Inference and missing data. Biometrika, 63(3), 581–590. https://doi.org/10.1093/biomet/63.3.581
  • Zaharia, M., Xin, R. S., Wendell, P., Das, T., Armbrust, M., Dave, A., Meng, X., Rosen, J., Venkataraman, S., Franklin, M. J., Ghodsi, A., Gonzalez, J., Shenker, S., & Stoica, I. (2016). Apache Spark: A unified engine for big data processing. Communications of the ACM, 59(11), 56–65. https://doi.org/10.1145/2934664
★

تقييم هذا المحتوى

5.0 / 5 • 7 تقييمات

اقتباس هذا المقال

looti, M. (2026, أكتوبر 24). PySpark: كيفية استخدام fillna() مع أعمدة محددة. عرب سايكلوجي. https://arabpsychology.com/statistics/pyspark-how-to-use-fillna-with-specific-columns/
looti, Mohammed. “PySpark: كيفية استخدام fillna() مع أعمدة محددة.” عرب سايكلوجي, 24 أكتوبر 2026, https://arabpsychology.com/statistics/pyspark-how-to-use-fillna-with-specific-columns/.
looti, Mohammed. “PySpark: كيفية استخدام fillna() مع أعمدة محددة.” عرب سايكلوجي. أكتوبر 24, 2026. https://arabpsychology.com/statistics/pyspark-how-to-use-fillna-with-specific-columns/.