فجوات في البيانات
تحتوي جداول الأسعار الحقيقية على فجوات: سهم لم يُتداول ذلك اليوم، أو مصدر بيانات أسقط سعرًا، أو سوق في عطلة. تشير pandas إليها بـ NaN («ليس رقمًا»). قبل اختيار ما تفعله، انظر: يعطي df.isna() جدول صح/خطأ، ويعدّ .sum() الفجوات لكل عمود، ويعطي .mean() نسبتها. عمود فارغ بنسبة 40% مشكلة تختلف عن عمود ينقصه قيمة واحدة.
import pandas as pd
nan = float("nan")
df = pd.DataFrame(
{"AAPL": [184.0, nan, 186.5, 187.0, nan], "MSFT": [410.0, 411.0, nan, nan, 415.0]},
index=pd.bdate_range("2026-03-02", periods=5),
)
print(df.isna().sum().to_dict()) # {'AAPL': 2, 'MSFT': 2} holes per column
print(df.isna().mean().round(2).to_dict()) # {'AAPL': 0.4, 'MSFT': 0.4} share of holesالترحيل أو الحذف
الملء الطبيعي للأسعار هو آخر سعر معروف: إلى أن يصل سعر جديد يبقى السوق عند السعر القديم. وهذا هو ffill() (الملء الأمامي). والفجوة الطويلة تعني غالبًا بيانات قديمة، لذا يملأ ffill(limit=2) يومين متتاليين على الأكثر. وتبقى قيم NaN في أول السلسلة تمامًا كما هي لعدم وجود سعر سابق يُرحَّل. والبديل هو الحذف: يزيل dropna() كل صف فيه فجوة، ولا ينظر dropna(subset=["AAPL"]) إلا إلى الأعمدة التي تهمك. الحذف آمن لكنه يقلل البيانات ويغيّر المسافات بين التواريخ.
msft = df["MSFT"]
print(msft.ffill().tolist()) # [410.0, 411.0, 411.0, 411.0, 415.0]
print(msft.ffill(limit=1).tolist()) # [410.0, 411.0, 411.0, nan, 415.0] only one day carried
print(len(df.dropna())) # 1 only one row is complete
print(len(df.dropna(subset=["AAPL"]))) # 3 rows where AAPL is knownالاستيفاء وما لا يجوز ملؤه أبدًا
يملأ interpolate() الفجوة بخط مستقيم بين القيمتين المعروفتين على جانبيها. يناسب الكميات الملساء (منحنى العائد أو مستوى التقلب) ونادرًا الأسعار التي تقفز. احذر عند الأطراف: افتراضيًا ينسخ interpolate() آخر قيمة في قيم NaN الأخيرة، وهذا اختلاق للبيانات. ويقيّده limit_area="inside" بالفجوات بين قيمتين معروفتين. ولا تملأ بـ 0 عشوائيًا: سعر 0 انهيار، وعائد 0 يوم ثابت، وتغيّر fillna(0) في الجدول الخطأ كل متوسط تحسبه لاحقًا بصمت.
gappy = pd.Series([nan, nan, 10.0, nan, nan, 16.0, nan])
print(gappy.interpolate().tolist())
# [nan, nan, 10.0, 12.0, 14.0, 16.0, 16.0] the last 16.0 is invented
print(gappy.interpolate(limit_area="inside").tolist())
# [nan, nan, 10.0, 12.0, 14.0, 16.0, nan] only between known values# Filling with 0 is a decision, not a default: it changes the statistics
print(gappy.fillna(0).tolist()) # [0.0, 0.0, 10.0, 0.0, 0.0, 16.0, 0.0]
print(gappy.mean()) # 13.0 NaN are skipped
print(gappy.fillna(0).mean()) # 3.7142857142857144 the zeros drag the average downbfill() وinterpolate() قيمًا لاحقة للفجوة، فيوظفان في تاريخ الفجوة معلومة لم تكن موجودة بعد. وفي الاختبار الرجعي هذا انحياز استباقي. استخدم ffill() لكل ما تراه الاستراتيجية آنيًا، واحتفظ بالاستيفاء لتنظيف بيانات تاريخية تُحلَّل مجتمعةً.df.isna().sum()تعدّ الفجوات لكل عمود؛ انظر إلى البيانات قبل أن تقرر كيف تعالجها.ffill(limit=n)ترحّل آخر قيمة معروفة (الخيار الصحيح للأسعار)؛ وdropna(subset=...)تزيل الصفوف الناقصة.interpolate(limit_area="inside")تسدّ الفجوات الداخلية فقط؛ ولا تملأ بـ 0 أو بالمستقبل بحكم العادة.
التالي: اكتشاف القيم الشاذة، أي إيجاد القيم الموجودة التي لا يمكن أن تكون صحيحة.
المختبر: ما الذي يُقيَّم
- عُدّ الفجوات في كل عمود باستخدام
count_missing25 نقطة - رحّل آخر سعر إلى الأمام باستخدام
forward_fill25 نقطة - احتفظ بالصفوف الموثوقة فقط باستخدام
complete_rows25 نقطة - سُدّ الفجوات داخل البيانات باستخدام
fill_gaps25 نقطة