برمجة بايثون, تحليل البيانات, علم البيانات

كيفية حساب الحد الأقصى المتحرك في بانداز (مع أمثلة)


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

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

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

1. مقدمة شاملة حول مفهوم الحد الأقصى المتحرك (Rolling Maximum) في تحليل البيانات

1.1 التعريف الرياضي والإحصائي للحد الأقصى المتحرك

يُعرف الحد الأقصى المتحرك (Rolling Maximum أو Moving Maximum) رياضياً بأنه تطبيق لمشغل القيمة العظمى (Supremum / Maximum Operator) على نافذة منزلقة ذات حجم محدد تتنقل عبر متتالية رقمية مرتبة زمنياً أو فهرسياً. إذا افترضنا وجود سلسلة زمنية متقطعة تمثل بمتجه رياضي $X = (x_1, x_2, dots, x_n)$، فإن الحد الأقصى المتحرك عند النقطة الزمنية $t$ باستخدام نافذة ذات حجم $k$ (حيث $k le t$) يُعرف بالمعادلة الرياضية التالية:
$$RM_t(k) = \max({x_{t-k+1}, x_{t-k+2}, dots, x_t})$$
يعكس هذا التعريف أن القيمة المحسوبة عند كل نقطة زمنية تعتمد حصراً على مجموعة جزئية موضعية من البيانات السابقة مباشرة أو المتمركزة حول النقطة، مما يمنح المقياس استجابة ديناميكية للتغيرات اللحظية بدلاً من الجمود الذي تتسم به المقاييس الثابتة.

يكمن الفارق الجوهري بين الحد الأقصى الكلي (Global Maximum) والحد الأقصى المتحرك في نطاق المعاينة وفضاء البحث الحسابي. فالحد الأقصى الكلي هو مقياس أحادي النتيجة يمسح كامل فضاء السلسلة الزمنية $X$ ليعيد القيمة $max(X)$ كقيمة مطلقة غير متغيرة زمنياً، مما يجعله غير حساس للتغيرات البنيوية أو التدهور التدريجي في أداء النظام عبر الزمن. في المقابل، يولد الحد الأقصى المتحرك سلسلة زمنية جديدة $Y = (y_1, y_2, dots, y_n)$ تماثل السلسلة الأصلية في الطول، حيث تعبر كل نقطة فيها عن الذروة التي تم الوصول إليها خلال الإطار الزمني المنصرم حديثاً، وهو ما يتيح مراقبة ديناميكية القمم وتغير مستويات السقف عبر الزمن.

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

1.2 أهمية حساب القيم القصوى في السلاسل الزمنية وهندسة الميزات

في مجال تعلم الآلة وهندسة الميزات للنماذج التنبؤية، يمثل الحد الأقصى المتحرك ميزة زمنية فائقة القيمة (Lagged / Rolling Feature). عند تدريب نماذج التنبؤ بالانحدار أو التصنيف على السلاسل الزمنية، فإن تزويد النموذج بالقيم القصوى لفترات سابقة (مثل 7 أيام، 30 يوماً، أو 90 يوماً) يمنحه سياقاً ظرفياً حول أقصى ضغط تحمله النظام أو أعلى نقطة سعرية تم اختبارها مؤخراً. تساهم هذه الميزات في تحسين قدرة خوارزميات التعلم الآلي مثل XGBoost و LightGBM والشبكات العصبية المتكررة على استشعار اقتراب المتغير من مستويات حرجة أو تشبع سعري أو استهلاك غير اعتيادي للموارد.

في التحليلات المالية والتداول الكمي، يشكل الحد الأقصى المتحرك حجر الزاوية في بناء المؤشرات الفنية المتقدمة. يُستخدم هذا المؤشر لتحديد “مستويات المقاومة” (Resistance Levels) التاريخية والنفسية، حيث يفشل السعر في تجاوز القمة المسجلة خلال النافذة السابقة. علاوة على ذلك، تستند استراتيجيات كسر القمم (Breakout Strategies) وقنوات التداول الشهيرة، مثل قنوات دونشيان (Donchian Channels)، كلياً على حساب الحد الأقصى لأعلى سعر والحد الأدنى لأدنى سعر خلال نافذة زمنية معينة لاكتشاف بدايات الاتجاهات الانفجارية في أسواق الأسهم والعملات الرقمية.

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

1.3 نظرة عامة على أدوات مكتبة بانداز (Pandas) المخصصة للحسابات المتحركة

توفر مكتبة بانداز بنية تحتية متكاملة وفائقة المرونة للتعامل مع المتتاليات الرقمية عبر كائنات السلاسل (Series) وجداول البيانات (DataFrame). صُممت هذه الكائنات لتستوعب العمليات المتجهة (Vectorized Operations) التي تُنفذ على مستوى لغات منخفضة المستوى مثل C و Cython، مما يمنحها سرعة استثنائية مقارنة بالحلقات التكرارية التقليدية في لغة بايثون القياسية. تتكامل هذه البنية بسلاسة مع هياكل مصفوفات NumPy، مما يجعل العمليات الحسابية على النوافذ المتدحرجة غاية في الفعالية وقابلة للتوسع لمعالجة ملايين السجلات.

تتيح بانداز ثلاثة مسارات وظيفية رئيسية لحساب القيم القصوى، يلبي كل منها غرضاً تحليلياً محدداً:

  • الدالة cummax() (Cumulative Maximum): تحسب الحد الأقصى التراكمي منذ النقطة الأولى في السلسلة وحتى اللحظة الحالية، وتعتبر عملية ذات نافذة متوسعة تبدأ دائماً من الفهرس الصفري وتتوسع مع كل صف جديد.
  • التابع rolling(window).max(): يمثل الحد الأقصى المتحرك الحقيقي ذو النافذة الثابتة أو المحددة زمنياً، حيث تنزلق النافذة مع حركة البيانات متخلصةً من القيم القديمة التي تخرج عن نطاق النافذة.
  • التابع expanding().max(): يقدم واجهة برمجية مرنة وموسعة للعمليات التراكمية، مع إمكانية التحكم في الحد الأدنى لعدد الفترات المقبولة وتطبيق دوال مخصصة على السلسلة المتنامية.

من منظور كفاءة الحوسبة واستهلاك الموارد، تختلف هذه الخوارزميات في تعقيدها الزمني والمكاني. تنفذ دالة cummax() عملياتها بتعقيد زمني خطي بحت $O(N)$ وتعقيد مكاني إضافي مقداره $O(1)$ لأنها تتطلب فقط متغير ذاكرة وحيد للاحتفاظ بأعلى قيمة تاريخية. في المقابل، تستخدم دالة rolling().max() خوارزميات متقدمة تعتمد على طوابير الانتظار ذات النهايتين الرتيبة (Monotonic Deque)، مما يتيح لها العمل أيضاً بتعقيد زمني مقارب لـ $O(N)$ بدلاً من التعقيد الساذج $O(N \times k)$، وهو ما يوفر حماية فائقة للذاكرة وسرعة تنفيذ مذهلة في معالجة البيانات الضخمة.

2. البنية التحتية لمكتبة بانداز وآلية معالجة النوافذ الحسابية

2.1 فهم كائنات النافذة (Window Objects) في بانداز

عند استدعاء التابع df.rolling() أو df.expanding() في بانداز، لا تقوم المكتبة بحساب القيم فوراً، بل تقوم بإنشاء وتجهيز كائن وسيط يُعرف بكائن النافذة من النوع pandas.core.window.rolling.Rolling. هذا الكائن يمثل تجريداً برمجياً يحمل كافة المعلمات الرياضية والهندسية للنافذة المطلوبة، مثل حجم النافذة (window)، ونوع التمركز (center)، والحد الأدنى للفترات المطلوبة (min_periods)، ونوع المحور المستهدف (axis). يعمل هذا الكائن كحاوية معلقة تطبق التقييم الكسول (Lazy Evaluation) ولا تُجري العمليات الرياضية الفعلية إلا عند استدعاء دالة تجميعية مثل max() أو mean() أو sum().

تتم إدارة حركة النوافذ عبر نوعين رئيسيين من الفهارس: الفهارس المعتمدة على الأعداد الصحيحة (Integer-based Indexing) حيث تُعامل كل خطوة كعدد ثابت من الصفوف بغض النظر عن المسافات الزمنية الحقيقية بينها، والفهارس الزمنية (DatetimeIndex) حيث تعتمد النافذة على الفروق الزمنية الفعلية المقاسة بالساعات أو الأيام. يعتمد محرك الحساب الداخلي على خوارزميات متخصصة لتتبع الفهرس بدقة، وتحديد أطراف النافذة اليسرى واليمنى (Left and Right Boundaries) بدقة متناهية قبل تمرير مصفوفات البيانات إلى شفرات C المترجمة مسبقاً.

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

2.2 الفرق بين العمليات التراكمية (Cumulative) والعمليات المتحركة (Rolling)

يكمن الاختلاف المنهجي بين العمليات التراكمية والعمليات المتحركة في كيفية إدارة “ذاكرة” المؤشر الإحصائي. العمليات التراكمية، الممثلة بدالة cummax()، تمتلك ذاكرة أبدية غير قابلة للنسيان ضمن السلسلة؛ إذ بمجرد تسجيل قيمة قياسية عليا عند الفهرس $t_0$، ستظل هذه القيمة هي المخرَج المهيمن لكافة الفهارس اللاحقة $t > t_0$ ما لم تظهر قيمة جديدة تتفوق عليها وتكسر الرقم القياسي السابق. هذا السلوك يجعل المنحنى التراكمي دالة رتيبة غير متناقصة (Monotonically Non-Decreasing Function) تتخذ شكلاً درجياً صاعداً.

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

يعتمد الاختيار بين المنهجين على السؤال التحليلي المطروح:

  • استخدم العمليات التراكمية: عندما ترغب في الإجابة عن: “ما هي أعلى نقطة تاريخية حققها المتغير منذ بداية التسجيل وحتى الآن؟” (مثل حساب أعلى سعر تاريخي All-Time High لتحديد عمق التراجع الاستثماري).
  • استخدم العمليات المتحركة: عندما ترغب في الإجابة عن: “ما هي أقصى قيمة واجهها النظام خلال آخر 30 يوماً؟” (مثل تقييم الضغط التشغيلي الحالي ومستويات الطلب الحديثة لإدارة المخزون قصير الأجل).

2.3 إعداد بيئة العمل والبيانات التجريبية للاختبار

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

نبدأ باستيراد المكتبات الضرورية وتوليد إطار بيانات (DataFrame) تركيبي يمثل حركة المبيعات اليومية لمتجر تجاري عبر سلسلة زمنية تتضمن قمم متكررة وتراجعات سعرية واضحة، كما يوضح الكود التالي:

import pandas as pd
import numpy as np

# ضبط إعدادات التوليد العشوائي للتكرارية
np.random.seed(42)

# توليد سلسلة تواريخ يومية لمدة 12 يوماً
dates = pd.date_range(start='2023-01-01', periods=12, freq='D')

# قيم مبيعات تركيبية تحاكي التذبذب والقمم
sales_values = [150, 220, 180, 310, 290, 400, 380, 250, 420, 390, 410, 450]

# بناء إطار البيانات
df = pd.DataFrame({'Date': dates, 'Sales': sales_values})
df = df.sort_values('Date').reset_index(drop=True)

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

3. الطريقة الأولى: حساب الحد الأقصى التراكمي باستخدام دالة cummax()

3.1 الصياغة البرمجية (Syntax) والمعلمات الأساسية لدالة cummax()

تعتبر الدالة cummax() إحدى الدوال الأساسية والمدمجة بكفاءة عالية داخل كائنات السلاسل الزمنية (pd.Series) وجداول البيانات (pd.DataFrame). تتميز هذه الدالة بصياغتها البرمجية المباشرة التي تتيح استدعاءها دون الحاجة إلى تحديد وسائط معقدة، حيث تأتي بالصيغة القياسية: Series.cummax(axis=None, skipna=True, *args, **kwargs). عند تطبيقها على مستوى جدول بيانات كامل، يمكن تحديد المحور axis=0 لمعالجة الأعمدة رأسياً عبر الصفوف، أو axis=1 لحساب الحد الأقصى التراكمي أفقياً عبر الأعمدة لكل صف على حدة.

يتحكم المعامل skipna في الكيفية التي تتعامل بها الدالة مع القيم المفقودة (NaN). القيمة الافتراضية لهذا المعامل هي skipna=True، والتي تعني تجاهل القيم المفقودة تماماً ومواصلة تمرير أعلى قيمة حقيقية سابقة عبر السلسلة دون انقطاع. أما إذا تم ضبط المعامل على skipna=False، فإن مواجهة أول قيمة مفقودة ستؤدي إلى تحويل كافة المخرجات اللاحقة في السلسلة التراكمية إلى NaN، نظراً لعدم إمكانية الجزم رياضياً بما إذا كانت القيمة المفقودة أكبر من القمة السابقة أم لا دون افتراضات مسبقة.

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

3.2 مثال تطبيقي تفصيلي خطوة بخطوة على بيانات المبيعات اليومية

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

# حساب الحد الأقصى التراكمي للمبيعات
df['CumMax_Sales'] = df['Sales'].cummax()

# عرض النتائج لمطالعة سلوك الدالة
print(df[['Date', 'Sales', 'CumMax_Sales']])

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

التاريخ (Date) المبيعات اليومية (Sales) الحد الأقصى التراكمي (CumMax_Sales) الحالة التحليلية
2023-01-01 150 150 نقطة البداية الأساسية
2023-01-02 220 220 قمة قياسية جديدة تم كسرها
2023-01-03 180 220 ثبات القمة (المبيعات أقل من السقف)
2023-01-04 310 310 قمة قياسية جديدة تم كسرها
2023-01-05 290 310 ثبات القمة السابقة
2023-01-06 400 400 قمة قياسية جديدة تم كسرها
2023-01-07 380 400 ثبات القمة السابقة
2023-01-08 250 400 ثبات القمة السابقة
2023-01-09 420 420 قمة قياسية جديدة تم كسرها
2023-01-10 390 420 ثبات القمة السابقة
2023-01-11 410 420 ثبات القمة السابقة
2023-01-12 450 450 قمة قياسية جديدة ونهاية السلسلة

3.3 تفسير المخرجات الرياضية وتتبع تطور القيمة العظمى

عند تفحص النتائج في الجدول أعلاه، نلاحظ السلوك الرياضي المميز للدالة التراكمية. في اليوم الأول (2023-01-01)، كانت المبيعات 150، وبالتالي أصبحت هي القمة التراكمية الابتدائية. في اليوم الثاني، ارتفعت المبيعات إلى 220، ولأن $220 > 150$، تم تحديث الحد الأقصى فوراً ليصبح 220. أما في اليوم الثالث، فقد تراجعت المبيعات إلى 180؛ ولأن $180 < 220$، احتفظت الدالة بالقمة السابقة (220) دون تغيير.

تتكرر هذه الآلية بوضوح في الأيام من 2023-01-06 إلى 2023-01-08. ففي يوم 6 يناير، تم تسجيل قمة قوية بلغت 400 وحدة، تلاها انخفاض في يوم 7 يناير (380 وحدة) وهبوط حاد في يوم 8 يناير (250 وحدة). خلال هذين اليومين من التراجع، ظل العمود CumMax_Sales ثابتاً تماماً عند القيمة 400. يوضح هذا الاستقرار أن مؤشر cummax() لا يكترث بحجم الهبوط أو مدته، بل يوثق فقط السقف المرجعي الذي وصل إليه النشاط الاقتصادي في تاريخه السابق.

من الناحية الهندسية والتحليلية، يُعد الفرق بين السلسلة الأصلية Sales وسلسلة CumMax_Sales مؤشراً كمياً بالغ الأهمية. فكلما اتسعت الفجوة السلبية بين المنحنيين ($Sales_t – CumMax_t < 0$)، دل ذلك على عمق الانكماش أو الابتعاد عن الذروة التشغيلية، بينما تشير النقاط التي يتطابق فيها المنحنيان ($Sales_t = CumMax_t$) إلى لحظات تحقيق أرقام قياسية تاريخية وانطلاقة لمستويات أداء غير مسبوقة.

4. الطريقة الثانية: حساب الحد الأقصى التراكمي المقسم حسب المجموعات (Grouped cummax)

4.1 دمج دالة groupby() مع دالة cummax() للتقسيم الفئوي

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

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

تأتي الصيغة البرمجية الموصى بها لتنفيذ هذه العملية كالتالي:
df['Group_CumMax'] = df.groupby('Category_Column')['Target_Column'].cummax()
تضمن هذه الصيغة المتجهة أعلى درجات الأداء الحاسوبي، حيث تُنفذ عمليات التقسيم والمسح التراكمي عبر خوارزميات C المدمجة، مما يتيح معالجة ملايين المجموعات والصفوف بأجزاء من الثانية.

4.2 بناء سيناريو عملي لبيانات متعددة المجموعات

لتطبيق هذا المفهوم عملياً، سنقوم ببناء إطار بيانات يحاكي مبيعات ثلاثة متاجر مختلفة (Store A, Store B, Store C) على مدار 5 أيام متتالية. سنقوم بتتبع الحد الأقصى التراكمي لمبيعات كل متجر على حدة لمراقبة تطور أداء المتاجر وسلوك نموها الداخلي بشكل منفصل تماماً، كما هو موضح في الكود البرمجي التالي:

# إنشاء إطار بيانات لمتاجر متعددة
store_data = {
    'Date': pd.date_range(start='2023-01-01', periods=5).tolist() * 3,
    'Store': ['Store A'] * 5 + ['Store B'] * 5 + ['Store C'] * 5,
    'Sales': [100, 150, 130, 200, 180, # مبيعات المتجر A
              300, 280, 320, 310, 350, # مبيعات المتجر B
              50, 80, 70, 60, 90] # مبيعات المتجر C
}

df_stores = pd.DataFrame(store_data)
# ترتيب البيانات زمنياً وفئوياً لضمان دقة المعالجة
df_stores = df_stores.sort_values(by=['Store', 'Date']).reset_index(drop=True)

# حساب الحد الأقصى التراكمي لكل متجر على حدة
df_stores['Store_CumMax'] = df_stores.groupby('Store')['Sales'].cummax()

print(df_stores)

يعرض الجدول التالي تفاصيل المخرجات الناتجة عن عملية التجميع، حيث نلاحظ كيف تم عزل العمليات الرياضية لكل متجر بدقة بالغة:

المتجر (Store) التاريخ (Date) المبيعات (Sales) الحد الأقصى التراكمي للمتجر (Store_CumMax) ملاحظات الأداء الفئوي
Store A 2023-01-01 100 100 بداية السلسلة للمتجر A
Store A 2023-01-02 150 150 قمة جديدة للمتجر A
Store A 2023-01-03 130 150 ثبات القمة للمتجر A
Store A 2023-01-04 200 200 قمة جديدة للمتجر A
Store A 2023-01-05 180 200 ثبات القمة للمتجر A
Store B 2023-01-01 300 300 إعادة ضبط وبداية مستقلة للمتجر B
Store B 2023-01-02 280 300 ثبات القمة للمتجر B
Store B 2023-01-03 320 320 قمة جديدة للمتجر B
Store B 2023-01-04 310 320 ثبات القمة للمتجر B
Store B 2023-01-05 350 350 قمة جديدة للمتجر B
Store C 2023-01-01 50 50 إعادة ضبط وبداية مستقلة للمتجر C
Store C 2023-01-02 80 80 قمة جديدة للمتجر C
Store C 2023-01-03 70 80 ثبات القمة للمتجر C
Store C 2023-01-04 60 80 ثبات القمة للمتجر C
Store C 2023-01-05 90 90 قمة جديدة للمتجر C

4.3 التعامل مع التجميع متعدد المستويات (Multi-level Grouping)

في الهياكل التنظيمية والمؤسسية الأكثر تعقيداً، قد يتطلب التحليل تجميع البيانات عبر مستويات هرمية متعددة، مثل تجميع المبيعات حسب “المنطقة الجغرافية” (Region)، ثم حسب “الفرع” (Store)، ثم حسب “فئة المنتج” (Product_Category). تدعم بانداز التجميع المتعدد بسلاسة من خلال تمرير قائمة من أسماء الأعمدة إلى دالة groupby(['Region', 'Store', 'Category']).

لضمان عودة المخرجات بنفس أبعاد وشكل إطار البيانات الأصلي دون حدوث تشوهات في الفهرس، يُفضل استخدام التابع transform('cummax') أو استدعاء الدالة المباشرة. يقوم التابع transform بتطبيق الدالة التجميعية على كل مجموعة فرعية ثم يعيد محاذاة النتائج (Alignment) وتوزيعها على الصفوف الأصلية بدقة، مما يسهل عمليات الدمج والمقارنة الفورية في مسارات معالجة البيانات دون الحاجة لإعادة تعيين الفهارس المعقدة (Index Resetting).

# مثال التجميع متعدد المستويات باستخدام transform
df['Hierarchical_CumMax'] = df.groupby(['Region', 'Store_Type'])['Sales'].transform('cummax')

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

5. الطريقة الثالثة: حساب الحد الأقصى ضمن نافذة متحركة محددة (Rolling Window Max)

5.1 استخدام الدالة df.rolling(window).max() وتحديد حجم النافذة

تُعد دالة df.rolling(window).max() الأداة الأساسية والأكثر استخداماً لحساب الحد الأقصى المتحرك الحقيقي ذو النافذة الثابتة. يحدد المعامل window عدد الصفوف أو الملاحظات المتتالية التي تشملها النافذة الحسابية في كل خطوة انزلاقية. عند تمرير عدد صحيح $k$ للنافذة، تقوم الخوارزمية بحساب القيمة القصوى للمصفوفة الفرعية المكونة من الصف الحالي والصفوف $k-1$ السابقة له.

نظراً لأن الصفوف الأولى في بداية السلسلة الزمنية لا تمتلك عدداً كافياً من الملاحظات السابقة يغطي حجم النافذة $k$ بالكامل، فإن السلوك الافتراضي لباندز هو إرجاع القيمة المفقودة NaN لتلك الصفوف الأولية (تحديداً لأول $k-1$ صف). يُعد هذا السلوك منطقياً إحصائياً لمنع الانحياز الناتج عن عدم اكتمال حجم العينة المستهدفة.

ولكن، إذا كانت المتطلبات التحليلية تستلزم الحصول على مخرجات حسابية منذ اليوم الأول دون إهدار الصفوف المبدئية، يمكن ضبط المعلمة min_periods. تحدد هذه المعلمة الحد الأدنى لعدد الملاحظات غير المفقودة اللازمة لإجراء الحساب. فعلى سبيل المثال، إذا تم ضبط window=5 و min_periods=1، فإن الدالة ستقوم بحساب الحد الأقصى للملاحظات المتاحة حتى وإن كان عددها صفاً واحداً فقط في البداية، وتتوسع النافذة تدريجياً حتى تصل إلى حجمها الكامل (5 صفوف) عند الصف الخامس وما بعده، كما يتضح من الصياغة البرمجية التالية:

# حساب الحد الأقصى بنافذة حجمها 3 مع السماح بالحساب من أول نقطة
df['Rolling_Max_W3'] = df['Sales'].rolling(window=3, min_periods=1).max()

5.2 مقارنة عملية بين نافذة بحجم 3 أيام ونافذة بحجم 7 أيام

يؤثر حجم النافذة تأثيراً جذرياً ومباشراً على حساسية المؤشر وسرعة استجابته للتقلبات. النوافذ الصغيرة (مثل 3 أيام) تتسم بالحساسية العالية والملاحقة اللصيقة لحركة البيانات الأصلية، حيث تعكس القمم اللحظية والتراجعات السريعة فور حدوثها. في المقابل، توفر النوافذ الأكبر (مثل 7 أيام أو 30 يوماً) مستوى أعلى من التمهيد الإحصائي (Smoothing)، حيث تحتفظ بالقمم لفترات أطول وتقلل من تأثير التذبذبات اليومية العشوائية.

لتوضيح هذه الفروق، سنقوم بتطبيق نافذتين متحركتين بحجمين مختلفين (3 أيام و 7 أيام) على نفس سلسلة المبيعات السابقة، مع ضبط min_periods=1 لتوحيد المقارنة، كما هو موضح في الكود التالي:

# حساب نافذة 3 أيام ونافذة 7 أيام
df['Roll_Max_3D'] = df['Sales'].rolling(window=3, min_periods=1).max()
df['Roll_Max_7D'] = df['Sales'].rolling(window=7, min_periods=1).max()

print(df[['Date', 'Sales', 'Roll_Max_3D', 'Roll_Max_7D']])

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

التاريخ (Date) المبيعات الفعلية (Sales) الحد الأقصى (نافذة 3 أيام) الحد الأقصى (نافذة 7 أيام) التحليل المقارن للسلوك
2023-01-01 150 150 150 تطابق البدايات مع min_periods=1
2023-01-02 220 220 220 استجابة متزامنة لارتفاع المبيعات
2023-01-03 180 220 220 احتفاظ كلتا النافذتين بقمة 220
2023-01-04 310 310 310 كسر قمة جديدة مشتركة
2023-01-05 290 310 310 ثبات القمة في النافذتين
2023-01-06 400 400 400 تسجيل ذروة جديدة (400)
2023-01-07 380 400 400 ثبات القمة المشتركة
2023-01-08 250 400 400 نافذة 3 أيام تشمل (400, 380, 250)
2023-01-09 420 420 420 ذروة قياسية جديدة تمسح القمم السابقة
2023-01-10 390 420 420 ثبات القمة الحديثة في النافذتين
2023-01-11 410 420 420 ثبات القمة المشتركة
2023-01-12 450 450 450 نهاية السلسلة بقمة صاعدة جديدة

5.3 التحكم في موضع النافذة باستخدام المعلمة center

بشكل افتراضي، تطبق دالة rolling() ما يُعرف بـ النوافذ المتأخرة (Trailing / Right-aligned Windows)، حيث يُنسب ناتج الحساب الإحصائي إلى الطرف الأيمن (نهاية) النافذة. هذا يعني أن القيمة المحسوبة عند الصف $t$ تعتمد فقط على الماضي والحاضر $[t-k+1, t]$ دون النظر إلى المستقبل، وهو النمط الحسابي الإلزامي في التحليلات اللحظية والنماذج التنبؤية لتفادي مشكلة تسرب البيانات المستقبلية (Look-ahead Bias / Data Leakage).

ومع ذلك، في التحليلات الاسترجاعية البعدية (Post-hoc / Historical Analysis) ومعالجة الإشارات، يكون من المرغوب فيه أحياناً تطبيق النوافذ المتمركزة (Centered Windows) من خلال ضبط المعلمة center=True. في هذه الحالة، يتم وضع النقطة الحسابية في منتصف النافذة تماماً؛ بحيث تتوزع الملاحظات بالتساوي بين الماضي والمستقبل $[t – lfloor k/2 rfloor, t + lfloor k/2 rfloor]$. يساهم هذا التمركز في القضاء التام على التأخر الزمني (Phase Shift / Lag) للمؤشر، مما يجعل القمم المتحركة تتطابق بصرياً وهندسياً مع القمم الفعلية للإشارة الأصلية.

# تطبيق نافذة متمركزة بحجم 3 أيام
df['Centered_Max_W3'] = df['Sales'].rolling(window=3, center=True).max()

يجدر التأكيد رياضياً على أن استخدام center=True في التنبؤات اللحظية أو استراتيجيات التداول الآلي الحية يُعد خطأ منهجياً فادحاً، لأنه يعتمد على معرفة قيم مستقبلية لم تكن متوفرة في النقطة الزمنية $t$، مما يؤدي إلى نتائج اختبارية وهمية وتفوق غير حقيقي للنماذج يفشل تماماً عند النشر الفعلي في بيئات الإنتاج.

6. تطبيق الحد الأقصى المتحرك على السلاسل الزمنية المعتمدة على التواريخ

6.1 استخدام فهارس التواريخ (DatetimeIndex) وتحديد النوافذ الزمنية

في العديد من التطبيقات العملية المعقدة، تأتي البيانات الزمنية بترددات غير منتظمة (Irregular Time Series)؛ نتيجة وجود عطلات نهاية الأسبوع، أو التوقفات التشغيلية، أو تسجيل الأحداث فقط عند وقوعها (Event-driven Data). في هذه الحالات، يفشل استخدام النوافذ المعتمدة على عدد الصفوف (مثل window=7) في تمثيل الأسبوع الحقيقي، لأن 7 صفوف قد تغطي 10 أيام أو أسبوعين إذا كانت هناك أيام مفقودة في التسجيل.

لحل هذه المشكلة جذرياً، تدعم بانداز استخدام سلاسل الإزاحة الزمنية (Time Offsets) كمعلمة للنافذة (مثل '3D' لثلاثة أيام، أو '12h' لاثنتي عشرة ساعة، أو '1W' لأسبوع كامل). لكي يعمل هذا النمط الزمني، تشترط بانداز أن يكون فهرس إطار البيانات من النوع الزمني DatetimeIndex وأن تكون البيانات مرتبة تصاعدياً بدقة متناهية، كما يوضح المثال التالي:

# ضبط التاريخ كفهرس زمني رسمي
df_time = df.set_index('Date').sort_index()

# حساب الحد الأقصى المتحرك لنافذة زمنية حقيقية مدتها 3 أيام
df_time['Time_Roll_Max_3D'] = df_time['Sales'].rolling('3D').max()

print(df_time[['Sales', 'Time_Roll_Max_3D']])

6.2 الفروق الجوهرية بين النوافذ المعتمدة على الصفوف والمعتمدة على الوقت

يكمن الفارق الجوهري بين النوافذ العددية (Row-based) والنوافذ الزمنية (Time-based) في ديناميكية احتساب الملاحظات داخل فضاء النافذة. فالنافذة العددية تثبت عدد الصفوف بصرف النظر عن الفارق الزمني الفعلي بينها، مما قد يدمج أحداثاً متباعدة زمنياً داخل نفس النافذة إذا كانت السلسلة تحتوي على فجوات تاريخية واسعة.

في المقابل، تقوم النوافذ الزمنية بتوسيع وتضييق عدد الصفوف المقبولة ديناميكياً في كل خطوة، بحيث تضمن فقط شمول البيانات التي تقع فعلياً ضمن النطاق الزمني المحدد $[t – \text{offset}, t]$. إذا احتوت فترة 3 أيام على صف واحد فقط، ستقوم النافذة بحساب القيمة القصوى لهذا الصف المنفرد. وإذا احتوت فترة أخرى على 20 صفاً متقارباً (كبيانات التداول عالية التردد في ساعات الذروة)، ستشمل النافذة كافة الصفوف العشرين معاً، مما يضمن اتساقاً فيزيائياً وزمنياً دقيقاً للحسابات الإحصائية عبر كامل السلسلة.

يوضح الجدول المقارن التالي أبرز الفروق المنهجية بين النموذجين:

وجه المقارنة النوافذ المعتمدة على الصفوف (Row-based) النوافذ المعتمدة على الوقت (Time-based)
طبيعة المعلمة window عدد صحيح يمثل الصفوف (مثل window=5) سلسلة إزاحة زمنية (مثل window='5D')
اشتراط نوع الفهرس يقبل الفهارس الرقمية أو الافتراضية يشترط فهرساً زمنياً مرتباً DatetimeIndex
التعامل مع الفجوات الزمنية يتجاهل الفجوات ويدمج صفوفاً متباعدة يحترم الفجوات الزمنية بدقة فيزيائية
عدد القيم المشمولة ثابت تماماً عند كل صف متغير وديناميكي بحسب كثافة الأحداث الزمنية
أفضل مجالات الاستخدام البيانات المعيارية المنتظمة والمصفوفات الرياضية البيانات المالية الحية، وسجلات الخوادم، والإنترنت الصناعي

6.3 إعادة التشكيل الزمني (Resampling) المسبق قبل حساب الحد الأقصى

في بعض البنى المعمارية لهندسة البيانات، يكون من الأفضل توحيد التردد الزمني للسلسلة بالكامل وسد الفجوات المفقودة قبل الشروع في حساب النوافذ المتحركة. يتحقق ذلك عبر استخدام التابع resample()، الذي يتيح تحويل السلسلة إلى شبكة زمنية منتظمة (Regular Time Grid).

يمكن للمحلل دمج إعادة التشكيل مع استراتيجيات ملء الفجوات المختلفة، مثل الملء التقدمي (Forward Fill عبر ffill()) لتكرار آخر قيمة مسجلة، أو الاستيفاء الرياضي (Interpolation)، ثم تطبيق rolling().max() على السلسلة المنتظمة، كما يوضح المثال التالي:

# إعادة التشكيل إلى تردد يومي منتظم وملء الأيام الشاغرة بآخر قيمة
df_regular = df_time.resample('D').ffill()

# حساب الحد الأقصى المتحرك على السلسلة المنتظمة بدقة هندسية تامة
df_regular['Smooth_Max_7D'] = df_regular['Sales'].rolling(7).max()

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

7. التعامل مع القيم المفقودة (Missing Values) والبيانات الشاذة (Outliers)

7.1 سلوك دوال الحد الأقصى المتحرك مع قيم NaN

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

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

أما في الدالة التراكمية cummax()، يحدد المعامل skipna=True (الافتراضي) استمرار نقل القمة التاريخية دون أي انقطاع حتى لو واجهت السلسلة فترات طويلة من البيانات المفقودة، بينما يؤدي ضبط skipna=False إلى إيقاف التراكم وتوليد قيم NaN بصورة دائمة بمجرد ظهور أول نقطة مفقودة.

7.2 استراتيجيات تنظيف البيانات ومعالجة القيم المتطرفة قبل الحساب

تتسم دوال القيم القصوى (Maximum Operators) بحساسيتها المفرطة والقصوى تجاه القيم الشاذة والمتطرفة (Outliers) الناتجة عن أخطاء القراءة أو مشاكل الإدخال. إذا تسربت قيمة شاذة موجبة ضخمة وغير واقعية إلى السلسلة، فإنها ستؤدي إلى “تسميم” (Poisoning) مؤشر الحد الأقصى، حيث ستستحوذ على القمة وتجبر المؤشر على البقاء عند مستوى زائف لفترة طويلة (طوال فترة النافذة في rolling، أو إلى الأبد في cummax).

لحماية المؤشرات من هذا التشويه، يُوصى بتطبيق استراتيجيات المعالجة المسبقة التالية:

  • التقطيع والقص الإحصائي (Clipping): استخدام التابع df['Sales'].clip(lower=min_val, upper=max_val) لحصر القيم ضمن حدود معيارية منطقية تتناسب مع الطاقة الاستيعابية للنظام.
  • تصفية النقاط الشاذة بناءً على الدرجة المعيارية (Z-score) أو المدى الربيعي (IQR): عزل القيم التي تتجاوز 3 انحرافات معيارية واستبدالها بقيم مفقودة أو استيفائها خطياً قبل تمريرها للنافذة المتحركة.
  • استخدام النوافذ الوسيطة المتحركة (Rolling Median Filter): لتنعيم البيانات والتخلص من الارتفاعات الفجائية المؤقتة الناتجة عن ضوضاء القياس، مع الحفاظ على القمم الحقيقية المستدامة.

7.3 التعامل مع القيم الصفرية والقيم السالبة في النماذج الإحصائية

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

عندما تكون كافة القيم داخل النافذة المتحركة سالبة (مثلاً: $[-10, -5, -12]$)، فإن التابع rolling().max() سيعيد القيمة $-5$ بشكل صحيح ودقيق تماماً، لأن $-5$ هي القيمة الأكبر رياضياً والأقرب إلى الصفر. ومع ذلك، يجب على المحلل الانتباه عند دمج هذه المخرجات في عمليات لاحقة مثل القسمة أو حساب النسب المئوية للتراجع؛ حيث يتطلب وجود الأصفار والقيم السالبة تعديل خطوط الأساس (Baselines) لمنع أخطاء القسمة على الصفر أو الانحرافات التفسيرية في نماذج التقييم المالي.

8. طرق الحساب المتقدمة: النوافذ المتوسعة (Expanding) والوزنية (Weighted Windows)

8.1 استخدام كائن Expanding لحساب الحد الأقصى المتوسع

يقدم كائن expanding() في بانداز واجهة برمجية متطورة للتعامل مع النوافذ التراكمية ذات البداية الثابتة والنهاية المتنامية. في حين تؤدي الدالة المباشرة df['col'].cummax() الغرض بسرعة وسهولة، يوفر التابع df['col'].expanding(min_periods=n).max() مرونة إضافية تتيح دمج معايير تحكم دقيقة في الحد الأدنى للفترات اللازمة لبدء الحساب وتوحيد المعمارية البرمجية مع باقي دوال التجميع.

تتطابق مخرجات expanding(min_periods=1).max() رياضياً وسلوكياً بنسبة 100% مع مخرجات cummax(). يتميز كائن expanding() بكونه يتيح التبديل السلس بين الدوال الإحصائية المختلفة (مثل الانتقال من .max() إلى .quantile() أو .std()) دون الحاجة لتغيير هيكل الكود البرمجي، مما يجعله الخيار المفضل في أطر عمل التحليل الكمي المعيارية، كما يتضح من المثال التالي:

# حساب الحد الأقصى المتوسع باستخدام كائن expanding
df['Expanding_Max'] = df['Sales'].expanding(min_periods=2).max()

8.2 النوافذ ذات الأوزان المخصصة والحد الأقصى المرجح

من الناحية الرياضية البحتة، لا يمكن تطبيق الأوزان الخطية الكلاسيكية مباشرة على مشغل القيمة القصوى $max()$ بنفس الطريقة المتبعة في المتوسطات المتحركة المرجحة؛ لأن القيمة القصوى هي مشغل رتبي غير خطي (Order Statistic) يختار قيمة مفردة من العينة بدلاً من دمج عناصرها بعمليات جمع خطية. ومع ذلك، يظهر في العديد من التطبيقات الهندسية والمالية مفهوم الحد الأقصى المخفف زمنياً (Decayed / Time-weighted Maximum).

في هذا النموذج، يتم إعطاء القمم القديمة وزناً يتناقص تدريجياً مع مرور الوقت عبر تطبيق معامل تلاشي أسي (Exponential Decay Factor $lambda$). إذا سجل النظام قمة ضخمة قبل 20 يوماً، فإن تأثير هذه القمة يضمحل تدريجياً ليفسح المجال للقمم الأحدث حتى لو كانت أقل منها بالقيمة المطلقة. يمكن بناء هذا السلوك المتقدم برمجياً في بايثون عبر دمج النوافذ المتحركة مع مصفوفات الأوزان أو عبر صياغة معادلات تكرارية مخصصة تعتمد على دالة التلاشي الأسي.

8.3 تطبيق دوال مخصصة عبر rolling().apply()

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

لضمان الحصول على أعلى أداء ممكن وتفادي الأعباء الزائدة لكائنات السلاسل الزمنية، يُنصح دائماً بتمرير المعلمة raw=True داخل دالة apply(). يؤدي ذلك إلى تمرير البيانات كـ مصفوفة NumPy أحادية البعد مباشرة إلى الدالة المخصصة، مما يزيد من سرعة التنفيذ الحسابي بما يصل إلى 5-10 أضعاف مقارنة بتمرير كائنات السلاسل الافتراضية، كما يوضح الكود التالي:

# تعريف دالة مخصصة لحساب الفرق بين الحد الأقصى والوسيط داخل النافذة
def peak_to_median_spread(window_arr):
    return np.max(window_arr) - np.median(window_arr)

# تطبيق الدالة المخصصة بكفاءة باستخدام raw=True
df['Peak_Spread'] = df['Sales'].rolling(window=5, min_periods=3).apply(peak_to_median_spread, raw=True)

9. التحسين البرمجي وإدارة الذاكرة والأداء في مجموعات البيانات الضخمة

9.1 تحسين أنواع البيانات (Data Types Optimization)

عند التعامل مع مجموعات بيانات ضخمة تحتوي على عشرات الملايين من الصفوف، تصبح إدارة استهلاك الذاكرة (Memory Footprint) وسرعة استرجاع البيانات من ذاكرة التخزين المؤقت للمعالج (CPU Cache Locality) عاملين حاسمين لنجاح النظام. بشكل افتراضي، تقوم مكتبة بانداز بتعيين أنواع البيانات ذات الدقة العالية مثل int64 للأعداد الصحيحة و float64 للأعداد العشرية، مما يستهلك 8 بايت لكل قيمة مفردة في كل عمود.

من خلال التحويل المتعمد للأنواع إلى دقة أصغر متوافقة مع نطاق القيم الفعلية للبيانات—مثل التحويل إلى int32 (4 بايت)، أو int16 (2 بايت)، أو float32 (4 بايت)—يمكن خفض استهلاك الذاكرة بمقدار النصف أو أكثر فوراً. علاوة على ذلك، يُعد تحويل الأعمدة الفئوية النصية المستخدمة في عمليات groupby إلى النوع الفئوي المخصص category خطوة تسريع جوهرية؛ إذ تقلل من زمن المقارنات النصية وتحولها إلى مسح لمعاملات عددية فائقة السرعة، كما يتضح من الكود التالي:

# قياس استهلاك الذاكرة قبل التحسين
initial_mem = df.memory_usage(deep=True).sum()

# تحسين أنواع البيانات بدقة هندسية
df['Sales'] = df['Sales'].astype(np.int32)
if 'Store' in df.columns:
    df['Store'] = df['Store'].astype('category')

# قياس استهلاك الذاكرة بعد التحسين
optimized_mem = df.memory_usage(deep=True).sum()
print(f"تم تخفيض استهلاك الذاكرة بنسبة: {(1 - optimized_mem/initial_mem)*100:.2f}%")

9.2 تسريع الحسابات باستخدام مكتبة Numba ومحرك النوافذ

في الإصدارات الحديثة من مكتبة بانداز، تم إدخال ميزة معمارية ثورية تتيح تفعيل محرك Numba للترجمة الآنية (Just-In-Time Compilation – JIT) داخل دوال النوافذ المتحركة وتطبيقات apply() عبر تمرير المعلمة engine='numba'. يقوم محرك Numba بترجمة شفرات بايثون والدوال الرياضية مباشرة إلى لغة الآلة الثنائية فائقة السرعة وتجاوز مفسر بايثون وقفل المترجم العام (GIL – Global Interpreter Lock).

يوفر هذا المحرك تسريعاً مذهلاً عند معالجة الدوال المخصصة على سلاسل البيانات المليونية، حيث يقترب زمن التنفيذ من سرعة لغات الأنظمة مثل C و Fortran. كما يدعم التوزيع المتوازي للحسابات على الأنوية المتعددة للمعالج عبر تفعيل خيارات التوازي في Numba (engine_kwargs={'parallel': True})، مما يجعل بانداز قادرة على منافسة المحركات الحوسبية المخصصة دون مغادرة بيئة بايثون المرنة.

# تطبيق دالة مخصصة فائقة السرعة عبر محرك Numba
df['Fast_Custom_Max'] = df['Sales'].rolling(window=100).apply(
    lambda x: np.max(x),
    engine='numba',
    raw=True
)

9.3 المعالجة المتوازية للبيانات باستخدام Dask و Polars كبدائل داعمة

عندما يتجاوز حجم البيانات سعة الذاكرة العشوائية (RAM) للجهاز، أو عند الحاجة لمعالجة مليارات السجلات في خطوط الإنتاج الضخمة، تصبح الأدوات التكميلية والموازية ضرورة حتمية. تبرز مكتبة Dask كأداة توسع ممتازة لبانداز، حيث تقوم بتقسيم إطار البيانات إلى أجزاء متعددة (Partitions) وتوزيع عمليات النوافذ المتحركة rolling().max() عبر كافة أنوية المعالج أو عبر عنقود من الخوادم الموزعة (Cluster Computing) بكفاءة تامة.

من جانب آخر، تُعد مكتبة Polars المبنية بلغة Rust البديل الأحدث والأسرع لمعالجة جداول البيانات. تعتمد Polars على معمارية Apache Arrow والتنفيذ المتوازي متكامل النوى وخوارزميات التقييم الكسول الأمثل (Lazy Execution)، مما يجعل حساب rolling_max فيها أسرع بعدة أضعاف مقارنة ببانداز مع استهلاك ذاكرة منخفض للغاية، مما يوفر خياراً استراتيجياً لبناء خطوط معالجة بيانات فائقة الأداء عند الحاجة.

10. مقارنة منهجية وتحليلية بين الطرق المختلفة لحساب الحد الأقصى

10.1 مصفوفة مقارنة شاملة للوظائف والتعقيد الحسابي

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

المعيار التقني الحد الأقصى التراكمي (cummax) الحد الأقصى المتوسع (expanding.max) الحد الأقصى المتحرك (rolling.max)
التعقيد الزمني (Time Complexity) خطي بحت $O(N)$ خطي بحت $O(N)$ خطي مقارب $O(N)$ عبر طابور رتيب
التعقيد المكاني (Space Complexity) $O(1)$ ذاكرة إضافية $O(1)$ ذاكرة إضافية $O(k)$ حيث $k$ حجم النافذة
طبيعة السلوك الرياضي دالة رتيبة غير متناقصة أبداً دالة رتيبة غير متناقصة أبداً دالة متذبذبة صاعدة وهابطة بديناميكية
المرونة وتخصيص المعلمات منخفضة (خيارات أساسية) عالية (دعم min_periods ودوال مخصصة) فائقة (نوافذ زمنية، تمركز، min_periods)
الاستجابة لانهيار الأداء معدومة (يحتفظ بالقمة للأبد) معدومة (يحتفظ بالقمة للأبد) فائقة (تسقط القمم القديمة بانتهاء النافذة)
حالات الاستخدام المثالية أعلى سعر تاريخي، التراجع الأقصى التحليلات المتنامية، الأطر القياسية المؤشرات الفنية، مراقبة الأحمال اللحظية

10.2 مقارنة الاستجابة للبيانات المتغيرة وديناميكية النوافذ

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

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

10.3 التوافقية مع مسارات عمل هندسة البيانات (Data Pipelines)

في بيئات الإنتاج الفعلية وهندسة البرمجيات المعيارية، يُفضل دائماً بناء أنابيب معالجة متسلسلة ونظيفة تضمن سهولة الاختبار والصيانة وإعادة الاستخدام. تدعم مكتبة بانداز هذه المنهجية بقوة من خلال استخدام التابع pd.DataFrame.pipe()، الذي يتيح تغليف عمليات حساب الحد الأقصى المتحرك والتراكمي داخل دوال نقية (Pure Functions) يتم تمريرها بسلاسة عبر خط الأنابيب، كما يتضح من الهيكل البرمجي التالي:

# صياغة دالة نقية لإضافة ميزات الحد الأقصى المتحرك
def add_rolling_features(df, col_name, windows=[3, 7]):
    df = df.copy()
    for w in windows:
        df[f'{col_name}_roll_max_{w}d'] = df[col_name].rolling(w, min_periods=1).max()
    df[f'{col_name}_cummax'] = df[col_name].cummax()
    return df

# تطبيق الدالة بسلاسة داخل خط معالجة البيانات
processed_df = (df
    .sort_values('Date')
    .pipe(add_rolling_features, col_name='Sales', windows=[3, 7])
)

تضمن هذه المعمارية المعيارية وضوح الكود، وتقليل الاعتماديات الجانبية (Side Effects)، وتسهيل كتابة اختبارات الوحدة البرمجية لكل مرحلة من مراحل التحويل الرياضي للبيانات.

11. تطبيقات ودراسات حالة عملية متقدمة في مجالات متعددة

11.1 دراسة حالة 1: التحليل المالي وحساب أقصى تراجع (Maximum Drawdown)

في التحليل الكمي وإدارة المحافظ الاستثمارية، يُعد مقياس أقصى تراجع (Maximum Drawdown – MDD) المعيار الذهبي لتقييم المخاطر الاستثمارية وقوة استراتيجيات التداول. يقيس هذا المؤشر أقصى نسبة مئوية للهبوط تكبدتها المحفظة من أعلى قمة تاريخية سجلتها وحتى أدنى قاع تلاها قبل الوصول إلى قمة جديدة. يعتمد هذا الحساب الرياضي كلياً على دالة cummax() لتحديد القمة المرجعية المتطورة عبر الزمن.

تُعرف معادلة التراجع النسبي (Drawdown Series) عند النقطة الزمنية $t$ بالصيغة التالية:
$$\text{Drawdown}_t = \frac{\text{Price}_t – \text{CumMax}_t}{\text{CumMax}_t}$$
ويتم استخراج أقصى تراجع تاريخي من خلال إيجاد الحد الأدنى لهذه السلسلة ($\text{MDD} = \min(\text{Drawdown})$). يوضح المثال البرمجي التالي كيفية تطبيق هذا التحليل المالي المعقد في بانداز بدقة وسرعة فائقة:

# سلسلة أسعار إغلاق افتراضية لأحد الأصول المالية
prices = [100, 105, 110, 108, 95, 90, 102, 115, 120, 110, 85, 95]
df_finance = pd.DataFrame({'Close': prices})

# 1. حساب القمة التراكمية التاريخية
df_finance['Peak'] = df_finance['Close'].cummax()

# 2. حساب سلسلة التراجع النسبي
df_finance['Drawdown'] = (df_finance['Close'] - df_finance['Peak']) / df_finance['Peak']

# 3. استخراج أقصى تراجع تاريخي مسجل
max_drawdown = df_finance['Drawdown'].min()
print(f"أقصى تراجع تاريخي للمحفظة (MDD): {max_drawdown * 100:.2f}%")

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

11.2 دراسة حالة 2: إدارة المخزون اللوجستي والتنبؤ بالطلب الأقصى

في هندسة سلاسل الإمداد وإدارة المستودعات اللوجستية، تواجه الشركات تحدياً موازناً بين تقليل تكاليف التخزين وتفادي نفاد المخزون (Stockouts) أثناء فترات الذروة المفاجئة. لحل هذه المعادلة، تعتمد نماذج تحديد “مخزون الأمان” (Safety Stock) على حساب أقصى معدل استهلاك يومي أو أسبوعي تم تسجيله خلال النوافذ التشغيلية المتعاقبة وضربه في الفترات الزمنية اللازمة للتوريد (Lead Time).

يوفر حساب الحد الأقصى المتحرك للطلب rolling(window='14D').max() لمدراء سلاسل الإمداد مؤشراً ديناميكياً يعكس الارتفاعات الموسمية والأحداث الترويجية الطارئة. يتم ضبط مستويات إعادة الطلب (Reorder Points) آلياً بناءً على هذه الذروات المتحركة، بحيث يُرفع مخزون الأمان تلقائياً عند استشعار اقتراب مواسم التسوق الكبرى، ويُخفض تدريجياً في الفترات الهادئة لتفادي تجميد السيولة في بضائع راكدة.

11.3 دراسة حالة 3: مراقبة أداء الخوادم وشبكات تكنولوجيا المعلومات

في هندسة موثوقية المواقع (Site Reliability Engineering – SRE) ومراقبة البنى التحتية السحابية، تُقاس مؤشرات استهلاك المعالج (CPU Utilization) وحجم استهلاك الذاكرة وتدفق حزم الشبكة بالثواني والدقائق. نادراً ما تعطي المتوسطات الحسابية صورة حقيقية عن المشاكل التقنية؛ إذ يمكن لمتوسط استهلاك المعالج أن يبدو مستقراً عند 40%، بينما يخفي في طياته قفزات ذروة لحظية (CPU Spikes) تصل إلى 100% وتتسبب في إسقاط اتصالات المستخدمين وتوقف التطبيقات.

من خلال تطبيق الحد الأقصى المتحرك بنافذة 5 دقائق على سجلات المراقبة (Telemetry Metrics)، يمكن لفرق العمليات بناء لوحات مراقبة لحظية تستشعر وصول أحمال النظام إلى مستويات حرجة. تتيح هذه المؤشرات إطلاق آليات التوسع التلقائي (Auto-scaling) وإضافة خوادم افتراضية جديدة فوراً بمجرد أن يتجاوز الحد الأقصى المتحرك عتبة الأمان المحددة، مما يضمن استمرارية تقديم الخدمات الرقمية دون أي انقطاع.

12. أفضل الممارسات، وتجنب الأخطاء الشائعة، ودليل استكشاف الأخطاء

12.1 الأخطاء الشائعة أثناء التنفيذ البرمجي وكيفية تصحيحها

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

  • إغفال ترتيب البيانات قبل الحساب: يُعد تطبيق دوال النوافذ على بيانات غير مرتبة زمنياً أو فهرسياً الخطأ الأكثر شيوعاً وتدميراً لدقة النتائج. الحل الإلزامي هو استخدام df.sort_values(by='Date') بشكل صريح قبل إنشاء كائنات النوافذ.
  • الفهارس غير الفريدة وتكرار القيم: عند تطبيق عمليات التجميع والتحويل transform('cummax') على فهارس تحتوي على مفاتيح مكررة غير فريدة، قد تصدر بانداز تحذيرات أو تعيد مصفوفات مشوهة الأبعاد. يُنصح دائماً بإعادة تعيين الفهرس reset_index(drop=True) لضمان سلامة المحاذاة الداخلية.
  • تسرب البيانات عبر المجموعات (Data Bleeding): يحدث هذا الخطأ الفادح عندما ينسى المبرمج استخدام groupby ويطبق cummax() مباشرة على إطار بيانات مدمج لعدة عملاء أو فروع، مما يؤدي إلى تسرب قيم فرع إلى آخر وإفساد التحليل بالكامل.

12.2 التحقق من صحة النتائج والاختبارات الآلية (Unit Testing)

في أطر العمل المؤسسية ومسارات التطوير المستمر (CI/CD)، لا يكفي فحص الكود بصرياً، بل يجب كتابة اختبارات وحدة آلية للتحقق الصارم من دقة الدوال المخصصة وتطابق المخرجات مع النتائج المتوقعة. توفر بانداز وحدة اختبارات مدمجة ومتقدمة pandas.testing تحتوي على دوال فحص مخصصة مثل assert_series_equal و assert_frame_equal.

تتيح هذه الدوال مقارنة السلاسل الناتجة بالسلاسل المعيارية مع مراعاة الفروق الدقيقة في تقريب الأعداد العشرية، والتحقق من حالات الحافة الشديدة (Edge Cases) كالسلاسل الفارغة، أو النوافذ التي تحتوي على قيم متطابقة كلياً، أو البيانات المليئة بالقيم المفقودة، كما يوضح المثال التالي:

import pandas.testing as pdt

# بناء اختبار وحدة للتأكد من سلوك النافذة المتحركة
def test_rolling_max():
    input_s = pd.Series([10, 20, 15, 30])
    expected = pd.Series([10.0, 20.0, 20.0, 30.0])
    result = input_s.rolling(window=2, min_periods=1).max()
    pdt.assert_series_equal(result, expected)

test_rolling_max()
print("تم اجتياز الاختبار الآلي بنجاح تام!")

12.3 إرشادات نهائية لكتابة كود بايثون نظيف ومعياري (PEP 8)

لضمان سهولة قراءة الكود البرمجي وصيانته من قبل فرق العمل المشتركة، يُوصى بالالتزام الصارم بمعايير دليل أسلوب بايثون (PEP 8) وأفضل الممارسات البرمجية التالية:

  • التسمية الوصفية الواضحة للأعمدة الناتجة: تجنب استخدام أسماء مبهمة مثل max_val، واستخدم أسماء تعبر بدقة عن نوع العملية وحجم النافذة المستخدمة مثل sales_roll_max_7d أو temp_cummax_by_station.
  • التوثيق الداخلي للافتراضات الإحصائية: كتابة تعليقات برمجية دقيقة توضح سبب اختيار نافذة معينة، وتوثق كيفية معالجة القيم المفقودة (مثل توضيح سبب ضبط min_periods=1).
  • الاعتماد الحصري على العمليات المتجهة (Vectorization): الابتعاد التام والنهائي عن استخدام الحلقات التكرارية for loops أو التابع البطيء iterrows() لحساب النوافذ المتحركة، والاستفادة الكاملة من الدوال المدمجة المحسنة في C لضمان أعلى مستويات الأداء.

خاتمة

استعرض هذا الدليل الشامل الأبعاد النظرية، والرياضية، والهندسية لحساب الحد الأقصى المتحرك والتراكمي في مكتبة بانداز. من خلال تفكيك آليات عمل الدوال الأساسية cummax() و rolling().max() و expanding().max()، اتضح أن اختيار الأداة المناسبة يرتبط ارتباطاً وثيقاً بطبيعة السؤال التحليلي وهيكل البيانات الزمني. تضمن المعالجة الدقيقة للنوافذ الزمنية، والتعامل الحذر مع القيم الشاذة والمفقودة، واستغلال المحركات الحوسبية المتقدمة مثل Numba و Dask، بناء خطوط معالجة بيانات تتسم بالدقة الرياضية، والسرعة الفائقة، وقابلية التوسع للتعامل مع أضخم مجموعات البيانات في مختلف القطاعات الاقتصادية والتكنولوجية.

References

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

looti, M. (2026, سبتمبر 2). كيفية حساب الحد الأقصى المتحرك في بانداز (مع أمثلة). عرب سايكلوجي. https://arabpsychology.com/calculate-rolling-maximum-pandas-examples/
looti, Mohammed. “كيفية حساب الحد الأقصى المتحرك في بانداز (مع أمثلة).” عرب سايكلوجي, 2 سبتمبر 2026, https://arabpsychology.com/calculate-rolling-maximum-pandas-examples/.
looti, Mohammed. “كيفية حساب الحد الأقصى المتحرك في بانداز (مع أمثلة).” عرب سايكلوجي. سبتمبر 2, 2026. https://arabpsychology.com/calculate-rolling-maximum-pandas-examples/.