EduFlow
Python لتحليل البيانات
⁦0%⁩

الوحدة 3 · تنظيف البيانات الحقيقية

القيم المفقودة

18 دقراءة 4 دتمرين Python

الدرس في لمحة

اعثر على القيم المفقودة بـ isna، وافهم أيّ النصوص تحوّلها read_csv بصمت إلى NaN، واختر عن قصد بين استرجاعها أو وضع تسمية لها أو حذفها أو إبقائها بأنواع تقبل القيم المفقودة.

  1. 1العثور على الفجوات
  2. 2ما قرّرته read_csv نيابةً عنك
  3. 3تقرير ما تفعله
  4. 4الأنواع القابلة للقيم المفقودة: أعداد صحيحة يمكن أن تُفقد

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

افتح orders_messy.csv في جدول بيانات، فيسهل أن تفوتك الفجوات: خلية كمية فارغة هنا، وN/A في عمود القناة هناك. حمّله في pandas فتصبح تلك الفجوات NaN، تتدفّق بعدها بصمت عبر حساباتك: المجاميع تتخطّاها، والمتوسطات تتجاهلها، وfillna(0) مكتوبة على عجل تحوّل “لا نعرف” إلى “لم يُبَع شيء”. القيم المفقودة (missing values) ليست إزعاجًا تقنيًا تزيله من طريقك؛ كل واحدة منها سؤال عن العمل.

العثور على الفجوات

Python
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. حمّل الملف دون التحويل لترى ما فيه حقًا:

Python
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.

تقرير ما تفعله

لا يوجد إصلاح يصلح لكل حالة. اطرح ثلاثة أسئلة بهذا الترتيب، وإذا كانت الإجابة عنها كلها “لا” فأبقِ القيمة مفقودة:

مسار قرار. أولًا: هل يمكن استرجاع القيمة من مصدر آخر؟ إذا نعم، استرجعها. ثانيًا: هل للفقد معنى، مثل عدم وجود كوبون؟ إذا نعم، ضع له تسمية. ثالثًا: هل الصف بلا فائدة بدونها في هذا التحليل؟ إذا نعم، احذفه لذلك التحليل. وإلا فأبقِها مفقودة. يمكن استرجاعها من مصدر آخر؟ هل لـ "مفقود" معنى ما؟ الصف بلا فائدة بدونها؟ أبقِها NA لا لا لا نعم نعم نعم استرجعها ضع لها تسمية احذف الصف (مؤقتًا) اربط بجدول نظيف fillna("unknown") dropna(subset=...)
ثلاثة أسئلة لكل قيمة مفقودة

الاسترجاع يأتي أولًا لأنه الخيار الوحيد الذي يضيف معلومة. معرّفات الطلبات في الملف المُصدَّر موجودة أيضًا في جدولي orders وorder_items النظيفين لدى Cartwheel، فيمكن البحث عن الكميات المفقودة هناك؛ وستتعلّم عملية الدمج (merge) التي تفعل ذلك في الدرس 4.2. وإلى أن تستطيع استرجاع قيمة، لا تخترع واحدة.

بالتطبيق على الملف المُصدَّر: القناة المفقودة لا يمكن تخمينها من أي شيء آخر في الصف، لكن “لا نعرف القناة” حقيقة تستحق أن تبقى ظاهرة، فتحصل على تسمية صريحة. أما كود الكوبون الفارغ في جدول orders النظيف فأمر مختلف مرة أخرى: هناك يعني الفقد “لم يُستخدم كوبون”، وهذه إجابة حقيقية.

Python
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:

Python
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() هو عدد الفجوات بالضبط.

قراءات إضافية

تحقّق من فهمك

3 أسئلة

السؤال 1 من 3

في الملف الفوضوي 9 كميات فارغة. شغّل زميلك fillna(0) على العمود قبل حساب متوسط الكمية لكل طلب. ماذا يحدث؟

السؤال 2 من 3

في ملف CSV الخام النص N/A في عمود القناة في 7 صفوف. بعد pd.read_csv، ماذا تعرض value_counts() له؟

السؤال 3 من 3

q = messy["quantity"].astype("Int64"). ماذا تعيد q.count() وlen(q) لـ 168 صفًا فيها 9 فجوات؟

تدريب

تمرين Python

عُدّ الفجوات وسمِّها وغيّر نوعها

حمّل الملف الفوضوي (تم ذلك نيابةً عنك). اجعل missing_by_col كائن Series يعدّ القيم المفقودة في كل عمود. وفي messy نفسه، استبدل القنوات المفقودة بـ "unknown"، وحوّل quantity إلى النوع "Int64" القابل للقيم المفقودة، مع إبقاء فجواته مفقودة. ثم اجعل avg_quantity متوسط الكميات المعروفة، مقرّبًا إلى منزلتين عشريتين. احتفظ بكل الصفوف الـ 168: لا حذف، ولا ملء للكميات.

ملفات البيانات: orders_messy.csv

import pandas as pd

messy = pd.read_csv("orders_messy.csv")

# 1. A Series: number of missing values in each column
missing_by_col = None

# 2. Replace missing channels with the label "unknown" (in messy itself)

# 3. Convert quantity to the nullable integer dtype "Int64" (in messy itself)

# 4. Average quantity of the rows where it is known, rounded to 2 decimals
avg_quantity = None
جارٍ تحميل المحرّر…

Ctrl/⌘ + Enter للتشغيل · يعمل في بيئة معزولة داخل متصفّحك. لا شيء يغادر جهازك.

(لا مخرجات)

الفحوص

  • missing_by_col تجد 9 كميات مفقودة و7 قنوات مفقودة
  • القنوات المفقودة أصبحت "unknown"
  • quantity من النوع Int64 وما زالت فيه فجواته التسع
  • avg_quantity يساوي 1.43
  • الصفوف الـ 168 كلها ما زالت موجودة