Pandas: مشروع: السلاسل الزمنية

آخر تحديث: 2026-08-26

لا يتعلق التنبؤ بالسلاسل الزمنية بالنمذجة فقط — قبل اللجوء إلى تعلم الآلة، يمكن أن يُنتج فهم الاتجاه والموسمية وحدهما تنبؤات أساسية قوية. في هذا الدرس، نستخدم سيناريو التنبؤ بمبيعات مقهى "أليس" للقهوة، ونستعرض خطوات المعالجة الكاملة من المعالجة المسبقة إلى التنبؤ الأساسي باستخدام إعادة التعيين + التدوير + الإزاحة. هذا تطبيق عملي لأدوات الدرس 17 ومقدمة لدروس تعلم الآلة القادمة.

⚠️ ملاحظة: يجب تشغيل الكود أدناه في بيئة بايثون محلية.

1. What You Will Learn


2. خلفية المشروع: توقع مبيعات مقهى القهوة

(1) المهمة

تريد أليس التوقع بإيرادات مقهى القهوة للشهر القادم — البيانات التاريخية تظهر اتجاهًا (نمو شهري ثابت) وموسمية (المزيد من المشروبات الساخنة في الشتاء).

(2) خط أنابيب التحليل

100%
graph TB
    A["1. Preprocessing<br>resample+fillna"] --> B["2. Trend Extraction<br>rolling+diff"]
    B --> C["3. Seasonality Analysis<br>groupby month"]
    C --> D["4. Moving Average Forecast<br>rolling mean"]
    D --> E["5. Evaluation<br>MAE/MAPE"]
    E --> F["6. Next Month Forecast"]
TEXT 📖 للعرض فقط
> **الخرج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) واتباع التعليمات. القيم الفعلية قد تختلف قليلاً حسب إصدار pandas الخاص بك.

3. معالجة البيانات المسبقة

▶ مثال

TEXT 📖 للعرض فقط
> **المخرجات:** قم بالتنفيذ في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي مسبقاً على مكتبة pandas. يرجى تثبيتها محلياً (`pip install pandas`) ومتابعة التنفيذ. قد تختلف القيم الفعلية قليلاً حسب إصدار pandas لديك.

: معالجة السلاسل الزمنية مسبقة (الصعوبة ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

# ============================================
# الخطوة 1: التوليد والمعالجة المسبقة
# ============================================

np.random.seed(42)

# توليد بيانات مبيعات لمدة 365 يوماً مع اتجاه + موسمية + ضوضاء
dates = pd.date_range('2023-01-01', periods=365)
trend = np.linspace(300, 450, 365)  # اتجاه صاعد
seasonality = 50 * np.sin(2 * np.pi * np.arange(365) / 365 * 2)  # دورة نصف سنوية
winter_effect = np.where(dates.month.isin([11, 12, 1, 2]), 30, 0)  # زيادة في الشتاء
noise = np.random.normal(0, 20, 365)

daily_sales = pd.DataFrame({
    'sales': np.round(trend + seasonality + winter_effect + noise, 0),
    'customers': np.round((trend + seasonality + winter_effect + noise) / 8, 0)
}, index=dates)

# إدخال أيام مفقودة
missing_idx = np.random.choice(365, 15, replace=False)
daily_sales.iloc[missing_idx] = np.nan

# المعالجة المسبقة: ملء القيم المفقودة ← التأكد من التردد اليومي
daily_sales = daily_sales.asfreq('D')  # التأكد من عدم وجود تواريخ مفقودة
daily_sales['sales'] = daily_sales['sales'].interpolate(method='time')
daily_sales['customers'] = daily_sales['customers'].interpolate(method='time')

print(f"النطاق الزمني: من {daily_sales.index[0].date()} إلى {daily_sales.index[-1].date()}")
print(f"القيم المفقودة بعد الملء: {daily_sales.isnull().sum().sum()}")
print(f"\nإحصائيات المبيعات اليومية:\n{daily_sales['sales'].describe()}")
TEXT 📖 للعرض فقط
> **المخرجات:** قم بالتنفيذ في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي مسبقاً على مكتبة pandas. يرجى تثبيتها محلياً (`pip install pandas`) ومتابعة التنفيذ. قد تختلف القيم الفعلية قليلاً حسب إصدار pandas لديك.

4. استخلاص الاتجاهات

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** قم بالتشغيل في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبته مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) واتباع الخطوات. قد تختلف القيم الفعلية قليلاً حسب إصدار pandas الخاص بك.

: الاتجاه وإزالة الاتجاه (الصعوبة ⭐⭐)

PYTHON
# ============================================
# الخطوة 2: استخلاص الاتجاه
# ============================================

# المتوسط المتحرك لمدة 30 يومًا = الاتجاه
daily_sales['trend'] = daily_sales['sales'].rolling(30, center=True).mean()

# إزالة الاتجاه = الفعلي - الاتجاه (يظهر الموسمية + الضوضاء)
daily_sales['detrended'] = daily_sales['sales'] - daily_sales['trend']

# معدل النمو شهرًا بعد شهر
monthly = daily_sales['sales'].resample('M').sum()
monthly_growth = monthly.pct_change() * 100

print("=== المبيعات الشهرية ===")
print(monthly.tail(6))
print(f"\nمتوسط النمو الشهري: {monthly_growth.mean():.1f}%")
print(f"النمو السنوي الإجمالي: {((monthly.iloc[-1] / monthly.iloc[0]) - 1) * 100:.1f}%")
TEXT 📖 للعرض فقط
> **الإخراج:** قم بالتشغيل في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبته مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) واتباع الخطوات. قد تختلف القيم الفعلية قليلاً حسب إصدار pandas الخاص بك.

5. تحليل الموسمية

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). لا يحتوي خادم Pandas على مكتبة pandas مثبتة مسبقًا. يرجى تثبيتها محليًا (`pip install pandas`) واتباع التوضيح. قد تختلف القيم الفعلية قليلاً اعتمادًا على إصدار pandas الخاص بك.

: مؤشر الموسمية (الصعوبة ⭐⭐⭐)

PYTHON
# ============================================
# الخطوة 3: تحليل الموسمية
# ============================================

# المتوسط الشهري (عبر جميع السنوات)
daily_sales['month'] = daily_sales.index.month
monthly_avg = daily_sales.groupby('month')['sales'].mean()

# مؤشر الموسمية = المتوسط الشهري / المتوسط الإجمالي
overall_avg = daily_sales['sales'].mean()
seasonal_index = (monthly_avg / overall_avg).round(3)

print("=== مؤشر الموسمية ===")
for m, idx in seasonal_index.items():
    label = "↑" if idx > 1.05 else ("↓" if idx < 0.95 else "→")
    print(f"  الشهر {m:2d}: {idx:.3f} {label}")

# نمط أيام الأسبوع
daily_sales['dayofweek'] = daily_sales.index.dayofweek
dow_avg = daily_sales.groupby('dayofweek')['sales'].mean()
dow_names = ['الإثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت', 'الأحد']
print(f"\n=== متوسط أيام الأسبوع ===")
for i, name in enumerate(dow_names):
    print(f"  {name}: ${dow_avg[i]:.0f}")
TEXT 📖 للعرض فقط
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). لا يحتوي خادم Piston على مكتبة pandas مثبتة مسبقًا. يرجى تثبيتها محليًا (`pip install pandas`) واتباع التوضيح. قد تختلف القيم الفعلية قليلاً اعتمادًا على إصدار pandas الخاص بك.

6. التوقعات باستخدام المتوسط المتحرك

▶ مثال

TEXT 📖 للعرض فقط
> **الناتج:** قم بتشغيله في بيئة بايثون محلية (pandas 2.x). لا يحتوي خادم Piston على مكتبة pandas مثبتة مسبقًا. يرجى تثبيتها محليًا (`pip install pandas`) واتباع الخطوات. قد تختلف القيم الفعلية قليلاً اعتمادًا على إصدار pandas الخاص بك.

: التوقع المتسلسل (الصعوبة ⭐⭐⭐)

PYTHON
# ============================================
# الخطوة 4: التوقع باستخدام المتوسط المتحرك
# ============================================

# تقسيم بيانات التدريب/الاختبار: آخر 30 يومًا كاختبار
train = daily_sales.iloc[:-30]
test = daily_sales.iloc[-30:]

# التوقع بالمتوسط البسيط المتحرك (استخدام متوسط آخر N يومًا)
def ma_forecast(train_series, window, horizon):
    """التنبؤ بـ 'horizon' يومًا قادمًا باستخدام المتوسط المتحرك"""
    last_ma = train_series.rolling(window).mean().iloc[-1]
    return pd.Series([last_ma] * horizon,
                     index=pd.date_range(train.index[-1] + pd.Timedelta(days=1),
                                         periods=horizon))

# تجريب نوافذ مختلفة
for w in [7, 14, 30]:
    forecast = ma_forecast(train['sales'], w, 30)
    mae = (forecast - test['sales']).abs().mean()
    mape = ((forecast - test['sales']) / test['sales']).abs().mean() * 100
    print(f"MA({w:2d}): MAE=${mae:.0f}, MAPE={mape:.1f}%")

# التوقع مع تعديل موسمي
# forecast = trend_forecast × seasonal_index
last_trend = train['sales'].rolling(30).mean().iloc[-1]
monthly_trend_growth = train['sales'].rolling(30).mean().diff(30).mean()
forecast_months = test.index.month
seasonal_forecast = pd.Series(
    [(last_trend + monthly_trend_growth * i) * seasonal_index[m]
     for i, m in enumerate(forecast_months)],
    index=test.index
)
mae_seasonal = (seasonal_forecast - test['sales']).abs().mean()
mape_seasonal = ((seasonal_forecast - test['sales']) / test['sales']).abs().mean() * 100
print(f"\nSeasonal-adjusted: MAE=${mae_seasonal:.0f}, MAPE={mape_seasonal:.1f}%")
TEXT 📖 للعرض فقط
> **الناتج:** قم بتشغيله في بيئة بايثون محلية (pandas 2.x). لا يحتوي خادم Piston على مكتبة pandas مثبتة مسبقًا. يرجى تثبيتها محليًا (`pip install pandas`) واتباع الخطوات. قد تختلف القيم الفعلية قليلاً اعتمادًا على إصدار pandas الخاص بك.

7. تقييم التنبؤات

▶ مثال

TEXT 📖 للعرض فقط
> **Output:** Run in a local Python environment (pandas 2.x). The Piston server does not have pandas pre-installed. Please install it locally (`pip install pandas`) and follow along. Actual values may vary slightly depending on your pandas version.

: تقييم وتنبؤ الشهر القادم (الصعوبة ⭐⭐)

PYTHON
# ============================================
# Step 5-6: Evaluation & Next Month Forecast
# ============================================

# Evaluation metrics
def evaluate(actual, forecast, name="Model"):
    mae = (forecast - actual).abs().mean()
    rmse = ((forecast - actual) ** 2).mean() ** 0.5
    mape = ((forecast - actual) / actual).abs().mean() * 100
    print(f"{name}: MAE=${mae:.0f}, RMSE=${rmse:.0f}, MAPE={mape:.1f}%")
    return mae, rmse, mape

# Evaluate best model
print("=== Model Evaluation ===")
evaluate(test['sales'], ma_forecast(train['sales'], 14, 30), "MA(14)")
evaluate(test['sales'], seasonal_forecast, "Seasonal-adjusted")

# Next month forecast (Jan 2024)
next_month_days = pd.date_range('2024-01-01', periods=31)
last_trend_full = daily_sales['sales'].rolling(30).mean().iloc[-1]
jan_index = seasonal_index[1]
next_month_forecast = last_trend_full * jan_index

print(f"\n=== January 2024 Forecast ===")
print(f"  Daily forecast: ${next_month_forecast:.0f}")
print(f"  Monthly forecast: ${next_month_forecast * 31:,.0f}")
print(f"  Based on trend: ${last_trend_full:.0f} × seasonal index: {jan_index:.3f}")
TEXT 📖 للعرض فقط
> **Output:** Run in a local Python environment (pandas 2.x). The Piston server does not have pandas pre-installed. Please install it locally (`pip install pandas`) and follow along. Actual values may vary slightly depending on your pandas version.

❓ أسئلة شائعة

س كيف أختار نافذة المتوسط المتحرك؟
ج تطابق النافذة مع الدورة الطبيعية للبيانات هو الخيار الأكثر أماناً. للبيانات اليومية، تعني الدورة الأسبوعية نافذة=7؛ والدورة الشهرية تعني نافذة=30. نافذة صغيرة جداً تلتقط الكثير من الضوضاء، ونافذة كبيرة جداً ت introduce تأخيراً ملموساً. ابدأ بنافذة تساوي الفترة الطبيعية، ثم جرب بضع قيم أعلى وأقل، وقارن MAPE. في هذا الدرس، يحقق MA(14) عادةً توازناً أفضل من MA(7) أو MA(30).
س كيف أفصل الاتجاه عن الموسمية؟
ج الاتجاه = المتوسط المتحرك طويل المدى (30+ يوماً)؛ الموسمية = نسبة متوسط كل شهر إلى المتوسط الكلي. إزالة الاتجاه تعني طرح الاتجاه من القيم الخام (النموذج الجمعي) أو قسمة القيم الخام على الاتجاه (النموذج الضربي). بيانات المبيعات تطلب عادةً نموذجاً ضربياً (تتغير التقلبات الموسمية مع المستوى)، بينما بيانات درجة الحرارة تتوافق مع النموذج الجمعي.
س ما قيمة MAPE التي تُعتبر جيدة؟
ج MAPE < 10% تعني دقة عالية، 10-20% جيد، 20-50% مقبول، و> 50% غير موثوق. لكن السياق مهم — بالنسبة لمبيعات يومية شديدة التقلب، فإن MAPE بنسبة 20% جيدة بالفعل، وبعد التجميع إلى إجماليات شهرية، يجب أن ينخفض MAPE إلى أقل من 10%. كلما زادت مدة التنبؤ، قلت موثوقيته — التنبؤ ليوم 7 أكثر موثوقية بكثير من التنبؤ ليوم 30.
س إلى أي مدى يمكنني التنبؤ بشكل موثوق؟
ج القاعدة العامة: أفق التنبؤ الموثوق يساوي تقريباً من 1/3 إلى 1/2 طول بياناتك التاريخية. مع سنة واحدة من التاريخ، يمكنك التنبؤ بشكل موثوق بحد أقصى 4-6 أشهر. المتوسطات المتحركة يمكنها فقط إسقاط استمرارية "مسطحة" — لا تستطيع التنبؤ بنقاط الانعطاف. تغيرات الاتجاه أو الأحداث غير المتوقعة تبطل التنبؤ تماماً. التنبؤ أداة لدعم القرار، وليس كرة بلورية.
س كيف يختلف هذا عن التنبؤ باستخدام التعلم الآلي؟
ج يفترض التنبؤ بالمتوسط المتحرك أن "المستقبل استمرار للماضي" — يمكنه فقط التقاط الاتجاه والموسمية. طرق التعلم الآلي (ARIMA/LSTM/Prophet) يمكنها نمذجة أنماط أكثر تعقيداً بكثير (اتجاهات غير خطية، تفاعلات متعددة المتغيرات، تأثيرات العطلات). يعمل هذا الدرس كخط أساس للتعلم الآلي — إذا حقق المتوسط المتحرك البسيط MAPE بنسبة 15%، فيجب أن ينخفض نموذج التعلم الآلي إلى أقل من 10% لتكون القيمة المضافة تعويضاً للتعقيد الإضافي.
س كيف يتم حساب مؤشر الموسمية؟
ج اقسم متوسط كل شهر على المتوسط الكلي. على سبيل المثال، إذا كان متوسط يناير 420 والمتوسط الكلي 380، فإن مؤشر الموسمية هو 420/380 = 1.105 (يناير يتجاوز المتوسط بنسبة 10.5%). مؤشر > 1 يشير إلى موسم الذروة؛ < 1 يشير إلى الموسم المنخفض. تحتاج إلى عامين على الأقل من البيانات لمؤشر موسمية موثوق (يناير قد يختلف من سنة إلى أخرى).
س هل يجب استخدام interpolate أو ffill للقيم المفقودة؟
ج للسلاسل الزمنية، يُوصى باستخدام interpolate — الاستيفاء بين القيم المجاورة ينتج نتائج أكثر سلاسة ويحافظ على الاتجاه. يُحضر ffill آخر قيمة معروفة إلى الأمام، مما يخلق تأثير الدرج خلال الفجوات (خلل في الاتجاه). للفجوات الكبيرة، استخدم interpolate(method='time') لمراعاة المسافة الزمنية؛ للفجوات الصغيرة، يكون الاستيفاء الخطي كافياً.

📖 ملخص


📝 تمارين

  1. أساسي (صعوبة ⭐): إنشاء بيانات مبيعات لمدة 180 يومًا (مع اتجاه)، تجميع إلى المجاميع الشهرية باستخدام resample('M')، وحساب معدل النمو الشهري.
  2. متوسط (صعوبة ⭐⭐): إنشاء بيانات لمدة 365 يومًا مع الموسمية، استخراج خط اتجاه لمدة 30 يومًا، حساب المؤشر الموسمي لمدة 12 شهرًا، وتحديد المواسم الذروة والمواسم الهادئة.
  3. تحدي (صعوبة ⭐⭐⭐): إكمال مشروع تنبؤ كامل: المعالجة المسبقة → الاتجاه + الموسمية → تنبؤات MA(7/14/30) → التنبؤ المعدّل موسميًا → تقييم MAPE → إخراج تنبؤ الشهر التالي.

← Previous: Project - Data Analysis · Next: Project - Comprehensive Analysis →

Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%