Pandas: السلاسل الزمنية المتقدمة

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

في الدرس 16، تعلمت عن أنواع datetime. يتعمق هذا الدرس في التحليل العملي للسلاسل الزمنية — تقوم أداة resample بتحويل البيانات اليومية إلى ملخصات شهرية، وتبني أداة shift/diff سمات التأخير (lag features)، وتجعل الدوال الثلاث للنافذة (rolling/expanding/ewm) الاتجاهات والتقلب واضحين تمامًا. هذه هي الأساسيات الجوهرية للتحليل الكمي وهندسة سمات التعلم الآلي.

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

1. What You Will Learn


2. تقرير مقهى أليس الشهري

(1) المشكلة: البيانات اليومية تفصيلية جداً — نحتاج إلى تجميع شهري

أليس لديها بيانات مبيعات يومية لـ 90 يومًا، ومديرها يتقريرًا شهريًا:

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'sales': np.random.poisson(50, 90) * 10,
    'customers': np.random.poisson(30, 90)
}, index=pd.date_range('2024-01-01', periods=90))
# بيانات يومية → تحتاج إلى ملخص شهري
TEXT 📖 للعرض فقط
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبتة مسبقًا. يرجى تثبيتها محليًا (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلية قليًّا اعتمادًا على إصدار pandas لديك.

(2) الحل: تجميع بسطر واحد باستخدام resample

▶ مثال

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

: تجميع شهري باستخدام resample (الصعوبة ⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'sales': np.random.poisson(50, 90) * 10,
    'customers': np.random.poisson(30, 90)
}, index=pd.date_range('2024-01-01', periods=90))

# إعادة تعيين العينة من يومي → شهري
monthly = df.resample('M').agg({
    'sales': 'sum',
    'customers': 'mean'
})
print(monthly)
#              sales  customers
# 2024-01-31   14680  29.55
# 2024-02-29   13520  31.10
# 2024-03-31   15430  30.45
TEXT 📖 للعرض فقط
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبتة مسبقًا. يرجى تثبيتها محليًا (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلية قليًّا اعتمادًا على إصدار pandas لديك.

3. إعادة التعيين — إعادة التعيين

(1) تخفيض العينات وزيادة العينات

▶ مثال

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

: إعادة التعيين تخفيض العينات/زيادة العينات (الصعوبة ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'price': np.random.uniform(100, 200, 60).round(2)
}, index=pd.date_range('2024-01-01', periods=60))

# تخفيض العينات: يومي → أسبوعي
weekly = df.resample('W').agg({
    'price': ['first', 'max', 'min', 'last']  # OHLC
})
print("أسبوعي OHLC:")
print(weekly.head())

# تخفيض العينات: يومي → ربع سنوي
quarterly = df.resample('Q').mean()

# زيادة العينات: يومي → ساعة (يحتاج طريقة ملء)
hourly = df.resample('h').ffill()  # ملء للأمام
# تحذير: ينشئ كثير من الصفوف!
TEXT 📖 للعرض فقط
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبته مسبقاً. يرجى تثبيته محلياً (`pip install pandas`) واتباع الخطوات. قد تختلف القيم الفعلي قليلاً حسب إصدار pandas الخاص بك.

(2) إعادة التعيين مقابل تجميع المجموعات

الميزة إعادة التعيين تجميع المجموعات
أساس التجميع تردد الوقت أي عمود
يعمل مع فهرس التاريخ والوقت أي إطار بيانات
زيادة العينات ✅ يملء القيم المفقودة
أسماء التردد D/W/M/Q/H
الجوهر تجميع على بُعد الوقت تجميع متعدد الأغراض

4. shift / diff / pct_change

(1) التأخير والفرق

▶ مثال

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

: shift/diff/pct_change (الصعوبة ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'sales': np.random.poisson(50, 10) * 10
}, index=pd.date_range('2024-01-01', periods=10))

# shift: move data forward/backward
df['sales_prev'] = df['sales'].shift(1)    # yesterday's sales
df['sales_next'] = df['sales'].shift(-1)   # tomorrow's sales

# diff: difference with previous value
df['sales_diff'] = df['sales'].diff()       # sales - prev_sales

# pct_change: percentage change
df['sales_pct'] = df['sales'].pct_change() * 100  # % change

print(df.round(1))
TEXT 📖 للعرض فقط
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (إصدار pandas 2.x). خادم Piston لا يحتوي على pandas مثبّتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلية قليًًا حسب إصدار pandas لديك.
💡 نصيحة: دالة shift هي حجر الأساس لهندسة ميزات السلاسل الزمنية في تعلم الآلة — df['lag_1'] = df['value'].shift(1) تستخدم قيمة الأمس للتنبؤ باليوم. تُستخرج diff تغيرات الاتجاه، بينما تُستخرج pct_change معدلات النمو.


5. rolling — النافذة المتحركة

(1) المتوسط المتحرك

▶ مثال

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
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'temperature': np.round(np.random.normal(20, 5, 30), 1)
}, index=pd.date_range('2024-01-01', periods=30))

# 7-day moving average
df['ma_7'] = df['temperature'].rolling(window=7).mean()

# With min_periods (allow partial windows)
df['ma_7_flex'] = df['temperature'].rolling(window=7, min_periods=3).mean()

# Multiple aggregations
# Pandas 2.x: rolling.agg() returns a multi-column DataFrame and cannot be assigned to a single column directly; use join instead
rolling_agg = df['temperature'].rolling(7).agg(['mean', 'std', 'min', 'max'])
rolling_agg.columns = [f'temp_{stat}' for stat in ['mean', 'std', 'min', 'max']]
df = df.join(rolling_agg)

# Centered rolling (include future values — for smoothing, not forecasting)
df['ma_7_center'] = df['temperature'].rolling(7, center=True).mean()

print(df[['temperature', 'ma_7', 'ma_7_center']].head(10))
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.

(2) النوافذ المبنية على الوقت

▶ مثال

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
import pandas as pd
import numpy as np

# Irregular time series — row-based window is wrong
df = pd.DataFrame({
    'value': [10, 20, 15, 30, 25, 40, 35, 50, 45, 60]
}, index=pd.to_datetime([
    '2024-01-01', '2024-01-02', '2024-01-05',  # gap on 03-04
    '2024-01-06', '2024-01-07', '2024-01-15',  # gap on 08-14
    '2024-01-16', '2024-01-20', '2024-01-21', '2024-01-25'
]))

# Row-based window (wrong for irregular data)
print("Row-based 3:")
print(df['value'].rolling(3).mean())

# Time-based window (correct — 7 calendar days)
print("\nTime-based 7D:")
print(df['value'].rolling('7D').mean())
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.

6. النافذة التراكمية التوسعيّة (expanding — Cumulative Window)

▶ مثال

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

: إحصائيات تراكمية توسعيّة (الصعوبة ⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'sales': np.random.poisson(50, 10) * 10
})

# التوسيع: جميع القيم حتى الموضع الحالي
df['cumsum'] = df['sales'].expanding().sum()
df['cummean'] = df['sales'].expanding().mean()
df['cummax'] = df['sales'].expanding().max()

# مع الحد الأدنى للفترات
df['cummean_3'] = df['sales'].expanding(min_periods=3).mean()

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

(1) مقارنة بين rolling و expanding و ewm

الميزة rolling (متحرك) expanding (توسعي) ewm (أُسِّي مُتَّزِن)
النافذة حجم ثابت مُتزايدة باستمرار لا نهائية (الأوزان تتلاشى)
الأوزان متساوية متساوية القيم الحديثة وزنها أكبر
حالة الاستخدام الاتجاهات المحلية الإحصائيات التراكمية الاتجاهات المُمَلَّسة
المعلمات window min_periods span/com/halflife

7. ewm — الترجيح الأُسِّي

▶ مثال

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

: التنعيم الأُسِّي ewm (الصعوبة ⭐⭐)

PYTHON
import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'price': np.cumsum(np.random.randn(30)) + 100
}, index=pd.date_range('2024-01-01', periods=30))

# Exponentially weighted moving average
df['ewm_5'] = df['price'].ewm(span=5).mean()
df['ewm_10'] = df['price'].ewm(span=10).mean()
df['ewm_20'] = df['price'].ewm(span=20).mean()

# Compare with simple moving average
df['sma_5'] = df['price'].rolling(5).mean()

print(df[['price', 'sma_5', 'ewm_5']].head(10).round(2))

# ewm parameters (only specify one):
# span=5     → similar to 5-period SMA but recent-heavy
# com=4      → center of mass (com = span-1)
# halflife=3 → weight halves every 3 periods
# alpha=0.3  → smoothing factor (0<α≤1)
TEXT 📖 للعرض فقط
> **الإخراج:** تشغيل في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي مسبقًا على مكتبة pandas. يرجى تثبيتها محليًا (`pip install pandas`) واتباع الخطوات. قد تختلف القيم الفعليّة قليلاً اعتمادًا على إصدار pandas لديك.

8. مثال كامل: تحليل شموع الأسهم اليومية

(5) ▶ نافذة متحركة تزحلق على طول محور الوقت

100%
graph TB
    A[Time Series Data] --> B[rolling Window]
    B --> C[Window slides along the time axis]
    C --> D[Window 1: t1 to tN]
    C --> E[Window 2: t2 to t(N+1)]
    C --> F[Window 3: t3 to t(N+2)]
    D --> G[mean / std / max / min]
    E --> G
    F --> G
    G --> H[Smoothed trend / Volatility]
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.

▶ مثال

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
import pandas as pd
import numpy as np

# ============================================
# Comprehensive example: Stock daily data
# resample → rolling → diff → ewm
# ============================================

# 1. Generate daily stock data (60 trading days)
np.random.seed(42)
dates = pd.bdate_range('2024-01-01', periods=60)
price = 100 + np.cumsum(np.random.randn(60) * 2)
df = pd.DataFrame({
    'open': price + np.random.randn(60) * 0.5,
    'high': price + np.abs(np.random.randn(60)) * 1.5,
    'low': price - np.abs(np.random.randn(60)) * 1.5,
    'close': price + np.random.randn(60) * 0.5,
    'volume': np.random.randint(100000, 500000, 60)
}, index=dates)

# 2. Resample: daily → weekly
weekly = df.resample('W-FRI').agg({
    'open': 'first', 'high': 'max', 'low': 'min',
    'close': 'last', 'volume': 'sum'
})
print("=== Weekly OHLCV ===")
print(weekly.head(4).round(2))

# 3. Rolling: 20-day moving average
df['ma_20'] = df['close'].rolling(20).mean()
df['vol_20'] = df['close'].rolling(20).std()

# 4. Daily returns
df['returns'] = df['close'].pct_change()
df['log_returns'] = np.log(df['close'] / df['close'].shift(1))

# 5. EWM: exponential smoothing
df['ewm_12'] = df['close'].ewm(span=12).mean()
df['ewm_26'] = df['close'].ewm(span=26).mean()
df['macd'] = df['ewm_12'] - df['ewm_26']

# 6. Summary
print(f"\n=== Statistics ===")
print(f"Period: {df.index[0].date()} to {df.index[-1].date()}")
print(f"Total return: {((df['close'].iloc[-1] / df['close'].iloc[0] - 1) * 100):.1f}%")
print(f"Volatility (20d): {df['vol_20'].iloc[-1]:.2f}")
print(f"Max drawdown: {((df['close'] / df['close'].cummax() - 1).min() * 100):.1f}%")
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.

❓ أسئلة شائعة

س ما الفرق بين resample و groupby؟
ج resample هو في الأساس groupby على بُعد الزمن — يُصنف حسب التردد (يومي/أسبوعي/شهري/ربع سنوي) ويعمل فقط مع DatetimeIndex/PeriodIndex. groupby يُصنف حسب أي عمود ولا يقتصر على الزمن. resample يدعم بشكل فريد إعادة التعيين (من تردد منخفض → إلى تردد عالٍ يتطلب ملء)، وهو ما لا يستطيع groupby فعله. القاعدة العامة: استخدم resample للتجميع الزمني، وgroupby لكل شيء آخر.
س كيف أختار حجم نافذة الانزلاق (rolling window)؟
ج يعتمد على السيناريو. للاتجاهات قصيرة المدى، استخدم نافذة من 5-10؛ للمدى المتوسط، 20-30 (شهري)؛ للمدى الطويل، 50-200 (ربع سنوي/سنوي). في المالية، المتوسطات المتحركة لـ 20/50/200 يوم هي المعيار. نافذة صغيرة جداً → ضوضاء زائدة؛ كبيرة جداً → تأخر مفرط. التوصية: افحص تردد بياناتك أولاً، واجعل النافذة تغطي 1-3 "دورات طبيعية".
س ما الفرق بين shift و diff؟
ج shift يتحرك البيانات دون حساب أي شيء؛ diff يحسب الفرق. s.diff() = s - s.shift(1). يُستخدم shift لبناء ميزات التأخر (التعلم الآلي)، بينما diff يستخرج مقدار التغيير (تحليل الاتجاه). pct_change يحسب معدل التغيير: s.pct_change() = (s - s.shift(1)) / s.shift(1).
س ما الفرق بين expanding و cumsum؟
ج expanding هي نسخة معممة من cumsum — تدعم أي دالة تجميع (mean/std/max/min)، بينما cumsum يحسب فقط المجموع التراكمي. s.expanding().sum() يعادل s.cumsum(). expanding أكثر مرونة لكنه أبطأ قليلاً؛ للاستخدام اليومي، cumsum أكثر إيجازاً.
س ما هو ewm؟
ج EWM (المتوسط المتحرك المُرجح أسياً) هو متوسط متحرك مُرجح أسياً — البيانات الأحدث تحصل على وزن أعلى، والبيانات الأقدم تتلاشى أسياً. span=5 يتصرف بشكل مشابه لمتوسط متحرك بسيط (SMA) لـ 5 فترات لكنه يمنح وزناً أكبر للقيم الأحدث. يستجيب للتغييرات بشكل أسرع من SMA وهو أساس التحليل الفني (MACD) والتعلم عبر الإنترنت.
س كيف أملأ القيم بعد إعادة التعيين مع resample؟
ج إعادة التعيين (من تردد منخفض → إلى تردد عالٍ) تنتج قيم NaN كثيرة. خيارات الملء: ffill() (ملء أمامي، يستخدم القيمة السابقة)، bfill() (ملء خلفي)، interpolate() (استكمال)، asfreq() (يحافظ على NaN). يوصى باستخدام ffill (بسيط ومحافظ)؛ لتحليل السلاسل الزمنية، استخدم interpolate (نتائج أكثر سلاسة).
س هل يمكن استخدام center=True للتنبؤ؟
ج كلاً على الإطلاق! center=True يجعل النافذة "تنظر إلى المستقبل" (تتضمن القيم اللاحقة)، لذلك مناسب فقط لتنعيم البيانات التاريخية — أبداً للتنبؤ. في سيناريوهات الوقت الفعلي أو التنبؤ، يجب استخدام center=False (الافتراضي)، الذي ينظر فقط إلى القيم السابقة. هذا الخطأ يسبب تسرباً حاداً للبيانات في التحليل الكمي.

📖 ملخص


📝 تمارين

  1. أساسي (مستع الصعوبة ⭐): أنشئ DataFrame للمبيعات اليومية على مدى 30 يومًا. استخدم resample('W') للتجميع إلى بيانات أسبوعية، و rolling(7) لحساب المتوسط المتحرك لمدة 7 أيام.
  2. متوسط (مستع الصعوبة ⭐⭐): أنشئ أسعار إغلاق أسهم لمدة 60 يومًا. احسب: shift(1) lagdiff() الفرق اليومي → pct_change() العوائد → rolling(20) التذبذب → ewm(span=12) التنعيم.
  3. تحدي (مستع الصعوبة ⭐⭐⭐): تحاكي مبيعات يومية لمدة 90 يومًا. أكمل: إعادة تعيين ملخصات شهرية/ربعية → مقارنة المتوسطات المتحركة لمدة 7/14/30 يومًا → المتوسط التراكمي المتسع → التنعيم بـ ewm → حساب أقصى انخفاض.

← Previous Lesson: Date and Time · Next Lesson: MultiIndex →

Web-Tutorial.com

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

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

100%