Pandas: مشروع: السلاسل الزمنية
آخر تحديث: 2026-08-26
لا يتعلق التنبؤ بالسلاسل الزمنية بالنمذجة فقط — قبل اللجوء إلى تعلم الآلة، يمكن أن يُنتج فهم الاتجاه والموسمية وحدهما تنبؤات أساسية قوية. في هذا الدرس، نستخدم سيناريو التنبؤ بمبيعات مقهى "أليس" للقهوة، ونستعرض خطوات المعالجة الكاملة من المعالجة المسبقة إلى التنبؤ الأساسي باستخدام إعادة التعيين + التدوير + الإزاحة. هذا تطبيق عملي لأدوات الدرس 17 ومقدمة لدروس تعلم الآلة القادمة.
1. What You Will Learn
- ❶ Time series preprocessing
- ❷ Trend and seasonality extraction
- ❸ Moving average forecasting
- ❹ Seasonal decomposition
- ❺ Forecast evaluation (MAE/MAPE)
2. خلفية المشروع: توقع مبيعات مقهى القهوة
(1) المهمة
تريد أليس التوقع بإيرادات مقهى القهوة للشهر القادم — البيانات التاريخية تظهر اتجاهًا (نمو شهري ثابت) وموسمية (المزيد من المشروبات الساخنة في الشتاء).
(2) خط أنابيب التحليل
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"]
> **الخرج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) واتباع التعليمات. القيم الفعلية قد تختلف قليلاً حسب إصدار pandas الخاص بك.
3. معالجة البيانات المسبقة
▶ مثال
> **المخرجات:** قم بالتنفيذ في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي مسبقاً على مكتبة pandas. يرجى تثبيتها محلياً (`pip install pandas`) ومتابعة التنفيذ. قد تختلف القيم الفعلية قليلاً حسب إصدار pandas لديك.
: معالجة السلاسل الزمنية مسبقة (الصعوبة ⭐⭐)
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()}")
> **المخرجات:** قم بالتنفيذ في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي مسبقاً على مكتبة pandas. يرجى تثبيتها محلياً (`pip install pandas`) ومتابعة التنفيذ. قد تختلف القيم الفعلية قليلاً حسب إصدار pandas لديك.
4. استخلاص الاتجاهات
▶ مثال
> **الإخراج:** قم بالتشغيل في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبته مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) واتباع الخطوات. قد تختلف القيم الفعلية قليلاً حسب إصدار pandas الخاص بك.
: الاتجاه وإزالة الاتجاه (الصعوبة ⭐⭐)
# ============================================
# الخطوة 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}%")
> **الإخراج:** قم بالتشغيل في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبته مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) واتباع الخطوات. قد تختلف القيم الفعلية قليلاً حسب إصدار pandas الخاص بك.
5. تحليل الموسمية
▶ مثال
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). لا يحتوي خادم Pandas على مكتبة pandas مثبتة مسبقًا. يرجى تثبيتها محليًا (`pip install pandas`) واتباع التوضيح. قد تختلف القيم الفعلية قليلاً اعتمادًا على إصدار pandas الخاص بك.
: مؤشر الموسمية (الصعوبة ⭐⭐⭐)
# ============================================
# الخطوة 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}")
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). لا يحتوي خادم Piston على مكتبة pandas مثبتة مسبقًا. يرجى تثبيتها محليًا (`pip install pandas`) واتباع التوضيح. قد تختلف القيم الفعلية قليلاً اعتمادًا على إصدار pandas الخاص بك.
6. التوقعات باستخدام المتوسط المتحرك
▶ مثال
> **الناتج:** قم بتشغيله في بيئة بايثون محلية (pandas 2.x). لا يحتوي خادم Piston على مكتبة pandas مثبتة مسبقًا. يرجى تثبيتها محليًا (`pip install pandas`) واتباع الخطوات. قد تختلف القيم الفعلية قليلاً اعتمادًا على إصدار pandas الخاص بك.
: التوقع المتسلسل (الصعوبة ⭐⭐⭐)
# ============================================
# الخطوة 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}%")
> **الناتج:** قم بتشغيله في بيئة بايثون محلية (pandas 2.x). لا يحتوي خادم Piston على مكتبة pandas مثبتة مسبقًا. يرجى تثبيتها محليًا (`pip install pandas`) واتباع الخطوات. قد تختلف القيم الفعلية قليلاً اعتمادًا على إصدار pandas الخاص بك.
7. تقييم التنبؤات
▶ مثال
> **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.
: تقييم وتنبؤ الشهر القادم (الصعوبة ⭐⭐)
# ============================================
# 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}")
> **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.
❓ أسئلة شائعة
interpolate أو ffill للقيم المفقودة؟interpolate — الاستيفاء بين القيم المجاورة ينتج نتائج أكثر سلاسة ويحافظ على الاتجاه. يُحضر ffill آخر قيمة معروفة إلى الأمام، مما يخلق تأثير الدرج خلال الفجوات (خلل في الاتجاه). للفجوات الكبيرة، استخدم interpolate(method='time') لمراعاة المسافة الزمنية؛ للفجوات الصغيرة، يكون الاستيفاء الخطي كافياً.📖 ملخص
- المعالجة المسبقة:
asfreqيضمن تردداً ثابتاً، ثم التداخل يملأ القيم المفقودة - الاتجاه: المتوسط المتحرك
rolling(30)؛ التفاضلdiffيكشف عن معدل التغيير - الموسمية: المتوسط الشهري / المتوسط العام = مؤشر الموسمية
- التنبؤ: المتوسط المتحرك (بسيط) → اتجاه × مؤشر الموسمية (محسن)
- التقييم: MAE (المطلق) / RMSE (الجذر التربيعي للمتوسط) / MAPE (النسبة المئوية)
- أفق التنبؤ الموثق ≈ 1/3 من طول البيانات التاريخية؛ المتوسط المتحرك يعمل كخط أساس للتعلم الآلي
📝 تمارين
- أساسي (صعوبة ⭐): إنشاء بيانات مبيعات لمدة 180 يومًا (مع اتجاه)، تجميع إلى المجاميع الشهرية باستخدام resample('M')، وحساب معدل النمو الشهري.
- متوسط (صعوبة ⭐⭐): إنشاء بيانات لمدة 365 يومًا مع الموسمية، استخراج خط اتجاه لمدة 30 يومًا، حساب المؤشر الموسمي لمدة 12 شهرًا، وتحديد المواسم الذروة والمواسم الهادئة.
- تحدي (صعوبة ⭐⭐⭐): إكمال مشروع تنبؤ كامل: المعالجة المسبقة → الاتجاه + الموسمية → تنبؤات MA(7/14/30) → التنبؤ المعدّل موسميًا → تقييم MAPE → إخراج تنبؤ الشهر التالي.
← Previous: Project - Data Analysis · Next: Project - Comprehensive Analysis →