كيفية استخدام عبارة Case في PySpark (مع مثال)

دليل أكاديمي شامل يشرح كيفية استخدام عبارة Case الشرطية في PySpark عبر دوال when وotherwise مع أمثلة عملية وتطبيقات على هياكل DataFrames الضخمة.

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

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

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

تعتبر معالجة الشروط المنطقية داخل هياكل البيانات الموزعة، وتحديداً أطر البيانات (DataFrames)، مسألة هندسية دقيقة تتجاوز مجرد فحص تعبير منطقي محلي. في بيئات قواعد البيانات العلائقية التقليدية، لطالما كانت عبارة CASE المعيارية حجر الزاوية في بناء المنطق التفرعي المستند إلى معايير SQL القياسية. ولكن مع التوسع نحو محركات الحوسبة واسعة النطاق، تطلب الأمر إعادة هندسة هذا المفهوم ليتوافق مع المفاهيم المعمارية لمحرك Spark، ولا سيما التقييم الكسول (Lazy Evaluation)، ومخططات التنفيذ غير الحلقية الموجهة (DAG)، وتجنب عمليات إعادة توزيع البيانات المكلفة عبر الشبكة والمعروفة بعمليات الخلط (Shuffle).

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

1. مقدمة نظرية حول المعالجة الشرطية في بيئات الحوسبة الموزعة

1.1 مفهوم التفرع الشرطي في معالجة البيانات الضخمة

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

يعتمد محرك Apache Spark على نموذج التقييم الكسول (Lazy Evaluation)، وهو ما يمنح المعالجة الشرطية كفاءة هندسية مضاعفة. فعند كتابة تعبير شرطي، لا يقوم المحرك بتنفيذه فوراً، بل يقوم بإنشاء مخطط تحليلي يتم تسجيله ضمن رسم بياني غير حلقي موجه (DAG). هذا التأجيل في التنفيذ يتيح لمحرّك التحسين الداخلي فحص كافة العمليات الشرطية المتتالية ودمجها أو تقليم الفروع غير الفعالة قبل ملامسة البيانات المخزنة على الأقراص أو الذاكرة، مما يقلل الهدر الحسابي إلى الصفر تقريباً.

علاوة على ذلك، تتميز التحويلات الشرطية في PySpark بأنها “تحويلات ضيقة النطاق” (Narrow Transformations). يعني هذا التوصيف المعماري أن كل قسم حسابي من البيانات الأصلية يحتاج فقط إلى بياناته الذاتية لتقييم الشرط واستخراج النتيجة للعمود الجديد، دون أي حاجة للتواصل مع العقد الأخرى أو تبادل السجلات عبر الشبكة. هذا الغياب التام لعمليات خلط البيانات (Shuffle Operations) يضمن بقاء استهلاك النطاق الترددي للشبكة منعدماً، مما يجعل التعقيد الزمني لعمليات التفرع الشرطي خطياً من الرتبة المتناسبة مباشرة مع حجم البيانات ومستقلاً عن عدد العقد الحاسوبية.

1.2 مقارنة بنيوية بين عبارة CASE في SQL ودوال PySpark المماثلة

يعود الفضل في توحيد صياغة العمليات الشرطية في لغات الاستعلام العلائقية إلى معيار SQL-92 (المعروف رسمياً بـ ISO/IEC 9075:1992)، والذي أدخل عبارة CASE WHEN ... THEN ... ELSE ... END كبنية تعبيرية قياسية تُرجع قيمة محددة بناءً على تقييم قائمة من المحددات المنطقية. حافظت هذه العبارة على ثباتها الدلالي لعقود عبر مختلف محركات قواعد البيانات، حيث تتيح التحقق المتسلسل من الشروط حتى الوصول إلى أول شرط يتحقق (First-match logic)، مع توفير قيمة بديلة افتراضية في حال فشل جميع الشروط السابقة.

عند ظهور واجهة أطر البيانات (DataFrame API) في أباتشي سبارك بواسطة فريق الباحثين في جامعة كاليفورنيا ببيركلي، نشأت الحاجة إلى ترجمة التعبير اللفظي لعبارة CASE إلى دوال برمجية وظيفية أصيلة تتناسب مع لغات البرمجة كبايثون وسكالا. تجلى هذا التقابل الدلالي من خلال دمج دالتي when() وotherwise()، حيث تقابل الدالة when() الفرع الشرطي WHEN ... THEN، بينما تؤدي الدالة otherwise() دور المعادل الدقيق للفرع البديل ELSE. هذه المواءمة البنيوية ضمنت الحفاظ على نفس البناء الفكري للمطورين القادمين من خلفية هندسة قواعد البيانات العلائقية.

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

1.3 أهمية دوال when وotherwise في هندسة البيانات

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

تمتد هذه الأهمية الاستراتيجية لتشمل مجالات هندسة الخصائص (Feature Engineering) الموجهة لتغذية نماذج تعلم الآلة الموزعة (Distributed Machine Learning). إن بناء خصائص جديدة قائمة على تقسيم البيانات المستمرة إلى فئات نوعية منفصلة (Binning or Discretization) يمثل خطوة حاسمة لزيادة دقة النماذج الخوارزمية. ومن خلال صياغة تعبيرات شرطية متعددة المستويات، يمكن لمهندسي البيانات تحويل المتغيرات العددية كالعمر، أو الدخل، أو نقاط الأداء إلى فئات دلالية موجزة تساهم في تقليل التشويش الإحصائي للبيانات الخام.

الأمر الأكثر أهمية من منظور البنية التحتية للحوسبة هو أن الاعتماد على الدوال الأصلية لـ PySpark يحمي بيئة التشغيل من الهدر الكارثي للذاكرة وزمن المعالجة الذي تسببه الدوال المعرفة من قبل المستخدم (User-Defined Functions – UDFs). عندما يكتب المطور دالة شرطية بلغة بايثون العادية ويطبقها كـ UDF، يضطر محرك Spark إلى تسلسل (Serialize) كل سطر بيانات ونقله من بيئة تشغيل جافا الافتراضية (JVM) إلى مفسر بايثون، ثم إعادة تسلسله مرة أخرى إلى الـ JVM. بالمقابل، تعمل دوال when() وotherwise() داخل محرك Catalyst التحليلي مباشرة على مستوى الكود الثنائي لجافا (Bytecode)، مستفيدة من تسريع المعالجة بتوليد الكود البرمجي لمرحلة كاملة (Whole-Stage Code Generation)، مما يحقق أداءً استثنائياً يتجاوز الدوال المعرفة بمراحل شاسعة.

2. البنية النحوية والدلالية لدوال when() وotherwise() في PySpark

2.1 استيراد وتفعيل الوحدات الأساسية من pyspark.sql.functions

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

تتمثل الممارسة البرمجية الفضلى المعتمدة عالمياً في مجتمعات هندسة البيانات في تجنب الاستيراد الشامل (Wildcard Imports مثل from pyspark.sql.functions import *)، وذلك لتفادي التضارب الدلالي الحتمي بين دوال Spark والدوال المدمجة مسبقاً في لغة بايثون؛ مثل دوال sum، أو min، أو max، أو حتى بعض الكلمات المحجوزة. عوضاً عن ذلك، يُنصح باستيراد الحزمة تحت اسم مستعار قياسي وموجز، وهو الحرف الكبير F، مما يمنح الشفرة البرمجية وضوحاً مطلقاً يحدد مصدر كل دالة بدقة متناهية.

لتحقيق ذلك عملياً، يتم استدعاء المكتبة بالصيغة القياسية التالية: from pyspark.sql import functions as F. بعد هذا التفعيل، يصبح بالإمكان الوصول إلى كافة العمليات المتجهة، بما في ذلك F.when() وF.col()، مع الاحتفاظ بسلامة النطاق البرمجي لبايثون. يسهم هذا النهج أيضاً في تعزيز إمكانية قراءة الكود من قِبل فرق العمل البرمجية المتعددة وتسهيل عمليات المراجعة البرمجية (Code Reviews) وأتمتة التحليل الساكن للشفرات البرمجية للكشف المبكر عن الأخطاء.

2.2 التشريح المصدري لدالة when المتسلسلة

تم تصميم الدالة when() لتستقبل وسيطين رئيسيين يحددان بنية التفرع: الوسيط الأول يمثل الشرط المنطقي الإلزامي (Condition)، والذي يجب أن يكون تعبيراً يرجع عموداً منطقياً من النوع البولياني (Boolean Column)، في حين يمثل الوسيط الثاني القيمة الناتجة المستهدفة (Value) التي يجب تعيينها للسطر الحسابي إذا ما تم تقييم الشرط على أنه صحيح (True). التركيب النحوي الأساسي لهذه الدالة يُصاغ كما يلي: F.when(condition, value).

تكمن القوة التعبيرية الحقيقية لهذه الأداة في قابليتها لربط الاستدعاءات المتعددة فيما يعرف بنمط السلسلة المنهجية (Method Chaining). فعند استدعاء دالة when() الأولى، فإنها تُرجع كائناً من فئة Column. ولأن فئة Column في PySpark تتضمن بداخلها تعريفاً لدالة when() إضافية، يمكن للمطور أن يُلحق استدعاءً شرطياً ثانياً وثالثاً مباشرة بنهاية الاستدعاء السابق، على النحو التالي: F.when(cond1, val1).when(cond2, val2).when(cond3, val3). يحاكي هذا التسلسل تماماً تكرار عبارات WHEN ... THEN في لغة SQL التقليدية.

تخضع هذه السلسلة المتصلة لآلية التحقق الصارم من الأولوية التنازلية، وتعرف بآلية التقييم عند أول تطابق (First-match Evaluation). فعند مرور كل سطر من أسطر البيانات عبر هذه السلسلة، يقوم المحرك بفحص الشرط الأول؛ فإذا تحقق، يتم تعيين القيمة المقابلة له فوراً ويتوقف المحرك عن فحص بقية الشروط اللاحقة لهذا السطر تحديداً، حتى لو كانت الشروط اللاحقة صحيحة ومتحققة هي الأخرى. هذه الخاصية تتطلب عناية رياضية فائقة في ترتيب الشروط من الأكثر خصوصية ودقة إلى الأكثر عمومية لتفادي مشكلات حجب النتائج غير المقصودة.

2.3 الوظيفة الدلالية لدالة otherwise والتعامل مع الحالات الافتراضية

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

في حال قرر مهندس البيانات إغفال استدعاء الدالة otherwise() عمداً أو سهواً، فإن محرك Apache Spark لا يُصدر خطأ برمجياً يوقف المعالجة، بل يتبنى سلوكاً حسابياً افتراضياً مستمداً من معايير SQL؛ حيث يقوم تلقائياً بإسناد القيمة الخالية الحقلية null (المقابلة لـ None في بايثون) لكافة الحالات التي لم ينطبق عليها أي شرط من الشروط السابقة. ورغم أن هذا السلوك قد يكون مرغوباً في بعض الحالات المتخصصة، إلا أنه في كثير من الأحيان يؤدي إلى تدفق قيم فارغة غير متوقعة قد تؤدي إلى انهيار خطوط المعالجة اللاحقة أو تشويه التحليلات الإحصائية.

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

3. إعداد بيئة العمل وإنشاء DataFrame تجريبي تفصيلي

3.1 تهيئة جلسة العمل SparkSession وتكوين الموارد

تُعد جلسة العمل SparkSession نقطة الدخول الموحدة والوحيدة لبرمجة التطبيقات في الإصدارات الحديثة من Apache Spark. تجمع هذه الجلسة كافة السياقات القديمة (مثل SparkContext وSQLContext وHiveContext) تحت مظلة معمارية واحدة متجانسة تتيح التفاعل السلس مع محرك البيانات ومكتباته الفرعية. تبدأ أي بيئة معالجة بافتتاح هذه الجلسة وضبط إعداداتها الحسابية المناسبة للمهام المستهدفة.

لإنشاء كائن الجلسة، يُعتمد نمط التصميم البرمجي الشهير المعروف بنمط الباني (Builder Pattern). يتيح هذا النمط للمطور تحديد معلمات التشغيل بشكل تسلسلي ومنظم. يتضمن ذلك تحديد الاسم الوصفي للتطبيق من خلال الدالة appName()، وتعيين العنوان الرئيسي للموزع الحسابي عبر الدالة master()، حيث يُستخدم النمط المحلي local[*] لتمكين التطبيق من استغلال كافة الأنوية المنطقية المتاحة على جهاز التطوير المحلي لأداء المعالجة المتوازية الافتراضية بكفاءة تامة.

يمكن تضمين هذا التكوين عبر الكود البرمجي التالي:

from pyspark.sql import SparkSession
spark = SparkSession.builder
    .appName("PySparkCaseStatementDemonstration")
    .master("local[*]")
    .config("spark.driver.memory", "2g")
    .getOrCreate()

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

3.2 تصميم وتجهيز مجموعة البيانات التجريبية

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

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

تمثل الشيفرة التالية إعداد البيانات الخام وهيكلية الأعمدة المستهدفة:

data_records = [
    (101, "Ahmad", 7),
    (102, "Fatima", 9),
    (103, "Zaid", 11),
    (104, "Layla", 14),
    (105, "Omar", 18),
    (106, "Yousef", 8),
    (107, "Mariam", 15)
]
columns_schema = ["player_id", "player_name", "points"]

يعكس هذا التوزيع العددي نقاطاً تتراوح من 7 إلى 18 نقطة، مما يسمح باختبار التفرع الشرطي بشكل دقيق يغطي كافة الحدود الفاصلة دون ترك أي مسار غير مختبر حسابياً.

3.3 توليد الـ DataFrame وفحص بنيته الهيكلية

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

تتم هذه العملية بالصيغة التالية:

df_players = spark.createDataFrame(data=data_records, schema=columns_schema)

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

يؤدي تشغيل df_players.printSchema() إلى تأكيد أن حقل points قد تم استنتاجه كمعرف عددي من نوع long وحقل player_name كـ string. هذا التثبت الإجرائي يعد خطوة استباقية إلزامية قبل تطبيق أي تعبيرات شرطية، إذ أن نوع البيانات المكتشف سيحدد العمليات الحسابية والمنطقية المسموح بتطبيقها عليه، متفادياً بذلك أخطاء عدم تطابق الأنواع التحليلية أثناء وقت الحساب الفعلي.

4. التطبيق العملي الكامل: بناء عبارة Case متعددة الشروط (مع مثال شامل)

4.1 تنفيذ التحويل البرمجي باستخدام withColumn

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

لتطبيق التقييم الشرطي لنقاط اللاعبين، سنضع قواعد عمل تنص على التالي: إذا كانت نقاط اللاعب أقل من 9، يُصنف في الفئة الضعيفة (“Bad”). وإذا كانت نقاطه تبدأ من 9 ولكنها أقل تماماً من 12، يصنف في الفئة المقبولة (“OK”). أما إذا كانت نقاطه من 12 وأقل من 15، فيصنف في الفئة الجيدة (“Good”). وأخيراً، فإن أي لاعب يحرز 15 نقطة فما فوق يؤول إلى الفئة الممتازة (“Great”) عبر الدالة التوجيهية otherwise().

يتم تجميع هذا التحويل الشامل داخل الشفرة البرمجية التالية:

from pyspark.sql import functions as F

df_classified = df_players.withColumn(
    "class",
    F.when(F.col("points") < 9, "Bad")
     .when((F.col("points") >= 9) & (F.col("points") < 12), "OK")
     .when((F.col("points") >= 12) & (F.col("points") < 15), "Good")
     .otherwise("Great")
)

يُلاحظ في هذه الصياغة الترابط المتين والانسيابي للشروط؛ حيث تُرجع كل دالة when تعبيراً برمجياً يتيح تعليق الدالة التالية عليه مباشرة، حتى ختام الهيكل بالدالة البديلة otherwise()، مما ينتج عنه تدفق برمجي نظيف وخالٍ من التعقيد.

4.2 التحليل التدريجي لمنطق التقييم الشرطي

لفهم الآلية التشغيلية التي يعتمدها محرك Spark في تقييم هذا التعبير المتسلسل، يجب تتبع رحلة السجل الواحد عبر المسار المنطقي. لنأخذ السجل الأول الخاص باللاعب “Ahmad” ذي النقاط 7؛ يبدأ المحرك بتقييم الشرط الأول F.col("points") < 9. وبما أن 7 أقل من 9، فإن التعبير البولياني يُنتج True مباشرة. بناءً على مبدأ التقييم القصير (Short-circuiting)، يسند المحرك فوراً السلسلة النصية “Bad” إلى عمود class الخاص بأحمد، ويتجاهل كافة الفروع اللاحقة دون بذل أي دورات معالجة إضافية في فحصها.

عند انتقال المحرك إلى السجل الثاني الخاص باللاعبة “Fatima” ذات النقاط 9، يتم تقييم الشرط الأول 9 < 9، والذي يُرجع قيمة False. ينتقل المحرك فوراً إلى الفرع الثاني المتمثل في التحقق من وقوع القيمة ضمن النطاق (points >= 9) & (points < 12). بما أن قيمة نقاط فاطمة تساوي 9، يتحقق الشطر الأول من الشرط وكذلك الشطر الثاني (9 أقل من 12)، مما يجعل ناتج العملية البوليانية المركبة True. على إثر ذلك، تُسند القيمة “OK” للعمود الجديد، ويتم تخطي ما تبقى من شروط.

يتكرر هذا الفحص الدقيق والمنظم مع كافة السجلات الحسابية المتبقية. فاللاعب “Layla” (14 نقطة) يفشل في أول شرطين ليقع ضمن الشرط الثالث المخصص للقيم الأقل من 15، مسجلاً فئة “Good”. أما اللاعب “Omar” (18 نقطة) واللاعبة “Mariam” (15 نقطة)، فتفشل نقاطهما في تلبية أي من الشروط الثلاثة المتسلسلة؛ فتقودهما المسارات المنطقية حتماً إلى الفرع الختامي المصاغ داخل otherwise()، ليُمنح كلاهما التصنيف الأعلى “Great”. هذا السلوك يبرز الكيفية التي تغطي بها الدالة الافتراضية كافة القيم المتطرفة والعليا بسلاسة وثبات مطلقين.

4.3 معاينة المخرجات المجدولة والتحقق من صحة النتائج

بعد الانتهاء من تعريف مسار التحويل، نصل إلى مرحلة استدعاء إجراء فعلي (Action) لإجبار المحرك على تنفيذ العمليات الكسولة المتراكمة وعرض المخرجات. نستخدم لهذا الغرض الأمر df_classified.show(truncate=False) لطباعة الجدول الناتج بوضوح تام يمنع اقتصاص النصوص داخل الأعمدة المعروضة.

تظهر النتائج النهائية على الشاشة متطابقة تماماً مع الأهداف التصميمية للمنطق الشرطي على النحو الموضح وصفياً:

  • اللاعب 101 (Ahmad): نقاطه 7، تم تعيينه للفئة: Bad.
  • اللاعب 102 (Fatima): نقاطها 9، تم تعيينها للفئة: OK.
  • اللاعب 103 (Zaid): نقاطه 11، تم تعيينه للفئة: OK.
  • اللاعب 104 (Layla): نقاطها 14، تم تعيينها للفئة: Good.
  • اللاعب 105 (Omar): نقاطه 18، تم تعيينه للفئة: Great.
  • اللاعب 106 (Yousef): نقاطه 8، تم تعيينه للفئة: Bad.
  • اللاعب 107 (Mariam): نقاطها 15، تم تعيينها للفئة: Great.

يؤكد هذا الفحص الميداني سلامة السلوك الرياضي للتصنيف، لاسيما عند النقاط الحدية الفاصلة مثل 9 و 15؛ حيث انضمت النقطة 9 إلى الفئة الأعلى (“OK”) ولم تسقط في الفئة الأدنى (“Bad”) بفضل استخدام مشغل الأكبر من أو يساوي (>=) الدقيق. كما انضمت النقطة 15 بنجاح إلى الفئة الاستثنائية (“Great”) وفق القيمة الافتراضية للفرع البديل، مما يثبت التطابق بين التحليل النظري والنتائج الحسابية المحققة فعلياً على أرض الواقع.

5. محاكاة عبارة CASE القياسية باستخدام استعلامات SQL المباشرة في PySpark

5.1 تسجيل الـ DataFrame كعرض زمني مؤقت (Temporary View)

يوفر محرك Apache Spark مرونة معمارية منقطعة النظير من خلال إمكانية التبديل السلس بين واجهة البرمجة الوظيفية لأطر البيانات والواجهة العلائقية المستندة إلى لغة SQL القياسية. ولكي نتمكن من استخدام جملة CASE WHEN بصيغتها النصية التقليدية الشائعة في قواعد البيانات مثل PostgreSQL أو Oracle، يجب أولاً تقديم إطار البيانات لمحرك الكتالوج الداخلي في Spark ككيان جدولي يمكن الاستعلام عنه.

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

يتم تنفيذ ذلك برمجياً عبر السطر التالي:

df_players.createOrReplaceTempView("players_view")

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

5.2 صياغة استعلام CASE WHEN المباشر في كود PySpark

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

تتم كتابة الاستعلام لمحاكاة نفس منطق التصنيف الرياضي السابق على النحو التالي:

sql_query = """
SELECT
    player_id,
    player_name,
    points,
    CASE
        WHEN points < 9 THEN 'Bad'
        WHEN points >= 9 AND points < 12 THEN 'OK'
        WHEN points >= 12 AND points < 15 THEN 'Good'
        ELSE 'Great'
    END AS class
FROM players_view
"""
df_sql_result = spark.sql(sql_query)

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

5.3 المقارنة التحليلية بين واجهة DataFrame ونهج SQL المباشر

يثور تساؤل متكرر في الأوساط التقنية حول المفاضلة بين الاعتماد على واجهة الدوال المتسلسلة (when/otherwise) واستخدام استعلامات SQL المباشرة (spark.sql). من المنظور الأدائي البحت، لا يوجد أي فارق في سرعة التنفيذ الحسابي أو استهلاك موارد الذاكرة بين النموذجين على الإطلاق. يعود هذا التطابق التام إلى أن كلتا الصياغتين تخضعان في النهاية لنفس المترجم الداخلي، وهو محرك التحسين كَاتاليست (Catalyst Optimizer)، الذي يقوم بتحويلهما إلى نفس شجرة التعبيرات التحليلية وخطة التنفيذ المادي ذاتها تماماً.

ومع ذلك، تظهر الفروق البارزة عند تقييم معايير هندسة البرمجيات وجودة الكود؛ حيث تتميز واجهة أطر البيانات بكونها برمجية بالكامل (Type-safe programmatically)، مما يتيح تجزئة منطق الشروط المعقدة وتركيبها ديناميكياً باستخدام هياكل لغة بايثون كاللوائح والحلقات التكرارية. كما تساعد بيئات التطوير على اكتشاف أخطاء أسماء المتغيرات والأعمدة أثناء الكتابة. على النقيض من ذلك، تظل استعلامات SQL عبارة عن كتل نصية معزولة (String Literals) يعجز المفسر عن التحقق من صحتها التركيبية إلا عند الوصول إلى مرحلة التشغيل الفعلي.

تتحدد استراتيجية الاختيار وفقاً لطبيعة المشروع وخلفية الكادر الهندسي؛ فإذا كان الفريق يتألف أساساً من مهندسي برمجيات وباحثين في تعلم الآلة، فإن واجهة الدوال الأصلية when وotherwise تكون الخيار الطبيعي والأكثر تناغماً مع بقية أجزاء النظام. أما في حال كانت الأولوية هي تسريع وتيرة العمل التشاركي مع محللي البيانات والمختصين التجاريين الذين يمتلكون تمكناً عميقاً من لغة SQL القياسية، فإن نهج الاستعلامات المباشرة يوفر جسراً تواصلياً مثالياً يلبي متطلبات العمل دون المساس بالأداء العام إطلاقاً.

6. المعالجة المتقدمة للشروط المنطقية المعقدة والمتعددة الأبعاد

6.1 دمج العوامل المنطقية الثنائية (& و | و ~)

عند بناء شروط منطقية مركبة تتطلب التحقق من عدة معايير في آن واحد ضمن فرع when الفردي، تفرض بيئة PySpark قواعد صارمة تختلف جوهرياً عن بايثون القياسية. نظراً لأن المقارنات تجري على مستوى أعمدة كاملة كمتجهات رياضية موجهة (Vectorized Expressions)، فإن الكلمات المفتاحية التقليدية في بايثون مثل and وor وnot لا تعمل، ويجب استبدالها حصراً بالعوامل الثنائية على مستوى البت (Bitwise Operators): الرمز & لتمثيل الواو المنطقية (AND)، والرمز | لتمثيل الاختيار المنطقي (OR)، والرمز ~ لتمثيل النفي المنطقي (NOT).

تتمثل القاعدة الجوهرية التي لا تقبل الاستثناء في وجوب إحاطة كل تعبير شرطي مفرد بأقواس دائرية منفصلة (...) قبل ربطه بالعامل الثنائي. يعود السبب في ذلك إلى قواعد أسبقية العوامل (Operator Precedence) في لغة بايثون؛ حيث تمتلك المشغلات الثنائية مثل & أسبقية تقييم أعلى من مشغلات المقارنة العلائقية مثل >= أو <. وفي حال إغفال الأقواس، ستحاول بايثون تطبيق العملية الثنائية بين القيمتين المجاورتين للرمز أولاً قبل مقارنتهما، مما يؤدي فوراً إلى انهيار الشفرة وظهور استثناء تركيبي معقد.

تتضح هذه الصياغة السليمة في المثال النموذجي التالي:

# استخدام الأقواس إلزامي لمنع حدوث أخطاء أسبقية العوامل
condition_complex = (F.col("points") >= 10) & (F.col("points") <= 20) | ~(F.col("is_active") == False)

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

6.2 التقييم الشرطي المعتمد على أعمدة متعددة متزامنة

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

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

لتوضيح ذلك، نفترض وجود عمودين: points و games_played، حيث نرغب في تمييز اللاعبين ذوي الفعالية الاستثنائية الذين حققوا نقاطاً عالية في عدد مباريات قليل:

df_multi_col = df_players.withColumn(
    "efficiency_status",
    F.when((F.col("points") >= 15) & (F.col("games_played") <= 10), "Super-Efficient")
     .when((F.col("points") >= 15) & (F.col("games_played") > 10), "Standard-High-Scorer")
     .otherwise("Regular")
)

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

6.3 بناء الهياكل الشرطية المتداخلة (Nested Case Statements)

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

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

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

7. التعامل مع القيم المفقودة (Nulls) وتوافق أنواع البيانات

7.1 إدارة القيم الخالية عبر دوال isNull وisNotNull

تعتبر القيم المفقودة (Nulls) من أكبر التحديات التي تواجه مهندسي البيانات أثناء المعالجة الشرطية الموزعة. في الأنظمة التي تتبع المنطق الثلاثي القيم الخاص بـ ANSI SQL (والذي يضم: True و False و Unknown)، تؤدي أي مقارنة علائقية قياسية تجري ضد قيمة مجهولة (مثل col("points") > 9 عندما تكون النقطة null) إلى ناتج مجهول (Unknown/False). هذا يعني أن السجلات المحتوية على قيم خالية ستفشل صامتاً في تلبية كافة الشروط وتتجه مباشرة نحو الفرع الافتراضي، وهو سلوك قد يخفي مشاكل جوهرية في جودة البيانات الأصلية.

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

يتم تطبيق هذا الفحص الوقائي عبر الصياغة النموذجية التالية:

df_safe = df_players.withColumn(
    "data_quality_class",
    F.when(F.col("points").isNull(), "Missing_Data")
     .when(F.col("points") < 9, "Bad")
     .otherwise("Acceptable")
)

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

7.2 مبدأ تجانس أنواع البيانات في فروع النتائج (Type Coercion)

يفرض محرك Apache Spark مبدأً بنيوياً صارماً يُلزم كافة فروع النتائج المنبثقة عن استدعاءات when() المتعددة وكذلك دالة otherwise() بإرجاع قيم تنتمي إلى نفس نوع البيانات الأساسي (Data Type) أو إلى أنواع متوافقة يمكن ترقيتها تلقائياً. العمود الناتج عن التحويل هو عمود موحد المخطط، ولا يمكن لعمود واحد في بنية الـ DataFrame الموزعة أن يحتوي على قيم متباينة نمطياً كأن يكون بعضها أعداداً صحيحة والبعض الآخر مصفوفات أو كائنات معقدة.

في حال حاولت الشفرة إرجاع أنواع غير متطابقة—كأن يُرجع الشرط الأول قيمة نصية "Passed" بينما يُرجع الشرط الثاني قيمة عددية صحيحة 100—سيقوم محرك التحليل والتحقق (Catalyst Analyzer) إما بمحاولة تطبيق التحويل الضمني (Implicit Type Coercion) إلى نوع أعم كتحويل الجميع إلى سلاسل نصية، أو سيتوقف عن العمل فوراً ويطلق استثناءً تحليلياً صريحاً يسمى AnalysisException مشيراً إلى فشل حل الأنواع المتضاربة (Type Mismatch Failure).

لتفادي هذا الخلل المعماري، يتحتم على المطور توحيد المخرجات صراحةً باستخدام دالة التحويل النمطي cast(). فإذا كانت المخرجات تتطلب تمازجاً معيناً، يجب صب كافة النتائج بشكل استباقي في النوع الموحد؛ على سبيل المثال: F.lit(100).cast("string"). يضمن هذا النهج الدقيق بقاء المخطط الهيكلي لإطار البيانات متماسكاً ومتوافقاً مع معايير المعالجة الصارمة، ويسهل تخزين النتائج لاحقاً في مستودعات البيانات الحديثة كـ Delta Lake وApache Iceberg دون مواجهة رفض هيكلي.

7.3 التعامل مع الحالات الاستثنائية للبيانات النصية والرقمية

إلى جانب مسألة القيم المفقودة (Nulls)، تزخر البيانات الواقعية بحالات شاذة تتطلب معالجة شرطية خاصة لا تقل أهمية. في البيانات الرقمية، تشكل القيم غير العددية (NaN – Not a Number) والقيم اللانهائية (Infinity) الناتجة عن القسمة على صفر أو الحسابات اللوغاريتمية الخاطئة تهديداً مستمراً للاستقرار الحسابي، إذ أنها قد تجتاز فحص isNotNull() بنجاح ولكنها تفسد التقييمات الرياضية للشروط اللاحقة. لمواجهة ذلك، يوفر Spark الدالة isnan() لعزل هذه الحالات الخاصة وتصنيفها بدقة تامة.

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

يوضح المثال التالي آلية المعالجة الوقائية المشتركة:

df_robust = df_players.withColumn(
    "robust_class",
    F.when(F.col("points").isNull() | F.isnan(F.col("points")), "Invalid_Numeric")
     .when(F.trim(F.col("player_name")) == "", "Missing_Identity")
     .when(F.col("points") >= 15, "Elite")
     .otherwise("Standard")
)

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

8. كفاءة الأداء وتحليل خطط التنفيذ لمحرك Catalyst Optimizer

8.1 آلية تحويل دوال when إلى أشجار تعبيرية (Expression Trees)

يعمل محرك التحسين الداخلي لـ Apache Spark، المعروف باسم Catalyst Optimizer، كعقل مدبر لكافة العمليات الحسابية داخل النظام. عندما يتم بناء سلسلة من دوال when() المتتابعة، لا يتم تشغيلها كحلقات برمجية إجرائية، بل يقوم المحرك بتمثيلها داخلياً على هيئة عقدة تعبيرية شجرية تسمى CaseWhen. تحوي هذه العقدة قائمة مرتبة من أزواج الفروع: (predicate, result)، مع فرع افتراضي يمثل elseValue.

خلال مرحلة التحسين المنطقي (Logical Optimization Phase)، يقوم المحرك بتطبيق قواعد متقدمة تهدف إلى تبسيط هذه الشجرة التعبيرية. من أبرز هذه القواعد ما يُعرف بـ “طي الثوابت” (Constant Folding) واختصار التعبيرات الزائدة؛ فإذا اكتشف المحرك وجود شرط منطقي دائم التحقق (Tautology مثل 1 == 1) أو دائم الاستحالة (Contradiction مثل 1 == 0)، فإنه يقوم بإلغاء الفروع التي يستحيل الوصول إليها، مما يقلص حجم الشجرة ويجعل التنفيذ أكثر سرعة واختصاراً.

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

8.2 قراءة وتفسير خطط التنفيذ الفيزيائية عبر explain()

تُعد دالة explain() النافذة الأساسية التي تمكّن المهندسين والباحثين من استكشاف ما يدور خلف الكواليس داخل محرك Spark. من خلال استدعاء هذه الدالة على إطار البيانات المحتوي على التعبير الشرطي (df_classified.explain(True))، يُعرض المخطط الكامل بمراحله الأربع: الخطة المنطقية المحللة (Analyzed Logical Plan)، والخطة المنطقية المحسنة (Optimized Logical Plan)، والخطة المادية (Physical Plan)، بالإضافة إلى شفرة توليد الكود البرمجي لمرحلة كاملة (Whole-Stage Code Generation).

عند فحص الخطة المادية للتحويل الشرطي المطبق، نلاحظ وجود عملية إسقاط تعبيري تُعرف بـ Project. يندرج تحت هذه العملية التعبير الداخلي الصريح CaseWhen، مع عرض لكافة الفروع المنطقية التي تم دمجها. يظهر التحليل بجلاء أن هذه العملية تتم موضعياً داخل كل قسم حسابي دون الحاجة إلى تشغيل أي مشغل من مشغلات التبادل الشبكي مثل Exchange، وهو ما يؤكد بشكل قاطع أن المعالجة تمت كعملية ضيقة النطاق (Narrow Transformation).

يساعد التدقيق في خطة التنفيذ المادي أيضاً على التحقق من أن أنواع البيانات المستنتجة قد تم حلها بشكل سليم، وأن التحويلات النمطية المطبقة لم تستدعِ عمليات تحويل معقدة قد تُجهد وحدات المعالجة المركزية. إن القراءة الدقيقة لمخرجات explain() تمنح المهندس اليقين التام بأن الشفرة البرمجية المكتوبة بأعلى مستويات التجريد تتحول بالفعل إلى تعليمات مادية متناهية الكفاءة على مستوى العتاد المشترك للمنظومة الموزعة.

8.3 المقارنة الأدائية بين دوال PySpark الأصلية والدوال المعرفة UDFs

يعد التخلي عن الدوال الأصلية لصالح الدوال المعرفة من قِبل المستخدم (Python UDFs) أحد الأخطاء الكارثية الأكثر شيوعاً التي تُلحق ضرراً فادحاً بأداء تطبيقات PySpark. لتوضيح هذا التباين الحسابي بعمق، يجب استيعاب المسار المعماري الذي تسلكه البيانات في كلتا الحالتين أثناء تنفيذ التحويل الشرطي على ملايين السجلات.

عند استخدام دوال when() وotherwise()، تجري المعالجة بأكملها داخل مساحة الذاكرة الخاصة بآلة جافا الافتراضية (JVM)، حيث يقوم محرك Catalyst بتوليد كود جافا ثنائي فائق السرعة عبر تقنية Janino Compiler ضمن إطار Whole-Stage Code Generation. هذا يعني أن البيانات تُعالج مباشرة في مسار خطي فائق الكفاءة، مع استغلال أمثل لسجلات المعالج (CPU Registers) ودون أدنى وسيط برمجي.

على النقيض من ذلك، فإن تطبيق المعالجة الشرطية عبر دالة بايثون تقليدية مغلفة بـ udf() يفرض عبئاً تشغيلياً هائلاً؛ حيث يضطر المحرك إلى فتح أنابيب تواصل فرعية (IPC Pipes) بين بيئة الـ JVM وعمليات بايثون الفرعية (Python Daemon Workers). يتطلب ذلك تسلسل كل كائن من الـ JVM ونقله عبر المقابس (Sockets) إلى بايثون، ثم إلغاء تسلسله، وتطبيق التفرع الشرطي في بيئة بايثون البطيئة، ثم إعادة تسلسل النتيجة ونقلها مجدداً إلى الـ JVM. تستهلك هذه العمليات الحسابية المتكررة أضعاف الوقت والذاكرة مقارنة بالدوال الأصلية، وتلغي تماماً إمكانية تحسين الخطة بواسطة Catalyst. ولذلك، تُعد الدوال الأصلية لـ PySpark هي المعيار الهندسي الإلزامي لكافة التطبيقات الإنتاجية عالية الأداء.

9. الأخطاء البرمجية الشائعة واستراتيجيات استكشاف الأخطاء وإصلاحها

9.1 خطأ استخدام الكلمات المحجوزة لبايثون (and / or) بدل العوامل المتجهية

يُعد الوقوع في مصيدة استخدام الروابط المنطقية الخاصة بلغة بايثون القياسية (مثل and و or) داخل وسيط الشرط لدالة when() من أكثر الأخطاء تواتراً لدى المطورين، لا سيما القادمين حديثاً من برمجة بايثون التقليدية إلى الحوسبة الموزعة. يظهر هذا الخطأ مباشرة عند محاولة كتابة تعبير مثل: F.when(F.col("points") > 5 and F.col("points") < 10, "Mid").

ينجم عن هذا الاستخدام غير الصحيح انهيار فوري للشفرة وتوليد الاستثناء البرمجي الشهير:
ValueError: Cannot convert column into bool: please use '&' for 'and', '|' for 'or', '~' for 'not' when building DataFrame expressions.
يعود السبب الجذري لهذا الاستثناء إلى البنية الداخلية لكائن Column في PySpark؛ حيث تتطلب الكلمات المحجوزة and و or في بايثون تقييم القيمة البوليانية الحتمية للكائن فوراً عبر استدعاء الدالة السحرية __bool__(). وبما أن كائن العمود لا يمثل قيمة فردية حاسمة بل يمثل متجهاً بيانياً مجرداً لملايين السجلات الموزعة التي لم تُعالج بعد، فإنه يرفض هذا التحويل القسري لمنع السلوكيات غير المحددة.

يتمثل الحل التقني الحصري لهذا الخطأ في الاستبدال الصارم للكلمات بالرموز الثنائية المقابلة مع التأطير الحتمي للأقواس: استخدام & بدلاً من and، والرمز | بدلاً من or. يوجه هذا التعديل البسيط مفسر بايثون لاستدعاء الدوال المحملة تحميلاً زائداً (Overloaded Operators) داخل فئة Column، وهي الدالتان __and__ و __or__، واللتان تقومان ببناء عقد شجرية منطقية جديدة بدلاً من محاولة تقييم القيمة فورياً، مما يعيد التناغم التام للشفرة البرمجية.

9.2 خطأ إغفال دالة otherwise والتبعات غير المقصودة

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

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

تقضي الممارسة الهندسية الفضلى والمثبتة في أطر حوكمة البيانات بضرورة إنهاء أي سلسلة شرطية باستدعاء صريح لدالة otherwise(). وفي حال لم تكن هناك قيمة افتراضية واضحة تفرضها منطقية الأعمال، يجب تعيين قيمة رمزية معيارية تشير صراحةً إلى عدم انطباق الشروط، مثل "UNKNOWN"، أو "UNCLASSIFIED"، أو -1 في الأعمدة العددية. يتيح هذا الإجراء الوقائي عزل تلك السجلات بسهولة فائقة وإجراء تدقيق لاحق عليها للكشف عن أسباب عدم خضوعها للمسارات المنطقية المعتادة.

9.3 أخطاء الترتيب غير المنطقي للشروط المتداخلة

نظراً لالتزام محرك Spark الصارم بآلية التقييم عند أول تطابق (First-match logic)، فإن الترتيب البنائي للشروط داخل سلسلة when() المتتابعة يكتسب أهمية حاسمة تتجاوز مجرد التنسيق الشكلي. يقع العديد من المطورين في خطأ ترتيب الشروط بترتيب متناقض يؤدي إلى وقوع البيانات في فئات شرطية عامة قبل أن تتاح لها الفرصة للوصول إلى الشروط التخصصية الأكثر دقة، وهي الظاهرة المعمارية المعروفة باسم “حجب الشروط” (Condition Shadowing).

لتجسيد هذا الخطأ عملياً، نفترض أن مطوراً يرغب في تصنيف النقاط بحيث تكون الدرجات الأعلى من 90 “ممتاز” والدرجات الأعلى من 75 “جيد جداً”، وقام بكتابة الشفرة على النحو التالي:

# ترتيب خاطئ يؤدي إلى حجب الشرط الثاني
df_faulty = df.withColumn(
    "grade",
    F.when(F.col("score") > 75, "Very Good")
     .when(F.col("score") > 90, "Excellent")
     .otherwise("Pass")
)

في هذا النموذج المعيب، لن يتم تصنيف أي طالب على الإطلاق بفئة “Excellent” حتى لو حصل على 100 درجة؛ ذلك لأن أي درجة تتجاوز 90 هي بالضرورة تتجاوز 75 أيضاً، وبالتالي سيتم التقاطها فوراً عند المحطة الأولى وتعيين فئة “Very Good” لها، محققة خروجاً مبكراً يعطل فحص الشرط الثاني تماماً.

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

10. أفضل الممارسات الهندسية والبدائل المتقدمة لصيانة الكود

10.1 توليد سلاسل الشروط ديناميكياً باستخدام هياكل بايثون

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

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

يوضح المثال المتقدم التالي هذه الآلية الوظيفية المبتكرة:

from functools import reduce

# قائمة ديناميكية من القواعد: (الشرط البولياني، القيمة الناتجة)
rules = [
    (F.col("points") < 9, "Bad"),
    ((F.col("points") >= 9) & (F.col("points") < 12), "OK"),
    ((F.col("points") >= 12) & (F.col("points") < 15), "Good")
]

# تطبيق دمج وظيفي ديناميكي لبناء السلسلة المنطقية برمجياً
initial_expr = F.when(rules[0][0], rules[0][1])
dynamic_case = reduce(
    lambda acc_expr, rule: acc_expr.when(rule[0], rule[1]),
    rules[1:],
    initial_expr
).otherwise("Great")

df_dynamic = df_players.withColumn("class", dynamic_case)

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

10.2 الاستعانة بدالة expr() لكتابة شروط SQL مضغوطة

توفر مكتبة pyspark.sql.functions أداة مميزة تُعرف بالدالة expr()، والتي تمثل حلقة وصل عبقرية بين التدوين البرمجي الوظيفي والتعبيرات النصية لـ SQL. تتيح هذه الدالة للمطور كتابة أجزاء محددة من تعبيرات SQL—بما في ذلك عبارة CASE WHEN المتكاملة—وتمريرها مباشرة كوسيط داخل توابع أطر البيانات مثل withColumn() دون الحاجة إلى تسجيل جداول مؤقتة أو مغادرة واجهة الـ DataFrame.

يتحقق هذا التوازن العملي عبر صياغة تعبير شرطي نصي مضغوط وأنيق كما يلي:

from pyspark.sql.functions import expr

df_expr = df_players.withColumn(
    "class",
    expr("""
        CASE
            WHEN points < 9 THEN 'Bad'
            WHEN points >= 9 AND points < 12 THEN 'OK'
            WHEN points >= 12 AND points < 15 THEN 'Good'
            ELSE 'Great'
        END
    """)
)

تجمع هذه الطريقة بين أفضل ما في النموذجين؛ فهي تمنح المهندس الإيجاز الشديد والمقروئية العالية المميزة لنصوص SQL القياسية، وتوفر في الوقت ذاته القدرة على إدماج هذا النص مباشرة ضمن تدفق برمجي متتابع لواجهة البيانات. كما تخضع العبارة المنفذة عبر expr() لنفس التحسينات الصارمة لمحرك Catalyst، مما يجعلها بديلاً هندسياً فائق الجاذبية للشروط الطويلة والمتشعبة التي قد تبدو مرهقة بصيغة when/otherwise التقليدية.

10.3 توثيق واختبار خطوط تصنيف البيانات في المشروعات الإنتاجية

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

بالتوازي مع التوثيق، تبرز ضرورة إدماج أطر عمل فحص جودة البيانات الموزعة، مثل إطار Great Expectations أو مكتبة PyDeequ التابعة لأمازون. تتيح هذه الأدوات تطبيق فحوصات تأكيدية لاحقة على العمود المصنف؛ مثل التحقق من أن نسبة القيم الخالية (Null Percentage) في العمود الجديد تساوي صفراً تماماً، أو التأكد من أن القيم الناتجة تنحصر حصراً داخل المجموعة المسموح بها والمحددة في متطلبات النظام (مثلاً: التأكد من اقتصار القيم على: ‘Bad’, ‘OK’, ‘Good’, ‘Great’).

أخيراً، يجب فصل المنطق الشرطي وبناؤه داخل دوال نقية مستقلة (Pure Functions) تخضع لاختبارات وحدة برمجية مؤتمتة وشاملة باستخدام مكتبات مثل pytest. من خلال تغذية هذه الدوال بعينات بيانات اختبارية محكومة (Mock DataFrames) تحوي كافة الحالات الطبيعية والحدية والبيانات المفقودة، يمكن التحقق المستمر من مناعة الكود الشرطي ضد التراجعات البرمجية (Regressions) عند إجراء أي تحسينات أو ترقيات مستقبلية للبنية التحتية، مما يضمن استمرارية واستقرار خطوط معالجة البيانات الضخمة بأعلى معايير الجودة الهندسية.

خاتمة

استعرضنا في هذا الدليل الأكاديمي الشامل الأسس النظرية والتطبيقية لتنفيذ وإدارة التفرعات الشرطية في بيئات الحوسبة الموزعة باستخدام PySpark. بدأنا بتأصيل المفاهيم المعمارية التي تميز معالجة البيانات الضخمة، ولا سيما نموذج التقييم الكسول وتصنيف العمليات الشرطية كتحويلات ضيقة النطاق تعصم عناقيد الحوسبة من التكاليف الباهظة لخلط البيانات عبر الشبكات. وتتبعنا الجذور التاريخية والدلالية لعبارة CASE المعيارية الصادرة ضمن مواصفات SQL-92 وكيفية مواءمتها داخل واجهة أطر البيانات الحديثة عبر دوال when() وotherwise() الأصلية والمستندة إلى محرك Catalyst التحليلي فائق التطور.

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

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

المراجع

  • 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.
  • International Organization for Standardization. (1992). Information technology — Database languages — SQL (ISO/IEC Standard No. 9075:1992). https://www.iso.org/standard/16668.html
  • Zaharia, M., Chowdhury, M., Franklin, M. J., Shenker, S., & Stoica, I. (2010). Spark: Cluster computing with working sets. In Proceedings of the 2nd USENIX Conference on Hot Topics in Cloud Computing (HotCloud’10) (pp. 1–7). USENIX Association.
★

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

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

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

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