Pandas: السلاسل الزمنية المتقدمة
آخر تحديث: 2026-08-26
في الدرس 16، تعلمت عن أنواع datetime. يتعمق هذا الدرس في التحليل العملي للسلاسل الزمنية — تقوم أداة resample بتحويل البيانات اليومية إلى ملخصات شهرية، وتبني أداة shift/diff سمات التأخير (lag features)، وتجعل الدوال الثلاث للنافذة (rolling/expanding/ewm) الاتجاهات والتقلب واضحين تمامًا. هذه هي الأساسيات الجوهرية للتحليل الكمي وهندسة سمات التعلم الآلي.
1. What You Will Learn
- ❶ resample — resampling
- ❷ shift / diff / pct_change
- ❸ rolling — moving window
- ❹ expanding — cumulative window
- ❺ ewm — exponential weighting
2. تقرير مقهى أليس الشهري
(1) المشكلة: البيانات اليومية تفصيلية جداً — نحتاج إلى تجميع شهري
أليس لديها بيانات مبيعات يومية لـ 90 يومًا، ومديرها يتقريرًا شهريًا:
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))
# بيانات يومية → تحتاج إلى ملخص شهري
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبتة مسبقًا. يرجى تثبيتها محليًا (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلية قليًّا اعتمادًا على إصدار pandas لديك.
(2) الحل: تجميع بسطر واحد باستخدام resample
▶ مثال
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبتة مسبقًا. يرجى تثبيتها محليًا (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلية قليًّا اعتمادًا على إصدار pandas لديك.
: تجميع شهري باستخدام resample (الصعوبة ⭐)
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
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على مكتبة pandas مثبتة مسبقًا. يرجى تثبيتها محليًا (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلية قليًّا اعتمادًا على إصدار pandas لديك.
3. إعادة التعيين — إعادة التعيين
(1) تخفيض العينات وزيادة العينات
▶ مثال
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبته مسبقاً. يرجى تثبيته محلياً (`pip install pandas`) واتباع الخطوات. قد تختلف القيم الفعلي قليلاً حسب إصدار pandas الخاص بك.
: إعادة التعيين تخفيض العينات/زيادة العينات (الصعوبة ⭐⭐)
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() # ملء للأمام
# تحذير: ينشئ كثير من الصفوف!
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبته مسبقاً. يرجى تثبيته محلياً (`pip install pandas`) واتباع الخطوات. قد تختلف القيم الفعلي قليلاً حسب إصدار pandas الخاص بك.
(2) إعادة التعيين مقابل تجميع المجموعات
| الميزة | إعادة التعيين | تجميع المجموعات |
|---|---|---|
| أساس التجميع | تردد الوقت | أي عمود |
| يعمل مع | فهرس التاريخ والوقت | أي إطار بيانات |
| زيادة العينات | ✅ يملء القيم المفقودة | ❌ |
| أسماء التردد | D/W/M/Q/H | — |
| الجوهر | تجميع على بُعد الوقت | تجميع متعدد الأغراض |
4. shift / diff / pct_change
(1) التأخير والفرق
▶ مثال
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (إصدار pandas 2.x). خادم Piston لا يحتوي على pandas مثبّتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلية قليًًا حسب إصدار pandas لديك.
: shift/diff/pct_change (الصعوبة ⭐⭐)
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))
> **الإخراج:** قم بالتشغيل في بيئة Python محلية (إصدار pandas 2.x). خادم Piston لا يحتوي على pandas مثبّتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) والمتابعة. قد تختلف القيم الفعلية قليًًا حسب إصدار pandas لديك.
df['lag_1'] = df['value'].shift(1) تستخدم قيمة الأمس للتنبؤ باليوم. تُستخرج diff تغيرات الاتجاه، بينما تُستخرج pct_change معدلات النمو.
5. rolling — النافذة المتحركة
(1) المتوسط المتحرك
▶ مثال
> **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.
: المتوسط المتحرك المتحرك (الصعوبة ⭐⭐)
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))
> **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) النوافذ المبنية على الوقت
▶ مثال
> **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.
: نافذة متحركة مبنية على الوقت (الصعوبة ⭐⭐)
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())
> **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)
▶ مثال
> **المخرجات:** قم بالتشغيل في بيئة Python محلية (pandas 2.x). خادم Piston لا يحتوي على pandas مثبّتًا مسبقًا. يرجى تثبيته محليًا (`pip install pandas`) واتباع الخطوات. قد تختلف القيم الفعلية قليلاً حسب إصدار pandas الخاص بك.
: إحصائيات تراكمية توسعيّة (الصعوبة ⭐)
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)
> **المخرجات:** قم بالتشغيل في بيئة 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 — الترجيح الأُسِّي
▶ مثال
> **الإخراج:** تشغيل في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي مسبقًا على مكتبة pandas. يرجى تثبيتها محليًا (`pip install pandas`) واتباع الخطوات. قد تختلف القيم الفعليّة قليلاً اعتمادًا على إصدار pandas لديك.
: التنعيم الأُسِّي ewm (الصعوبة ⭐⭐)
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)
> **الإخراج:** تشغيل في بيئة بايثون محلية (pandas 2.x). خادم Piston لا يحتوي مسبقًا على مكتبة pandas. يرجى تثبيتها محليًا (`pip install pandas`) واتباع الخطوات. قد تختلف القيم الفعليّة قليلاً اعتمادًا على إصدار pandas لديك.
8. مثال كامل: تحليل شموع الأسهم اليومية
(5) ▶ نافذة متحركة تزحلق على طول محور الوقت
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]
> **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.
▶ مثال
> **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.
: خط أنابيب تحليل السلاسل الزمنية الكامل (الصعوبة ⭐⭐⭐)
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}%")
> **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.
❓ أسئلة شائعة
s.diff() = s - s.shift(1). يُستخدم shift لبناء ميزات التأخر (التعلم الآلي)، بينما diff يستخرج مقدار التغيير (تحليل الاتجاه). pct_change يحسب معدل التغيير: s.pct_change() = (s - s.shift(1)) / s.shift(1).s.expanding().sum() يعادل s.cumsum(). expanding أكثر مرونة لكنه أبطأ قليلاً؛ للاستخدام اليومي، cumsum أكثر إيجازاً.📖 ملخص
- إعادة التجميع تجمع البيانات حسب تردد الزمني — وهي "التجميع بمحاذاة البُعد الزمني"
- التحريك ينقل البيانات لإنشاء ميزات متأخرة؛ الفرق/التغير النسبي يحسبان الفروق ومعدلات التغيير
- النافذة المتنقلة تحسب عبر نافذة متحركة بحجم ثابت (المتوسط/الانحراف المعياري/القيم القصى المتحركة)
- النافذة الزمنية المتنقلة ('7D') مثالية للسلاسل الزمنية غير المنتظمة
- النافذة الموسعة هي نافذة تراكمية (من البداية إلى الموقع الحالي)؛ التفاوض الأُسّي يطبق وزنًا أسيًّا (القيم الحديثة تحصل على وزن أكبر)
- الاختيار: الاتجاهات المحلية ← النافذة المتنقلة، الإحصائيات التراكمية ← النافذة الموسعة، التنعيم ← التفاوض الأُسّي
📝 تمارين
- أساسي (مستع الصعوبة ⭐): أنشئ DataFrame للمبيعات اليومية على مدى 30 يومًا. استخدم
resample('W')للتجميع إلى بيانات أسبوعية، وrolling(7)لحساب المتوسط المتحرك لمدة 7 أيام. - متوسط (مستع الصعوبة ⭐⭐): أنشئ أسعار إغلاق أسهم لمدة 60 يومًا. احسب:
shift(1) lag→diff()الفرق اليومي →pct_change()العوائد →rolling(20)التذبذب →ewm(span=12)التنعيم. - تحدي (مستع الصعوبة ⭐⭐⭐): تحاكي مبيعات يومية لمدة 90 يومًا. أكمل: إعادة تعيين ملخصات شهرية/ربعية → مقارنة المتوسطات المتحركة لمدة 7/14/30 يومًا → المتوسط التراكمي المتسع → التنعيم بـ
ewm→ حساب أقصى انخفاض.
← Previous Lesson: Date and Time · Next Lesson: MultiIndex →