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

كيفية تجميع البيانات بالساعة في Pandas (مع مثال)


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

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

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

1. مقدمة إلى تحليل السلاسل الزمنية وتجميع البيانات الزمنية في Pandas

1.1 أهمية التحليل الزمني للبيانات في بيئة بايثون

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

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

عند إجراء مقارنة منهجية بين التحليل اللحظي (Real-time Point Analysis) والتحليل التجميعي الدوري (Periodic Aggregate Analysis)، نجد أن التحليل اللحظي يعاني غالباً من متلازمة “فرط التشتت”، حيث تتداخل التقلبات الطفيفة التي لا تعبر عن تغير حقيقي في الظاهرة مع الإشارات الحيوية. في المقابل، يعمل التحليل التجميعي الساعي كمرشح تمرير منخفض (Low-pass Filter) يمتص الاضطرابات العابرة ويُبقي على التباينات الهيكلية المنتظمة، مما يقلل من تكلفة التخزين السحابي ويُسرّع من زمن استجابة لوحات التحكم وقواعد البيانات التحليلية الموجهة لاتخاذ القرارات الإدارية العليا.

1.2 مكانة مكتبة Pandas كأداة معيارية لمعالجة السلاسل الزمنية

تعود الريادة المطلقة لمكتبة Pandas في معالجة السلاسل الزمنية إلى أصول نشأتها الأولى؛ حيث صممها Wes McKinney في أروقة الصناديق الاستثمارية الكمية في وول ستريت لتلبية الاحتياجات المالية الصارمة لمعالجة تدفقات الأسعار والبيانات الزمنية المتقطعة. قامت المكتبة بسد فجوة تقنية هائلة في بيئة بايثون عبر توفير هياكل بيانات متقدمة مثل إطار البيانات (DataFrame) والسلاسل الزمنية (Series)، وتكاملت عضوياً مع مكتبة NumPy للاستفادة من المصفوفات الرقمية منخفضة المستوى المكتوبة بلغة C، مما منحها سرعة حسابية فائقة في تنفيذ العمليات المتجهية (Vectorized Operations).

تستمد مكتبة Pandas قوتها من المرونة الهندسية التي توفرها للكائنات الموجهة للوقت؛ فهي لا تتعامل مع الطوابع الزمنية كمجرد نصوص أو أرقام مجردة، بل ككائنات زمنية ذكية واعية بالمناطق الزمنية (Time-zone Aware)، والتقويمات التشغيلية، والإزاحات الزمنية المخصصة (Date Offsets). يتيح هذا التصميم للمطورين إجراء عمليات معقدة مثل التحويل بين النطاقات الجغرافية المختلفة، ومحاذاة السلاسل الزمنية غير المتزامنة، وتطبيق نوافذ التدحرج الزمني (Rolling Windows) بأقل عدد ممكن من الأسطر البرمجية وبأعلى كفاءة استهلاك للذاكرة.

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

1.3 نطاق وهيكل المقال التعليمي

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

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

بنهاية هذا الدليل، سيكون القارئ ملماً بالفروق الدقيقة بين مختلف الأساليب البرمجية مثل df.groupby(df['time'].dt.hour)، وdf.resample('h')، وpd.Grouper(freq='h')، وسيمتلك القدرة على تحديد النهج الأمثل لكل سيناريو عملي بناءً على حجم البيانات، ومتطلبات الأداء، والأهداف التحليلية المرجوة، مدعوماً برؤى تصويرية وتطبيقات عملية متكاملة تدعم اتخاذ القرار المؤسسي.

2. المفاهيم الأساسية لهياكل البيانات الزمنية والتحويل البرمجي

2.1 فهم نوع البيانات التاريخية والوقتية (datetime64)

تعتمد مكتبة Pandas في تمثيلها للبيانات الزمنية على نوع البيانات المعياري datetime64[ns] المشتق من مكتبة NumPy. من الناحية المعمارية، تختلف الكائنات الزمنية الصريحة جذرياً عن السلاسل النصية العادية (Strings) أو الأرقام الصحيحة؛ فالنصوص تُخزن في الذاكرة كسلاسل من المحارف المشفرة (مثل UTF-8) مما يستهلك حجماً كبيراً ويتطلب معالجة برمجية بطيئة عند المقارنة أو الفرز. في المقابل، يُخزن نوع datetime64[ns] الطابع الزمني كقيمة عددية صحيحة واحدة بحجم 64 بت تمثل عدد النانو ثواني المنقضية منذ حقبة يونكس (Unix Epoch) التي بدأت في الأول من يناير عام 1970 عند منتصف الليل بالتوقيت العالمي المنسق (UTC).

يوفر هذا التمثيل الداخلي عالي الدقة على مستوى النانو ثانية إمكانية تغطية نطاق زمني يمتد من عام 1677 إلى عام 2262 ميلادياً، وهو ما يلبي متطلبات التطبيقات العلمية والمالية بدقة متناهية. إن هذا البناء الرياضي الموحد داخل الذاكرة يلغي الحاجة إلى عمليات التحليل النصي المتكررة، ويجعل عمليات المقارنة الزمنية (مثل التحقق مما إذا كان الوقت أ يسبق الوقت ب) مجرد عملية مقارنة حسابية بسيطة وسريعة جداً على مستوى المعالج المركزي (CPU Register Comparison).

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

2.2 استخدام الدالة pd.to_datetime للتحويل القياسي

تُعد الدالة pd.to_datetime الأداة الأساسية والمدخل الإجباري لتحويل الأعمدة النصية وغير المتجانسة إلى كائنات زمنية موحدة ومعيارية داخل مكتبة Pandas. تمتلك هذه الدالة مرونة برمجية فائقة تمكنها من استنتاج التنسيقات الزمنية المختلفة تلقائياً (Format Inference)، مثل التواريخ المكتوبة بصيغة ISO-8601 القياسية، أو التنسيقات الشائعة في الأنظمة المصرفية والطبية مثل “DD/MM/YYYY” أو “MM-DD-YYYY HH:MM:SS”.

على الرغم من قدرة الدالة على التحليل التلقائي، إلا أن أفضل الممارسات الهندسية تقتضي تحديد معامل التنسيق format بشكل صريح ومباشر، لا سيما عند معالجة مجموعات البيانات الضخمة؛ حيث يؤدي توفير التنسيق الثابت (مثل format='%Y-%m-%d %H:%M:%S') إلى إيقاف محاولات التخمين الإحصائي التي تجريها الدالة لكل سجل على حدة، مما يرفع من سرعة التحويل البرمجي بمقدار قد يتجاوز عشرين ضعفاً مقارنة بالتحليل الضمني غير المقيد، فضلاً عن تجنب الأخطاء الكارثية الناتجة عن التفسير الخاطئ لتبادل الأيام والشهور.

تتضمن الدالة أيضاً استراتيجيات محكمة لإدارة الأخطاء وتأويل القيم غير الصالحة أو التالفة التي تنتشر في السجلات الواقعية؛ فباستخدام المعامل errors='coerce'، تقوم الدالة بإجبار أي قيمة غير قابلة للتحويل أو تحتوي على تشوهات نصية على التحول إلى قيمة زمنية فارغة (pd.NaT – Not a Time) بدلاً من توقيف تنفيذ البرنامج وإلقاء استثناء (Exception). يتيح هذا الأسلوب لمهندس البيانات عزل وتنظيف البيانات الفاسدة لاحقاً دون مقاطعة خطوط الأنابيب التحليلية المؤتمتة.

2.3 استخراج الخصائص الزمنية عبر الوصول dt accessor

توفر مكتبة Pandas واجهة برمجية متخصصة تُعرف باسم الموصّل الزمني (.dt accessor)، وهي مساحة أسماء مخصصة تتيح لكافة كائنات السلاسل (Series) ذات النوع الزمني الوصول المباشر إلى مجموعة واسعة من الدوال والسمات الرياضية للوقت والتاريخ دون الحاجة إلى كتابة حلقات تكرار أو استدعاء دوال خارجية. يعمل الموصّل .dt كجسر برمجي يُبرز الخصائص الفردية للطابع الزمني بكفاءة عالية على مستوى المتجهات بأكملها.

تتضمن الواجهة سمات زمنية أساسية تمثل المكونات الهيكلية للتاريخ والوقت، ومن أبرزها السمة dt.hour التي تستخلص رقم الساعة كقيمة عددية صحيحة تتراوح من 0 إلى 23، والسمة dt.day لاستخراج يوم الشهر، والسمة dt.weekday أو dt.dayofweek لتحديد ترتيب يوم الأسبوع، فضلاً عن دوال متقدمة مثل dt.quarter لتحديد الربع السنوي، وdt.is_leap_year للتحقق من كبس السنة. تُعيد هذه العمليات سلاسل رقمية جديدة تتبع نفس الفهرس الأصلي، مما يجعلها جاهزة للاستخدام كمفاتيح تجميعية أو كأعمدة جديدة في إطار البيانات.

من الناحية الأدائية، يُظهر استخدام الموصّل .dt تفوقاً كاسحاً عند مقارنته بحلقات التكرار التقليدية (مثل for loops) أو حتى دوال التطبيق السطري (apply مع تعبيرات lambda). يعود هذا التفوق إلى أن عمليات .dt مكتوبة بلغة C المنخفضة ضمن كود Cython الخاص بمكتبة Pandas، حيث يتم استخراج الأرقام الزمنية مباشرة عبر الإزاحات الحسابية الثابتة من البايتات المخزنة في الذاكرة دفعة واحدة، مما يقلل من العبء التشغيلي لمفسر بايثون (Python Global Interpreter Lock Overhead) ويوفر سرعة تنفيذ فورية حتى مع الجداول التي تتضمن عشرات الملايين من الصفوف.

3. البنية التركيبية الأساسية لعملية التجميع بالساعة باستخدام dt.hour

3.1 تحليل الصيغة البرمجية df.groupby([df[‘time’].dt.hour])

تُعد الصيغة البرمجية df.groupby([df['time'].dt.hour]) التعبير الأكثر مباشرة وشيوعاً لتنفيذ عمليات التجميع بالساعة في بايثون للمبتدئين والمحترفين على حد سواء. لتفكيك هذه الصيغة وفهم ميكانيكيتها الداخلية، يجب استيعاب نموذج “التقسيم والتطبيق والدمج” (Split-Apply-Combine) الذي أرسى قواعده Hadley Wickham، والذي يمثل الفلسفة الحاكمة لدوال التجميع في تحليل البيانات الحديث؛ حيث تبدأ الدالة groupby بتقسيم إطار البيانات الأصلي إلى مجموعات فرعية بناءً على القيم المستخرجة من تعبير التجميع.

عند تمرير df['time'].dt.hour كمحدد للتجميع، تقوم بايثون باستخلاص رقم الساعة المجرد (من 0 إلى 23) لكل صف في العمود الزمني، وتتعامل مع هذه الأرقام كمفاتيح تصنيفية (Grouping Keys). يتم تعيين الصفوف التي تشترك في نفس قيمة الساعة إلى نفس الحاوية (Bucket)، بغض النظر عن التاريخ أو اليوم الذي وقعت فيه تلك الساعة؛ مما يعني أن الساعة الثامنة صباحاً من يوم الأحد والساعة الثامنة صباحاً من يوم الثلاثاء ستندمجان تلقائياً في نفس المجموعة الحسابية.

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

3.2 تطبيق العمليات التجميعية الرياضية على الأعمدة الرقمية

بمجرد تشكيل كائن التجميع GroupBy، تتطلب الخطوة المنهجية التالية تحديد العمود أو الأعمدة الرقمية المستهدفة بالتحليل الإحصائي، وتطبيق الدالة التجميعية المطلوبة لحساب المقاييس الكمية المجمعة. يتم ذلك عبر استخدام معاملات الفهرسة العادية لاختيار العمود المطلوب، مثل كتابة df.groupby(df['time'].dt.hour)['sales'] لعزل عمود المبيعات وتوجيه العمليات الحسابية نحوه حصرياً وتجاهل بقية الأعمدة غير المعنية.

يأتي تطبيق دالة الجمع .sum() كأشهر العمليات التجميعية وأكثرها استخداماً في بيئات الأعمال لحساب الإجمالي التراكمي للقيم المسجلة خلال كل نافذة ساعية. عند استدعاء هذه الدالة، يقوم المحرك الحسابي الداخلي لبانداز بالمرور عبر فهارس كل مجموعة ساعية وتجميع قيم المبيعات المقابلة لها بسرعة فائقة بالاعتماد على خوارزميات الجمع التراكمي المكتوبة بلغة C، مما يضمن أقصى درجات الدقة الرياضية وتفادي أخطاء التقريب الرقمي للنقاط العائمة (Floating Point Precision Issues).

تتمثل مخرجات هذه العملية الحسابية في كائن سلسلة زمنية (Series) ذي بنية مخصصة، حيث يتحول ناتج التجميع إلى عمود رقمي يحمل إجماليات المبيعات، بينما تصبح أرقام الساعات (من 0 إلى 23) هي الفهرس المفتاحي الجديد (Index) للسلسلة. يمكن بعد ذلك إعادة تعيين هذا الفهرس عبر الدالة reset_index() لتحويل النتيجة مرة أخرى إلى إطار بيانات قياسي (DataFrame) مكون من عمودين صريحين: عمود للساعة وعمود للمجموع التراكمي، مما يجعله جاهزاً للتصدير، أو الرسم البياني، أو الدمج مع جداول تشغيلية أخرى.

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

4.1 استيراد الحزم البرمجية وضبط بيئة بايثون

يتطلب الشروع في التحليل العملي تهيئة رصينة لبيئة العمل البرمجية لضمان استقرار العمليات وقابلية تكرار النتائج (Reproducibility). يبدأ الإعداد باستيراد مكتبة Pandas بالاسم المستعار المتعارف عليه عالمياً pd، بالإضافة إلى استدعاء مكتبة NumPy لمعالجة العمليات المصفوفية وتوليد الأرقام العشوائية تحت الاسم المستعار np. يُفضل دائماً التحقق من الإصدارات المثبتة لضمان التوافق مع أحدث الميزات البرمجية المستقرة؛ حيث شهدت إصدارات Pandas 2.0 وما بعدها تحسينات جذرية في دعم الأنواع الزمنية وخيارات الأداء المتقدمة.

تقتضي الممارسات الهندسية الفضلى العمل داخل بيئات افتراضية معزولة (Virtual Environments) باستخدام أدوات مثل venv أو conda، لتجنب تضارب التبعيات البرمجية بين الحزم المختلفة على مستوى نظام التشغيل. علاوة على ذلك، يتيح ضبط خيارات العرض العامة في Pandas تحسين تجربة استكشاف وتحليل البيانات داخل بيئات العمل التفاعلية مثل Jupyter Notebooks؛ حيث يمكن ضبط الحد الأقصى للصفوف المعروضة عبر الأمر pd.set_option('display.max_rows', 50) وضبط تنسيق الأرقام العشرية عبر pd.set_option('display.float_format', lambda x: '%.2f' % x) لضمان قراءة إحصائية واضحة ومريحة للعين.

فيما يلي الأوامر التأسيسية لضبط البيئة واستدعاء المكتبات المطلوبة للبدء في المعالجة التجريبية:

import pandas as pd
import numpy as np
import datetime as dt

4.2 بناء إطار البيانات التجريبي (DataFrame)

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

يتم بناء إطار البيانات النموذجي عبر تمرير قاموس بايثون (Dictionary) يحتوي على التواريخ كقيم نصية غير منسقة لمحاكاة كيفية استلام البيانات الخام من قواعد البيانات أو ملفات سجلات الخادم (Log Files)، بالإضافة إلى القيم الرقمية المقابلة لكل عملية، كما هو موضح في البناء البرمجي التالي:

raw_data = {
    'transaction_time': [
        '2026-03-30 08:15:20',
        '2026-03-30 08:42:10',
        '2026-03-30 08:58:05',
        '2026-03-30 09:12:45',
        '2026-03-30 09:33:12',
        '2026-03-30 11:05:00',
        '2026-03-30 11:45:30',
        '2026-03-30 14:20:15',
        '2026-03-30 14:55:50',
        '2026-03-30 14:59:10'
    ],
    'sales': [120.50, 85.00, 210.25, 45.00, 300.75, 150.00, 95.50, 410.00, 180.20, 65.80]
}

df = pd.DataFrame(raw_data)

يعكس هذا الجدول المصغر نمطاً واقعياً متعدد المستويات؛ حيث نشهد تركزاً لثلاث عمليات خلال الساعة الثامنة صباحاً بقيم متباينة، وعمليتين في الساعة التاسعة، وعمليتين في الساعة الحادية عشرة، وثلاث عمليات في الساعة الثانية عشرة ظهراً (الساعة 14 بتنسيق 24 ساعة)، بينما تغيب المعاملات تماماً في الساعة العاشرة والساعة الثانية عشرة والساعة الثالثة عشرة، مما يوفر بيئة اختبار مثالية لاختبار سلوك دوال التجميع المختلفة في بايثون.

4.3 التحقق المبدئي من صحة وتراكيب البيانات

تقتضي البروتوكولات الصارمة لعلم البيانات إجراء مرحلة فحص استكشافي وتحقق مبدئي من أنواع البيانات وتراكيبها الهيكلية قبل الشروع في أي عمليات تحويل أو تجميع إحصائي. نبدأ بفحص البنية الحالية لأنواع الأعمدة داخل إطار البيانات باستخدام الخاصية df.dtypes؛ حيث يُظهر الفحص الأولي أن عمود transaction_time مُعرّف كنوع object (أي سلسلة نصية مجردة في معايير بايثون)، وهو ما يمنع الوصول المباشر إلى أي خصائص زمنية، في حين يُسجل عمود sales كنوع float64 وهو النوع العددي المناسب للحسابات المالية.

لمعاينة البيانات والتأكد من سلامة الصفوف الأولى، نستدعي الدالة df.head() التي تعرض عينة من السجلات للتأكد من خلو القيم من أي محارف خاصة أو تنسيقات تالفة. بناءً على هذا التحقق الاستكشافي، نقوم فوراً بتنفيذ خطوة المعالجة التصحيحية عبر تحويل عمود الوقت صراحة إلى نوع datetime64[ns] المعياري باستخدام دالة التحويل مع تحديد التنسيق كما توضح التعليمات التالية:

df['transaction_time'] = pd.to_datetime(df['transaction_time'], format='%Y-%m-%d %H:%M:%S')

بإعادة تنفيذ الأمر df.dtypes، نلاحظ تحول نوع العمود بنجاح إلى datetime64[ns]، وبذلك يكون إطار البيانات قد استوفى كافة الشروط الهندسية والمتطلبات التقنية للانتقال إلى مرحلة التجميع الساعي بكفاءة وموثوقية رياضية كاملة.

5. التطبيق العملي للنموذج: التجميع وحساب المجموع بالساعة خطوة بخطوة

5.1 تنفيذ كود التجميع الساعي الكامل

بعد اكتمال مرحلة التأسيس الهيكلي للبيانات وتحويل عمود التوقيت إلى طبيعته الزمنية القياسية، نصل إلى لحظة التنفيذ العملي لتجميع إجمالي المبيعات بالساعة. سنقوم بصياغة استعلام تجميعي يعتمد على دمج الموصّل .dt.hour داخل دالة groupby لتطبيق عملية الجمع التراكمي على عمود المبيعات بدقة متناهية، مع استخدام الأساليب المعيارية لتنظيف وتنسيق الجدول المخرج ليكون جاهزاً للتقارير التنفيذية.

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

hourly_sales = df.groupby(df['transaction_time'].dt.hour)['sales'].sum().reset_index()
hourly_sales.columns = ['Hour', 'Total_Sales']
print(hourly_sales)

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

5.2 تحليل وتفسير النتائج المستخرجة

عند طباعة ومعاينة الجدول الناتج hourly_sales من الكود السابق، نحصل على النتائج التحليلية المفصلة التالية التي تعكس تجميع البيانات بدقة:

  • الساعة 8 (Hour 8): إجمالي المبيعات = 415.75 وحدة نقدية (ناتج جمع: 120.50 + 85.00 + 210.25).
  • الساعة 9 (Hour 9): إجمالي المبيعات = 345.75 وحدة نقدية (ناتج جمع: 45.00 + 300.75).
  • الساعة 11 (Hour 11): إجمالي المبيعات = 245.50 وحدة نقدية (ناتج جمع: 150.00 + 95.50).
  • الساعة 14 (Hour 14): إجمالي المبيعات = 656.00 وحدة نقدية (ناتج جمع: 410.00 + 180.20 + 65.80).

يكشف التحليل الدقيق لهذه المخرجات عن جانب جوهري في سلوك دالة groupby القياسية في بايثون؛ وهو الاختفاء التام للساعات التي لم تشهد أي معاملات مالية (مثل الساعات 0 إلى 7، والساعة 10، والساعة 12، والساعة 13، والساعات من 15 إلى 23). يحدث هذا السلوك لأن groupby هي دالة موجهة للبيانات الفعلية (Data-driven) تقوم بتكوين المجموعات فقط بناءً على القيم الفريدة الموجودة بالفعل في السلسلة المدخلة، ولا تمتلك أي معرفة مسبقة بالطبيعة المستمرة للزمن أو بأن اليوم يحتوي حتماً على 24 ساعة متعاقبة.

للتحقق من السلامة الرياضية للعملية التجميعية (Sanity Check)، نقوم بحساب المجموع الكلي للعمود الأصلي ومقارنته بمجموع العمود المجمع عبر المعادلة البرمجية assert np.isclose(df['sales'].sum(), hourly_sales['Total_Sales'].sum())؛ حيث يبلغ المجموع الإجمالي 1663.00 وحدة نقدية في كلا الحسابين، مما يثبت بما لا يدع مجالاً للشك أن التجميع الساعي قد حافظ على سلامة البيانات المالية دون فقدان أو تكرار غير مقصود للقيم.

6. التجميع بالساعة عبر أيام وتواريخ متعددة: الساعة المجردة مقابل الساعة المحددة بالتاريخ

6.1 إشكالية التجميع بالساعة المجردة عبر تواريخ متباينة

تنشأ إشكالية تحليلية ومنهجية بالغة التعقيد عند استخدام الأسلوب البرمجي البسيط df['time'].dt.hour على مجموعات بيانات تمتد لفترات زمنية طويلة تغطي أسابيع، أو شهوراً، أو سنوات متعددة. تكمن هذه الإشكالية في أن السمة .dt.hour تقوم بتجريد الساعة بالكامل من سياقها التقويمي واليومي، مما يؤدي إلى دمج كافة المعاملات التي وقعت عند الساعة التاسعة صباحاً في الأول من يناير، مع تلك التي وقعت عند التاسعة صباحاً في منتصف يوليو، وتلك التي حدثت في أواخر ديسمبر، واحتسابها جميعاً ككتلة واحدة تمثل “الساعة 9”.

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

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

6.2 التجميع المزدوج بالتاريخ والساعة (Date and Hour Grouping)

للتغلب على إشكالية طمس التواريخ والحفاظ على التمييز الكامل بين الساعات المتشابهة في الأيام المختلفة، نلجأ إلى تقنية التجميع متعدد المستويات (Multi-Level Grouping) من خلال تمرير قائمة تحتوي على مفتاحين تجميعيين متزامنين إلى دالة groupby: المفتاح الأول يمثل التاريخ المجرد df['time'].dt.date، والمفتاح الثاني يمثل رقم الساعة df['time'].dt.hour.

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

multi_hourly = df.groupby([df['transaction_time'].dt.date, df['transaction_time'].dt.hour])['sales'].sum().reset_index()
multi_hourly.columns = ['Date', 'Hour', 'Total_Sales']

تؤدي هذه الصيغة في الأصل إلى تكوين فهرس هرمي متعدد المستويات (MultiIndex) يربط كل تاريخ بالساعات التابعة له منطقياً. وبمجرد استدعاء الدالة reset_index()، يتم تفكيك هذا الفهرس المركب وتحويله إلى جدول مستوٍ (Flat DataFrame) يحتوي على ثلاثة أعمدة واضحة: عمود التاريخ، وعمود الساعة، وعمود المبيعات الإجمالية. يضمن هذا النهج فصل مبيعات الساعة الثامنة ليوم الإثنين عن مبيعات الساعة الثامنة ليوم الثلاثاء بشكل قاطع، مما يتيح تتبع التطور الزمني الحقيقي ساعة بساعة عبر الأيام المتتالية.

6.3 تحويل الطابع الزمني لاقتطاع الدقائق والثواني (dt.floor)

يُعد استخدام الدالة dt.floor أحد أرقى الحلول الهندسية وأكثرها كفاءة في بايثون للتعامل مع التجميع الساعي عبر تواريخ متعددة دون الحاجة إلى تقسيم التاريخ والساعة إلى عمودين منفصلين. تقوم دالة التقريب الأرضي df['time'].dt.floor('h') أو dt.floor('H') باقتطاع وتصفير الدقائق، والثواني، والنانو ثواني من الطابع الزمني، مع إرجاع التوقيت إلى بداية الساعة التي يقع فيها السجل مع الاحتفاظ الكامل بمعلومات السنة والشهر واليوم داخل كائن datetime64 سليم وقائم بذاته.

على سبيل المثال، يتم تحويل الطابع الزمني 2026-03-30 08:42:10 تلقائياً عبر هذه الدالة إلى 2026-03-30 08:00:00. وعند تطبيق التجميع المباشر على هذا العمود المقرب، نحصل على تسلسل زمني موحد ومستمر يحتفظ بالطبيعة الزمنية للكائنات، كما يظهر في التطبيق التالي:

df['hour_window'] = df['transaction_time'].dt.floor('h')
floored_hourly = df.groupby('hour_window')['sales'].sum().reset_index()

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

7. استخدام الدالة resample كبديل منهجي للتجميع الساعي

7.1 الأسس النظرية لإعادة أخذ العينات (Resampling) في Pandas

تُمثل دالة إعادة أخذ العينات resample الركيزة المنهجية الأقوى والأكثر تخصصاً في مكتبة Pandas للتعامل مع السلاسل الزمنية المستمرة؛ فهي ليست مجرد دالة تجميع عامة مثل groupby، بل هي محرك زمني متخصص يفهم مفاهيم التردد الزمني (Frequencies)، والانتظام الزمني، واستمرارية الفترات. تعتمد النظرية الحسابية لإعادة أخذ العينات على مفهومين أساسيين: تقليل التردد (Downsampling) وهو خفض دقة البيانات من مستوى دقيق (مثل الثواني أو الدقائق) إلى مستوى تجميعي أوسع (مثل الساعات أو الأيام)، وتكبير التردد (Upsampling) وهو العكس تماماً بزيادة التردد الزمني وتوليد نقاط بينية جديدة.

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

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

7.2 تطبيق resample(‘h’) للتجميع الساعي

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

resampled_sales = df.set_index('transaction_time').resample('h')['sales'].sum().reset_index()
print(resampled_sales)

توفر دالة resample تحكماً فائق الدقة في كيفية إغلاق وتسمية الفترات الزمنية عبر معاملين بالغي الأهمية:

  • معامل الإغلاق (closed): يحدد ما إذا كانت حدود الفترة الزمنية مغلقة من الطرف الأيسر أو الأيمن؛ حيث يعني closed='left' (وهو الافتراضي في التردد الساعي) أن الفترة [08:00:00 - 09:00:00) تتضمن الدقيقة 08:00 وتستبعد الدقيقة 09:00 تماماً.
  • معامل التسمية (label): يحدد الطابع الزمني الذي سيحمله الصف الناتج؛ حيث يضع الخيار label='left' بداية الفترة (08:00) كتسمية رسمية للساعة، بينما يضع الخيار label='right' نهاية الفترة (09:00) كتسمية للحاوية التجميعية.

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

8. استخدام pd.Grouper للتحكم المتقدم في التجميع الزمني

8.1 مفهوم وتكوين الكائن pd.Grouper

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

يعتمد تكوين كائن pd.Grouper على معاملين رئيسيين: المعامل key الذي يُحدد اسم العمود الزمني المستهدف بالمعالجة، والمعامل freq الذي يحدد التردد الزمني المطلوب تجميع البيانات وفقه (مثل freq='h' للتجميع الساعي القياسي). يتيح هذا التركيب مرونة استثنائية لتجاوز التجميع الساعي البسيط إلى ترددات ساعية مركبة أو غير تقليدية؛ حيث يمكن ببساطة ضبط التردد ليصبح freq='2h' لتجميع البيانات في حاويات زمنية مدتها ساعتان، أو freq='30min' لنصف الساعة، أو حتى freq='1h30min' للتحليلات الزمنية المعقدة.

علاوة على ذلك، يدعم pd.Grouper إمكانية تحديد الإزاحات الزمنية عبر معامل offset (أو origin)، مما يتيح بدء النوافذ الساعية عند دقائق مخصصة (مثلاً البدء عند الدقيقة 15 من كل ساعة بدلاً من الدقيقة 00:00)، وهو أمر بالغ الأهمية عند تحليل نوبات العمل المتداخلة في المصانع والمستشفيات أو جلسات التداول في الأسواق المالية التي تفتتح في أوقات غير متطابقة مع رأس الساعة الصفرية.

8.2 التجميع متعدد الأبعاد باستخدام pd.Grouper

تظهر القوة الحقيقية لكائن pd.Grouper عند الانتقال من التحليل الزمني أحادي البعد إلى التحليل متعدد الأبعاد (Multi-Dimensional Aggregation)، حيث يسمح بدمج الترددات الزمنية بالساعة مع أعمدة تصنيفية وفئوية أخرى (مثل فئات المنتجات، أو فروع المتاجر، أو أنواع العملاء) داخل تعبير تجميعي واحد متناغم لا يمكن تنفيذه باستخدام دالة resample وحدها.

يوضح النموذج البرمجي التالي كيفية تنفيذ تجميع متزامن يجمع بين الساعات وفروع المتاجر:

# إضافة عمود تجريبي يمثل فرع المتجر
df['branch'] = ['Branch_A', 'Branch_A', 'Branch_B', 'Branch_B', 'Branch_A', 'Branch_B', 'Branch_A', 'Branch_B', 'Branch_A', 'Branch_B']
advanced_grouping = df.groupby([pd.Grouper(key='transaction_time', freq='h'), 'branch'])['sales'].sum().reset_index()
print(advanced_grouping)

تتيح هذه الهيكلية البرمجية تحويل المخرجات المجمعة بسهولة بالغة إلى جداول محورية (Pivot Tables) مصفوفية عبر الدالة pivot_table، حيث تمثل الساعات الفهرس الرأسي وتمثل الفروع الأعمدة الأفقية، مما يتيح للمحللين مقارنة أداء الفروع في كل ساعة بصرياً وبشكل فوري.

من منظور كفاءة الذاكرة وسرعة المعالجة، يتميز pd.Grouper بأنه يتجنب إنشاء فهارس مؤقتة في الذاكرة العشوائية أو نسخ أطر البيانات، ويقوم بتنفيذ التجميع متعدد الأبعاد في ممر حسابي موحد (Single Pass) داخل الذاكرة، مما يجعله الخيار الهندسي الأول والأنسب لبناء خطوط إنتاج وتجهيز البيانات الضخمة (Data Pipelines) في منصات ذكاء الأعمال المتقدمة.

9. تطبيق دوال التجميع الإحصائية المتعددة بالساعة

9.1 حساب المقاييس الإحصائية الأساسية بالساعة

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

يأتي حساب المتوسط الحسابي (mean) عبر الدالة df.groupby(df['time'].dt.hour)['sales'].mean() كخطوة أساسية لتحديد القيمة المتوقعة لمتوسط المعاملة الواحدة خلال كل ساعة، مما يسمح بتمييز الساعات التي تجذب عملاء ذوي قدرة شرائية عالية عن الساعات التي تشهد معاملات كثيرة ولكن بقيم مالية صغيرة. بالتوازي مع ذلك، يُستخدم مقياس الوسيط (median) كمؤشر أكثر متانة ومقاومة للقيم الشاذة والمتطرفة.

كما يُعد قياس حجم التكرارات وعدد العمليات المنفذة في كل نافذة ساعية أمراً حيوياً لتخطيط السعة التشغيلية، ويتم حسابه عبر دالتي count (التي تحسب القيم غير الفارغة فقط) وsize (التي تحسب العدد الإجمالي للصفوف بما فيها القيم المفقودة). بالإضافة إلى ذلك، يكتمل التحليل الأساسي باستخراج القيم القصوى (max) والدنيا (min) لتحديد النطاق الديناميكي للبيانات، وحساب الانحراف المعياري (std) والتباين (var) لقياس مدى استقرار أو تقلب النشاط الساعي عبر الزمن، مما يدعم إدارة المخاطر في الأنظمة المالية والتشغيلية.

9.2 استخدام الدالة agg لتطبيق مقاييس مخصصة ومتعددة

تُمثل الدالة التجميعية الشاملة agg (أو aggregate) القوة الضاربة في مكتبة Pandas عندما يتعلق الأمر بتطبيق مصفوفة متكاملة من المقاييس الإحصائية المتباينة دفعة واحدة على عمود واحد أو أعمدة متعددة ضمن نفس عملية التجميع الساعي، مما يوفر الوقت الحسابي ويلغي الحاجة لتكرار عمليات الفرز والتقسيم.

يمكن تمرير قائمة من الدوال المدمجة مباشرة إلى الدالة agg كما هو موضح أدناه:

hourly_summary = df.groupby(df['transaction_time'].dt.hour)['sales'].agg(['count', 'sum', 'mean', 'std', 'max']).reset_index()
hourly_summary.columns = ['Hour', 'Tx_Count', 'Total_Sales', 'Average_Ticket', 'Std_Dev', 'Max_Transaction']

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

كما تدعم الدالة كتابة دوال مخصصة عبر دوال لامبدا (Lambda Functions) لمعالجة الحسابات الإحصائية المتقدمة والمعقدة؛ مثل حساب معامل الاختلاف الساعي (Coefficient of Variation) أو استخراج النسبة المئوية المخصصة للمئين التسعين (90th Percentile) كما يوضح النموذج التالي:

p90_summary = df.groupby(df['transaction_time'].dt.hour)['sales'].agg(
    P90_Sales=lambda x: x.quantile(0.90),
    Interquartile_Range=lambda x: x.quantile(0.75) - x.quantile(0.25)
).reset_index()

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

10. معالجة الفجوات الزمنية والساعات المفقودة وملء البيانات

10.1 تحديد واستكشاف الساعات الخالية من النشاط

تُمثل “الساعات المفقودة” (Missing Hours) أحد أكبر التحديات في خطوط أنابيب هندسة البيانات؛ فعندما نقوم بتجميع ملايين السجلات الصادرة عن حساسات IoT أو متاجر إلكترونية باستخدام groupby، فإن الساعات التي لم يحدث فيها أي إرسال أو بيع تختفي تماماً من الجدول الناتج. إذا تم تغذية هذه السلسلة المتقطعة إلى نماذج تعلم الآلة أو خوارزميات التنبؤ الزمني (مثل الشبكات العصبية المتكررة LSTM أو نماذج Prophet)، فإن النماذج ستفترض خطأً أن الساعات المتعاقبة في الجدول متصلة زمنياً، مما يشوه فهم النموذج للموسمية اليومية ويؤدي إلى انهيار دقة التنبؤات.

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

full_time_index = pd.date_range(start=df['transaction_time'].min().floor('h'), end=df['transaction_time'].max().floor('h'), freq='h')

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

10.2 استراتيجيات ملء القيم المفقودة (Imputation Techniques)

تعتمد الاستراتيجية الهندسية المختارة لمعالجة وملء القيم الفارغة (NaN) الناتجة عن الساعات المفقودة على الطبيعة الفيزيائية والاقتصادية للمتغير المستهدف بالتحليل:

  • الملء بالصفر الثابت (Zero Filling): يُعد هذا الخيار هو المعيار الصحيح رياضياً للمتغيرات التراكمية الحجمية (Volumetric Variables) مثل حجم المبيعات، أو عدد الزيارات، أو كمية الأمطار الساقطة؛ حيث يعني غياب الحدث في تلك الساعة أن القيمة المتحققة هي صفر بالفعل. يتم تطبيق ذلك بسهولة عبر الأمر hourly_series.reindex(full_time_index).fillna(0).
  • أساليب التمرير الأمامي والخلفي (Forward and Backward Fill): تُستخدم هذه التقنية للمتغيرات الحالة أو المتغيرات البيئية المستمرة (Continuous State Variables) مثل درجات الحرارة المسجلة، أو الضغط الجوي، أو أسعار صرف العملات؛ حيث يُعد افتراض بقاء القيمة الأخيرة المسجلة ثابتاً خلال الساعة الفارغة عبر ffill() هو الافتراض الأكثر منطقية وقرباً للواقع الفيزيائي، واستخدام bfill() للملء العكسي في بداية السلسلة.
  • الاستيفاء الخطي المتقدم (Linear and Time Interpolation): يتميز هذا النهج بحساب قيم تقديرية ناعمة للساعات المفقودة بناءً على ميل التغير الرياضي بين النقطة السابقة والنقطة اللاحقة للفجوة عبر استدعاء الدالة interpolate(method='time'). يناسب هذا الأسلوب قراءات حساسات السرعة وتدفق السوائل، حيث يُفترض أن التغير يحدث تدريجياً عبر الزمن وليس على شكل قفزات مفاجئة.

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

# إعداد إطار البيانات المجمع وتجهيز الفهرس الكامل
grouped_data = df.groupby(df['transaction_time'].dt.floor('h'))['sales'].sum()
complete_hourly_series = grouped_data.reindex(full_time_index, fill_value=0).reset_index()
complete_hourly_series.columns = ['Timestamp_Hour', 'Imputed_Sales']

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

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

11.1 إدارة أنواع البيانات وتخفيض البصمة الذاكرية

عند الانتقال من معالجة عينات البيانات الصغيرة إلى التعامل مع مجموعات البيانات الضخمة (Big Data) التي تحتوي على مئات الملايين من الطوابع الزمنية في بيئات الإنتاج الفعلية، تصبح إدارة استهلاك الذاكرة العشوائية (RAM) هي العامل الحاسم بين نجاح خط الأنابيب البرمجي أو انهياره التام بخطأ نفاد الذاكرة (Out-Of-Memory OOM Error). تخصص مكتبة Pandas افتراضياً أنواع بيانات واسعة الحجم مثل int64 وfloat64 وobject، مما يؤدي إلى هدر ذاكري كبير يمكن تقليصه هندسياً بنسبة قد تتجاوز 75%.

تتمثل الخطوة الأولى للتحسين في تقليص أحجام الأنواع الرقمية (Downcasting)؛ فعلى سبيل المثال، إذا كانت أرقام الساعات تتراوح بين 0 و23 فقط، فإن تخزينها في نوع int64 (الذي يستهلك 8 بايت لكل قيمة) يُعد هدراً صريحاً، ويمكن تحويلها فورياً إلى نوع int8 أو uint8 (الذي يستهلك بايتاً واحداً فقط) عبر الأمر df['hour'] = df['hour'].astype('uint8') دون أي فقدان للدقة الرقمية. وبالمثل، يمكن خفض دقة الأعمدة الرقمية العشرية غير الحساسة من float64 إلى float32.

فيما يخص الأعمدة النصية والتصنيفية المصاحبة لعملية التجميع (مثل فئات المنتجات أو أسماء المدن)، يجب الامتناع تماماً عن تركها كنوع object، وتحويلها صراحة إلى النوع الفئوي (category). يقوم هذا النوع ببناء جدول ترميز رقمي داخلي يربط النصوص المكررة بأرقام صحيحة صغيرة، مما يقلص حجم استهلاك الذاكرة بشكل دراماتيكي ويُسرّع من سرعة عمليات التجميع والتصفية بمقدار الضعف. يمكن التحقق من فعالية هذه التحسينات وفحص البصمة الذاكرية الحقيقية عبر استدعاء الأمر الدقيق df.memory_usage(deep=True).sum() قبل وبعد عملية التحسين.

11.2 تسريع العمليات الحسابية وتقنيات المعالجة المتوازية

لتحقيق أقصى درجات التسريع البرمجي في معالجة التجميع الساعي، يجب الالتزام الصارم بتجنب استخدام الدوال المخصصة البطيئة عبر apply داخل عمليات groupby، والاعتماد المطلق على الدوال التجميعية المدمجة الموجهة (Vectorized Cythonized Functions) مثل sum()، وmean()، وcount() التي تنفذ حلقاتها الحسابية مباشرة داخل لغة C دون التفاعل مع مفسر بايثون البطيء.

علاوة على ذلك، تبرز أهمية تقنيات القراءة الانتقائية للبيانات (Selective Column Loading)؛ فعند قراءة ملفات ضخمة من صيغ التخزين العمودية الحديثة مثل Apache Parquet، يجب استخدام معامل columns لتحميل العمود الزمني والأعمدة الرقمية المعنية بالتجميع فقط وتجاهل بقية الأعمدة غير الضرورية، مما يقلل من عمليات الإدخال والإخراج للقرص الصلب (Disk I/O Bottlenecks) ويُبقي استهلاك الذاكرة في حدوده الدنيا.

عندما يتجاوز حجم البيانات سعة الذاكرة العشوائية لجهاز واحد، يتطلب التحول المعماري الانتقال إلى محركات المعالجة المتوازية؛ حيث يمكن استخدام مكتبة Dask التي توفر واجهة برمجية مطابقة تماماً لمكتبة Pandas ولكنها تقوم بتوزيع البيانات على كتل مصفوفية مجزأة ومعالجتها بالتوازي عبر كافة أنوية المعالج المركزي، أو التحول نحو مكتبة Polars المكتوبة بلغة Rust والتي تتميز بنموذج تنفيذ كسول فائق السرعة (Lazy Execution Engine) يعتمد على استغلال التعليمات البرمجية للمتجهات (SIMD Instructions)، مما يتيح معالجة تجميع مليارات السجلات الساعية في أجزاء من الثانية.

12. التصور البياني لمخرجات التجميع بالساعة ودراسة حالة متكاملة

12.1 رسم المخططات البيانية لتوزيع النشاط الساعي

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

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

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

يوضح الكود التالي كيفية إنشاء رسم بياني شريطي أنيق باستخدام مكتبة Matplotlib مباشرة من مخرجات التجميع الساعي:

import matplotlib.pyplot as plt
# إعداد الرسم البياني
plt.figure(figsize=(10, 5))
plt.bar(hourly_sales['Hour'], hourly_sales['Total_Sales'], color='#1f77b4', edgecolor='black', alpha=0.8)
plt.title('Hourly Total Sales Distribution', fontsize=14, fontweight='bold')
plt.xlabel('Hour of Day (24-Hour Format)', fontsize=12)
plt.ylabel('Total Sales ($)', fontsize=12)
plt.xticks(range(0, 24))
plt.grid(axis='y', linestyle='--', alpha=0.7)
plt.tight_layout()
# plt.show()

12.2 بناء سيناريو تحليلي متكامل من البيانات الخام إلى القرار

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

يوضح البرنامج النصي الشامل التالي دورة المعالجة الكاملة من البداية وحتى استخراج المؤشرات النهائية:

# 1. محاكاة توليد تدفق بيانات واقعي لمدة 48 ساعة
np.random.seed(42)
date_range_sim = pd.date_range(start='2026-04-01 00:00:00', end='2026-04-02 23:59:59', freq='3min')
simulated_data = pd.DataFrame({
    'timestamp': np.random.choice(date_range_sim, size=500),
    'order_value': np.random.gamma(shape=2, scale=50, size=500).round(2),
    'payment_status': np.random.choice(['Completed', 'Failed', 'Refunded'], size=500, p=[0.85, 0.10, 0.05])
})

# 2. خط أنابيب المعالجة والتنظيف والتجميع الساعي
# تصفية المعاملات الناجحة فقط
clean_orders = simulated_data[simulated_data['payment_status'] == 'Completed'].copy()
clean_orders['timestamp'] = pd.to_datetime(clean_orders['timestamp'])

# التجميع الساعي المنهجي المعتمد على التقريب الأرضي
hourly_metrics = clean_orders.groupby(clean_orders['timestamp'].dt.floor('h')).agg(
    Hourly_Revenue=('order_value', 'sum'),
    Order_Count=('order_value', 'count'),
    Average_Order_Value=('order_value', 'mean')
)

# 3. صيانة الاستمرارية وملء الفجوات الزمنية لـ 48 ساعة كاملة
full_timeline = pd.date_range(start='2026-04-01 00:00:00', end='2026-04-02 23:00:00', freq='h')
final_hourly_dashboard = hourly_metrics.reindex(full_timeline).fillna({
    'Hourly_Revenue': 0.0,
    'Order_Count': 0,
    'Average_Order_Value': 0.0
}).reset_index()

final_hourly_dashboard.rename(columns={'index': 'Hour_Timeline'}, inplace=True)

# 4. استخراج الرؤى التحليلية لصناع القرار
peak_hour_revenue = final_hourly_dashboard.loc[final_hourly_dashboard['Hourly_Revenue'].idxmax()]
total_48h_revenue = final_hourly_dashboard['Hourly_Revenue'].sum()
print(f"Total Revenue: ${total_48h_revenue:,.2f}")
print(f"Peak Operational Hour: {peak_hour_revenue['Hour_Timeline']} with Sales: ${peak_hour_revenue['Hourly_Revenue']:,.2f}")

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

خاتمة وخلاصة منهجية

استعرضنا في هذا الدليل الشامل الأبعاد النظرية والتطبيقية لعملية تجميع البيانات بالساعة باستخدام مكتبة Pandas في بايثون، موضحين كيف تتكامل هياكل البيانات الزمنية والتحويلات البرمجية لتقديم رؤى دقيقة من السجلات اللحظية المتدفقة. إن إتقان أساليب التجميع المختلفة—سواء عبر الواجهة السريعة dt.hour، أو الأسلوب المتخصص resample('h')، أو الأداة متعددة الأبعاد pd.Grouper—يُمكّن مهندسي ومحللي البيانات من اختيار الأداة المناسبة وفقاً لطبيعة المسألة وحجم الذاكرة المتاحة ومستوى استمرارية السلسلة الزمنية المطلوبة.

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

References

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

looti, M. (2026, سبتمبر 2). كيفية تجميع البيانات بالساعة في Pandas (مع مثال). عرب سايكلوجي. https://arabpsychology.com/how-to-group-data-by-hour-in-pandas-with-example/
looti, Mohammed. “كيفية تجميع البيانات بالساعة في Pandas (مع مثال).” عرب سايكلوجي, 2 سبتمبر 2026, https://arabpsychology.com/how-to-group-data-by-hour-in-pandas-with-example/.
looti, Mohammed. “كيفية تجميع البيانات بالساعة في Pandas (مع مثال).” عرب سايكلوجي. سبتمبر 2, 2026. https://arabpsychology.com/how-to-group-data-by-hour-in-pandas-with-example/.