الوحدة 3 · تنظيف البيانات الحقيقية
القيم المفقودة
18 دقراءة 4 دتمرين Python
الدرس في لمحة
اعثر على القيم المفقودة بـ isna، وافهم أيّ النصوص تحوّلها read_csv بصمت إلى NaN، واختر عن قصد بين استرجاعها أو وضع تسمية لها أو حذفها أو إبقائها بأنواع تقبل القيم المفقودة.
- 1العثور على الفجوات
- 2ما قرّرته read_csv نيابةً عنك
- 3تقرير ما تفعله
- 4الأنواع القابلة للقيم المفقودة: أعداد صحيحة يمكن أن تُفقد
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
افتح orders_messy.csv في جدول بيانات، فيسهل أن تفوتك الفجوات: خلية كمية فارغة هنا، وN/A في عمود القناة هناك. حمّله في pandas فتصبح تلك الفجوات NaN، تتدفّق بعدها بصمت عبر حساباتك: المجاميع تتخطّاها، والمتوسطات تتجاهلها، وfillna(0) مكتوبة على عجل تحوّل “لا نعرف” إلى “لم يُبَع شيء”. القيم المفقودة (missing values) ليست إزعاجًا تقنيًا تزيله من طريقك؛ كل واحدة منها سؤال عن العمل.
العثور على الفجوات
import pandas as pd
messy = pd.read_csv("orders_messy.csv")
print(messy.shape)
print(messy.isna().sum())
print(messy[messy.isna().any(axis=1)].head().to_string())تعيد isna() جدولًا (DataFrame) من القيم المنطقية، وجمعه يعدّ الفجوات في كل عمود: 9 كميات و7 قنوات. وتسأل isna().any(axis=1) لكل صف هل أيّ عمود فيه مفقود (axis=1 تعني “عبر الأعمدة”)، وهذا يتيح لك النظر إلى الصفوف المتضرّرة فعلًا. والنظر إليها مهم: الكميات التسع المفقودة تخصّ طلبات عادية عبر السنتين، لا يومًا واحدًا معطوبًا.
ما قرّرته read_csv نيابةً عنك
لا توجد في الملف الخام خلايا قناة فارغة. فيه النص N/A، وحوّلته pandas إلى NaN لأن N/A موجود في قائمتها الافتراضية لعلامات الفقد، مع الخلية الفارغة وNA وNaN وnull وNone وغيرها. هذا مفيد عادةً. وأحيانًا يكون خاطئًا: عمود رموز الدول سيفقد ناميبيا، التي رمزها في ISO هو NA. حمّل الملف دون التحويل لترى ما فيه حقًا:
import pandas as pd
raw = pd.read_csv("orders_messy.csv", keep_default_na=False)
print(raw["channel"].value_counts())
print((raw["quantity"] == "").sum(), "empty quantity cells")الآن يظهر N/A كقيمة حقيقية بعدد 7، وعمود الكمية كله نصوص، لأن الخلايا الفارغة بقيت نصوصًا فارغة. وللتحكّم في القائمة بدقة، مرّر na_values=["N/A", ""] مع keep_default_na=False.
تقرير ما تفعله
لا يوجد إصلاح يصلح لكل حالة. اطرح ثلاثة أسئلة بهذا الترتيب، وإذا كانت الإجابة عنها كلها “لا” فأبقِ القيمة مفقودة:
الاسترجاع يأتي أولًا لأنه الخيار الوحيد الذي يضيف معلومة. معرّفات الطلبات في الملف المُصدَّر موجودة أيضًا في جدولي orders وorder_items النظيفين لدى Cartwheel، فيمكن البحث عن الكميات المفقودة هناك؛ وستتعلّم عملية الدمج (merge) التي تفعل ذلك في الدرس 4.2. وإلى أن تستطيع استرجاع قيمة، لا تخترع واحدة.
بالتطبيق على الملف المُصدَّر: القناة المفقودة لا يمكن تخمينها من أي شيء آخر في الصف، لكن “لا نعرف القناة” حقيقة تستحق أن تبقى ظاهرة، فتحصل على تسمية صريحة. أما كود الكوبون الفارغ في جدول orders النظيف فأمر مختلف مرة أخرى: هناك يعني الفقد “لم يُستخدم كوبون”، وهذه إجابة حقيقية.
import pandas as pd
messy = pd.read_csv("orders_messy.csv")
messy["channel"] = messy["channel"].fillna("unknown")
print(messy["channel"].value_counts())
print(len(messy.dropna(subset=["quantity"])), "rows with a known quantity")تحذف dropna(subset=["quantity"]) الصفوف التي تنقصها الكمية فقط، وفي النتيجة التي تخزّنها فقط؛ أما dropna() العادية دون subset فتحذف الصف إذا كان أيّ عمود فيه مفقودًا، وهذا على الملف الخام سيرمي 16 صفًا، منها مجاميع سليمة تمامًا. احذف لتحليل محدّد، لا من نسختك الرئيسية.
وفخ آخر: لا تستطيع العثور على الفجوات بـ ==. القيمة NaN لا تساوي أي شيء، ولا حتى نفسها، فتكون messy["channel"] == np.nan مساوية لـ False في كل صف. استخدم دائمًا isna() وnotna().
الأنواع القابلة للقيم المفقودة: أعداد صحيحة يمكن أن تُفقد
حُمّل عمود الكمية على هيئة float64، فتُطبع الكميات 1.0 و2.0. هذا هو الأثر نفسه الذي رأيته مع referred_by في الدرس 2.2: NaN الكلاسيكية عدد عشري. ولدى pandas أيضًا أنواع قابلة للقيم المفقودة (nullable)، تُكتب بحرف كبير، وتخزّن علامة فقد مستقلة هي pd.NA:
import pandas as pd
messy = pd.read_csv("orders_messy.csv")
qty = messy["quantity"].astype("Int64")
print(qty.dtype, qty.iloc[32:35].tolist())
print(qty.sum(), qty.count(), len(qty))
print(round(qty.mean(), 2))يحتفظ Int64 بالأعداد الصحيحة كأعداد صحيحة، ويعرض الفجوات كـ <NA>. والحسابات التجميعية تتخطّى القيم المفقودة افتراضيًا، فتجمع sum() الكميات المعروفة الـ 159، ويخبرك الفرق بين count() وlen() بعدد المفقود بالضبط. وتضم العائلة نفسها Float64 وboolean وstring، وتحمّل pd.read_csv(..., dtype_backend="numpy_nullable") كل الأعمدة بهذه الطريقة. وسلوك واحد يجب أن تعرفه: المقارنات مع pd.NA تعيد <NA> بدلًا من False، لأن سؤال “هل الكمية المجهولة تساوي 1؟” لا إجابة صادقة عنه.
في التمرين تعدّ الفجوات وتضع لها تسميات وتغيّر نوعها. وفي الدرس التالي تنظّف الأعمدة النصية، حيث تأتي أسماء الدول بأربع كتابات مختلفة.
أهم النقاط
- تحوّل
read_csvافتراضيًا الخلايا الفارغة والنصوص مثلN/AوNAوnullإلى قيم مفقودة، فافحص الملف الخام حين تبدو الأعداد مفاجئة. df.isna().sum()تعدّ الخانات الفارغة في كل عمود؛ وdf[df.isna().any(axis=1)]تعرض الصفوف التي فيها أيّ منها.- كل قيمة مفقودة تحتاج قرارًا - استرجعها، أو ضع لها تسمية، أو احذف الصف، أو أبقِها مفقودة - والملء بـ 0 نادرًا ما يكون صحيحًا.
- النوع
Int64القابل للقيم المفقودة (nullable) يحتفظ بالأعداد الصحيحة كأعداد صحيحة ويعرض الفجوات كـ<NA>، بدلًا من تحويل العمود إلى أعداد عشرية. - المجاميع والمتوسطات والأعداد تتخطّى القيم المفقودة افتراضيًا، فيكون الفرق بين
count()وlen()هو عدد الفجوات بالضبط.