رصد القيم المفقودة والصفوف المكررة ومعالجتها

القيم المفقودة والصفوف المكررة: فحص الخلايا الفارغة والصفوف المتكررة قبل اختيار المعالجة.

لـرصد القيم المفقودة/الخانات الفارغة والصفوف المكررة ومعالجتها، ابدأ بقياس ما ينقص البيانات وفهم ما يتكرر فيها. فالخلية الفارغة لا تعني صفرًا، وقد يشترك طالبان في الخصائص نفسها. لذلك قد يغيّر الحذف أو التعويض التلقائي معنى الجدول. سنستخدم pandas لفحص جودة البيانات، ومقارنة عدة قرارات والاحتفاظ بسجل التعديلات والتحويلات المنجزة.

قبل أن تبدأ. يمكنك تعديل خلايا Python وتشغيلها في هذه الصفحة، باستثناء خلية إعداد المكتبات. شغّلها بالترتيب: يُحمَّل Python عند التشغيل الأول، ثم تتشارك الخلايا الذاكرة نفسها. تبدأ جلسة (session) جديدة عند إعادة تحميل الصفحة أو إعادة تشغيل Python. للعمل محليًا، اتبع دليل Python وuv وVS Code وJupyter، واختر النواة الخاصة ببيئتك .venv.

إعداد المكتبات — خلية غير قابلة للتعديل. في الـNotebook المحلي، تتحقق هذه الخلية من وجود pandas و openpyxl، وتستخدم uv لتثبيت الحزم الناقصة فقط في بيئة Python الخاصة بالنواة المختارة. لا تعيد تثبيت الحزم المتاحة. وفي هذه الصفحة، تجهّز بيئة التنفيذ في المتصفح المكتبات عند التشغيل الأول؛ ولا تشغّل uv على حاسوبك.

# Check for libraries before installing anything.
from importlib.util import find_spec
import subprocess
import sys

missing = [name for name in ("pandas", "openpyxl") if find_spec(name) is None]
if missing:
    subprocess.run(
        ["uv", "pip", "install", "--python", sys.executable, *missing],
        check=True,
    )
else:
    print("pandas and openpyxl are already installed.")
pandas and openpyxl are already installed.

1. تحميل بيانات مناسبة للمهمة

تبدأ المعالجة باختيار الأعمدة اللازمة. في مجموعة الانحدار، يكون الهدف Exam_Score، ويُستبعد Result عند قراءة الملف. ونحتفظ بالمتغيرات التسعة عشر الأخرى لفحص القيم المفقودة والصفوف المتكررة. تحمّل الدالة read_excel ورقة Données في كائن DataFrame، وهو بنية البيانات الجدولية في pandas.

# Read the workbook placed next to the notebook.
import pandas as pd

filename = "StudentPerformanceFactors_Reg_Classif.xlsx"
df = pd.read_excel(
    filename, sheet_name="Données", engine="openpyxl",
    usecols=lambda name: name != "Result",
)

# To use CSV, replace the Excel loading block with:
# df = pd.read_csv("StudentPerformanceFactors_Reg_Classif.csv",
#                  usecols=lambda name: name != "Result")
print("Observations:", len(df))
print("Columns:", df.shape[1])
Observations: 6607
Columns: 20

نحصل على 6607 ملاحظات و20 عمودًا. لاستخدام CSV، استبدل كتلة pd.read_excel(...) بسطرَي البديل pd.read_csv(...) بعد إزالة علامة # من بدايتهما. لا حاجة إلى قراءة الملفين ودمجهما؛ فهما يحتويان على الملاحظات نفسها وبالترتيب نفسه.

احتفظ بالملف الأصلي دون تعديل. ستشير الأسماء complete_rows، documented وworkshop إلى نتائج منفصلة. يتيح لك ذلك مقارنة كل قرار بالمصدر والتراجع عنه دون الحاجة إلى استرجاع ملف استُبدل محتواه.

2. رصد القيم المفقودة وعدّها

القيمة المفقودة هي معلومة غير متاحة لملاحظة ومتغير معيّنين. تمثلها pandas بعلامات منها NaN أو pd.NA، بحسب نوع العمود. استخدم isna() لرصدها؛ إذ تُنتج جدولًا من القيم المنطقية، تكون فيه True عند مواضع الفقدان وFalse في المواضع الأخرى. يشرح توثيق البيانات المفقودة هذه التمثيلات.

يختلف غياب المعلومة عن القيمة المسجّلة: فصفر جلسات دعم هو عدد معلوم، بينما تعني الخلية المفقودة أن العدد لم يُسجَّل. وبالمثل، تظل الإجابة No عن نشاط ما فئة معلومة. وتعويض كل القيم المفقودة بصفر أو بـNo يضيف معلومة لا يوفرها الملف.

فحص القيم المفقودة في ثماني ملاحظات

لنعرض أول ثمانية صفوف غير مكتملة والأعمدة الثلاثة المعنية. يحافظ الاختيار على ترتيب الملف. يبدأ فهرس الجدول الأصلي من الصفر، وتحتفظ التصفية بتسميات هذا الفهرس؛ لذلك يبدأ المقتطف بالفهرس 33 وتظهر فيه أرقام غير متتالية. يساعد الفهرس على تحديد صف في الجدول المحمّل، لكنه ليس معرّفًا للطالب.

# Inspect missing entries actually present in the file.
missing_columns = [
    "Teacher_Quality", "Parental_Education_Level", "Distance_from_Home"
]
incomplete_rows = df.isna().any(axis=1)
print(df.loc[incomplete_rows, missing_columns].head(8).to_string())
    Teacher_Quality Parental_Education_Level Distance_from_Home
33           Medium                  College                NaN
127             NaN              High School                Far
240          Medium                      NaN           Moderate
275          Medium              High School                NaN
316          Medium                  College                NaN
359          Medium              High School                NaN
380          Medium              High School                NaN
396             NaN                  College           Moderate

لم تُسجَّل المسافة في الصف ذي الفهرس 33، ولا جودة المعلم في الصف ذي الفهرس 127، ولا المستوى التعليمي للوالدين في الصف ذي الفهرس 240. تشير العلامة نفسها NaN إلى غياب معلومة في أعمدة مختلفة، دون أن توضّح سبب الغياب أو القيمة التي كان ينبغي تسجيلها.

عدّ الخلايا والصفوف

تعدّ isna().sum() القيم المفقودة في كل عمود. ولتحديد الصفوف المتأثرة، تختبر any(axis=1) وجود قيمة مفقودة واحدة على الأقل في الصف. تُحسب النسب أدناه بقسمة كل عدد على الملاحظات البالغ عددها 6607، لا على الصفوف غير المكتملة وحدها.

# Count by column, then distinguish cells from observations.
missing_mask = df.isna()
counts = missing_mask.sum()
summary = pd.DataFrame({
    "Missing": counts,
    "Percentage": (counts / len(df) * 100).round(2),
})
print(summary.loc[summary["Missing"] > 0].to_string())
print("Missing cells:", int(missing_mask.sum().sum()))
print("Affected observations:", int(missing_mask.any(axis=1).sum()))
                          Missing  Percentage
Teacher_Quality                78        1.18
Parental_Education_Level       90        1.36
Distance_from_Home             67        1.01
Missing cells: 235
Affected observations: 229
المتغيرالقيم المفقودةنسبتها من الملاحظات
Teacher_Quality781.18%
Parental_Education_Level901.36%
Distance_from_Home671.01%
القيم المفقودة في متغيرات مجموعة الانحدار.

لدينا 235 خلية مفقودة تخص 229 ملاحظة، أي نحو 3.47% من الصفوف البالغ عددها 6607. يختلف العددان لأن الملاحظة الواحدة قد تفتقد عدة قيم. جميع الأعمدة العددية في هذه المجموعة مكتملة؛ لذلك لا توجد فيها قيم عددية مفقودة تحتاج إلى تعويض في هذه النسخة من الملف.

القيم المفقودة: 78 و90 و67 خلية بحسب المتغير، أي 235 خلية موزعة على 229 ملاحظة متأثرة.
عدّ الخلايا المفقودة وعدّ الملاحظات المتأثرة عمليتان مختلفتان.

قبل تفسير هذه الأعداد، تحقّق من قواعد إدخال البيانات لدى المصدر. فقد تحتاج سلسلة تحتوي على مسافات أو رمز مثل ? إلى قاعدة قراءة خاصة؛ ولا تستطيع isna() وحدها اكتشاف جميع الرموز الخاصة بمجال العمل. نعدّ هنا القيم التي تعرّفت عليها القراءة بوصفها مفقودة في المصنف المرفق. ولا يكفي الجدول وحده لتحديد سبب فقدانها.

3. اختيار معالجة القيم المفقودة

تحتاج معالجة القيم المفقودة إلى قرار مبرّر. هل يمكنك استرجاع المعلومة من مصدر موثوق؟ وهل المتغير ضروري للتحليل؟ وهل يتركز الفقدان في مجموعة معيّنة؟ لا تكفي نسبة صغيرة من القيم المفقودة للإجابة. فقد يقلّل حذف الصفوف المعلومات المتاحة ويغيّر المجتمع المدروس، وقد يؤدي التعويض إلى تشابه مصطنع بين الملاحظات.

يقصد بـتعويض القيم المفقودة (Imputation) إحلال قيمة مختارة أو مقدّرة محل معلومة مفقودة. ويتوقف اختيار القاعدة على معنى المتغير وهدف التحليل.

يمكنك أيضًا استبعاد متغير من التحليل إذا تعذّر استخدامه أو كانت صلته بالهدف محدودة. وهذا يحذف عمودًا، بينما يحذف استبعاد الملاحظات صفوفًا؛ فالمعلومات التي نفقدها تختلف بين الحالتين. ينبغي تبرير الاختيار بالهدف والمعلومات المتاحة.

الإجراءما يتيحهالحدود التي ينبغي مراعاتها
استرجاع المعلومةتصحيح القيم بالرجوع إلى مصدر متحقق منه.لا تخمّن الإجابة.
حذف صفقصر التحليل على الملاحظات التي تتوافر فيها المعلومات المطلوبة.وثّق الاستبعادات وآثارها.
الاحتفاظ بفئة «غير معروف»إظهار غياب المعلومة في متغير فئوي.لا تسترجع الفئة الجديدة المعلومة الأصلية.
تعويض قيمة متغير عدديتعويض قيمة مفقودة بعدد، مثل الوسيط أو المتوسط الحسابي.راعِ الوحدة ومجال القيم؛ فقد يتغيّر التوزيع.
تعويض قيمة متغير فئوياستخدم المنوال، أي القيمة الأكثر تكرارًا، أو تسمية صريحة مثل «غير معروف».لا تحسب متوسط أسماء الفئات. يظل التعويض قاعدة متفقًا عليها في التحليل.
معالجة غياب نص حراسترجع النص من مصدره أو احتفظ بغيابه بصورة صريحة.لا تخترع جملة، ولا تعوّض كل النصوص المفقودة تلقائيًا بالنص الأكثر تكرارًا.
اختر الإجراء بحسب المعلومات المتاحة ومعنى المتغير.

يوجّه نوع المتغير اختيار المعالجة: فالمدة مثل Hours_Studied تعبّر عن كمية، بينما يعبّر Teacher_Quality عن فئة. وقد تحتوي السلسلة النصية على فئة مثل Medium أو على نص حر. وبالمقابل، قد تُخزَّن فئة في صورة رمز عددي، دون أن يمنحها ذلك معنى الكمية. لذا اختر القاعدة بحسب معنى الحقل، لا بحسب نوعه في pandas وحده.

قياس أثر الحذف قبل اتخاذ القرار

تعيد dropna() هنا الصفوف التي لا تحتوي على أي قيمة مفقودة. وتقصر الوسيطة subset الشرط على أعمدة محددة، كما يوضّح توثيق dropna. لنقارن قاعدتين مستقلتين:

# Compare two removal rules using separate copies.
complete_rows = df.dropna()
with_quality = df.dropna(subset=["Teacher_Quality"])

print("No missing entries:", len(complete_rows))
print("Teacher_Quality recorded:", len(with_quality))
print("Rows removed by dropna():", len(df) - len(complete_rows))
No missing entries: 6378
Teacher_Quality recorded: 6529
Rows removed by dropna(): 229

سيُبقي حذف جميع الصفوف غير المكتملة 6378 صفًا، ويحذف 229 صفًا. أما اشتراط وجود قيمة في Teacher_Quality وحده فسيُبقي 6529 صفًا. يخدم الخياران حاجتين مختلفتين؛ فإذا لم يستخدم التحليل المسافة، فإن غيابها وحده لا يبرّر استبعاد الملاحظة منه.

لا تثبت أي من النتيجتين أن الصفوف المحتفظ بها تمثل الصفوف المستبعدة. ومن الفحوص المفيدة مقارنة خصائص المجموعتين عندما تتوافر المعلومات. يقيس الحساب هنا عدد الصفوف التي سنفقدها، ولا يثبت أن الحذف استراتيجية مناسبة لنموذج لاحق.

معالجتان للقيم المفقودة: حذف 229 صفًا غير مكتمل يُبقي 6378 ملاحظة، وتوثيق القيم المفقودة يحتفظ بالصفوف البالغ عددها 6607.
النسختان مستقلتان: تبدأ كل منهما من المصدر، ولا تُجمع نتائجهما.

توثيق القيم المفقودة والاحتفاظ بمواضعها

الأعمدة الثلاثة التي تحتوي على قيم مفقودة هنا فئوية. في نسخة مخصصة للاستكشاف، يمكننا كتابة Unknown في الخلايا المفقودة وإضافة مؤشر للفقدان، وهو عمود منطقي يحفظ مواضعها الأصلية. تعوّض fillna القيم المعروفة بوصفها مفقودة فقط، وتبقى الفئات المسجّلة دون تغيير. راجع توثيق fillna.

# Retain rows and make missing information visible.
documented = df.copy()
for column in missing_columns:
    documented[column + "_missing"] = df[column].isna()
    documented[column] = df[column].fillna("Unknown")

print("Observations retained:", len(documented))
print("Originally missing:", int(documented.filter(like="_missing").sum().sum()))
print("Remaining missing:", int(documented.isna().sum().sum()))
Observations retained: 6607
Originally missing: 235
Remaining missing: 0

تحتفظ النسخة بالملاحظات البالغ عددها 6607. ولا تزال مؤشراتها الثلاثة تسجّل القيم المفقودة الأصلية البالغ عددها 235، رغم أن خلايا الجدول أصبحت تحمل تسمية. إذن تعني عبارة «لا قيم مفقودة متبقية» أن التمثيل قد تغيّر، ولا تعني أننا استرجعنا المعلومات المفقودة.

قد تحتاج التسمية Unknown إلى ترميز قبل التدريب، بحسب النموذج المختار. وقد تجمع أيضًا أسبابًا مختلفة لغياب المعلومات. يتيح الاحتفاظ بسجل مراجعة الإعداد عندما يتغيّر التوثيق أو السؤال المطروح.

تعويض متغير فئوي بفئته الأكثر تكرارًا

من القواعد البسيطة للمتغير الفئوي استخدام المنوال، أي الفئة الأكثر تكرارًا. يعرض توثيق التعويض في scikit-learn هذه الاستراتيجية، إلى جانب المتوسط والوسيط للمتغيرات العددية.

# Illustrate replacement with the most frequent category.
quality_mode = df["Teacher_Quality"].mode().iloc[0]
imputed_quality = df["Teacher_Quality"].fillna(quality_mode)

print("Observed mode:", quality_mode)
print("Values replaced:", int(df["Teacher_Quality"].isna().sum()))
print("Missing after replacement:", int(imputed_quality.isna().sum()))
Observed mode: Medium
Values replaced: 78
Missing after replacement: 0

في هذه النسخة، تُعوَّض القيم المفقودة في Teacher_Quality، وعددها 78، بالقيمة Medium. تبدأ الخلية من المصدر ولا تعدّل النسخة documented. توضّح النتيجة آلية التعويض، لكنها لا تثبت أن Medium كانت القيمة الفعلية لهذه الملاحظات. ولو تعددت القيم المنوالية، لاختارت iloc[0] إحداها بحسب الترتيب المعاد؛ وهو قرار آخر يحتاج إلى توثيق.

عند إجراء تقييم تنبؤي لاحقًا، يجب حساب قيمة التعويض، سواء أكانت متوسطًا أم وسيطًا أم منوالًا، باستخدام بيانات التدريب وحدها، ثم إعادة استخدامها في مجموعة التحقق أو الاختبار. فحسابها من البيانات كلها يستخدم معلومات مخصصة للتقييم، وهذا تسرب للبيانات. يجمع Pipeline خطوات الإعداد والنموذج في سلسلة واحدة، ويساعد على الحفاظ على هذا الفصل، كما يشرح دليل الأخطاء الشائعة في scikit-learn. وتبقى الخلية السابقة مثالًا وصفيًا دون تقييم نموذج.

4. فحص الصفوف المكررة قبل حذفها

الصف المكرر تمامًا هو تكرار لصف كامل: تتطابق القيم في جميع أعمدة مجموعة البيانات المفحوصة. ولا يكفي اشتراك صفين في ساعات الدراسة أو الحضور أو بعض الحقول الأخرى لاعتبارهما مكررين. تضع duplicated() افتراضيًا علامة على النسخ الإضافية، وتحتفظ بالأولى مرجعًا. ولا تتعامل مع الفهرس بوصفه متغيرًا من متغيرات الجدول. يقصر المعامل subset المقارنة على أعمدة مختارة؛ فتشير النتيجة إلى قيم مشتركة في تلك الحقول دون أن تثبت تكرار الصف كاملًا. لا نستخدمه لتحديد التكرارات التامة في هذا المثال. راجع توثيق duplicated.

تقارن duplicated() البيانات كما هي مخزّنة. وقد تعتبر المواضع المفقودة في الأعمدة نفسها متطابقة أثناء المقارنة. لذلك لا يثبت تطابق القيم المعلومة ومواضع الفقدان في صفين أن المعلومات غير المسجّلة فيهما متطابقة. ولا يغني هذا الفحص التقني عن التحقق من مصدر السجلات.

# Look for repeated rows across all columns in the view.
print("Exact duplicates:", int(df.duplicated().sum()))
print("Observations after deduplication:", len(df.drop_duplicates()))
Exact duplicates: 0
Observations after deduplication: 6607

لا يحتوي المصدر على أي صف مكرر تمامًا في مجموعة الانحدار؛ لذلك تحتفظ drop_duplicates() بالصفوف البالغ عددها 6607. ولا يثبت ذلك أن الشخص لا يظهر أكثر من مرة، إذ لا يوفر الملف معرّفًا للطالب يتيح التحقق من هويته.

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

التمييز بين الصفوف المكررة والمعرّفات المتكررة

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

تتوقف القاعدة على ما يمثله الصف. فإذا ظهر الشخص بصورة مشروعة مرة في كل فترة، فقد يتكرر معرّفه الشخصي، بينما يجب أن يكون الزوج «الشخص والفترة» فريدًا. لا يوفر المصنف معرّفًا للطالب، ولا يحل فهرس pandas محله. لذلك نفحص التكرارات عبر الأعمدة العشرين لمجموعة الانحدار، دون اختراع معرّف.

فحص تكرارات معلومة المصدر

لنأخذ الملاحظات الثماني الأولى من مجموعة الانحدار ونضف نسخة كاملة من أول صفين، بما فيها الأعمدة العشرون. تخص هذه العملية workshop وحده. تجمع pd.concat الصفوف كاملة، وتُسنِد ignore_index=True فهرسًا جديدًا دون تغيير قيمها.

# Copy two entire rows, including all twenty columns.
workshop = pd.concat([df.head(8), df.head(2)], ignore_index=True)
print("Rows in the working example:", len(workshop))
print("Extra copies:", int(workshop.duplicated().sum()))
print("Rows in repeated groups:", int(workshop.duplicated(keep=False).sum()))
print("Row 0 matches its copy at 8:", workshop.iloc[0].equals(workshop.iloc[8]))
print("Row 1 matches its copy at 9:", workshop.iloc[1].equals(workshop.iloc[9]))
Rows in the working example: 10
Extra copies: 2
Rows in repeated groups: 4
Row 0 matches its copy at 8: True
Row 1 matches its copy at 9: True

يحتوي المثال التطبيقي على عشرة صفوف، منها نسختان إضافيتان. عند استخدام keep=False، تضع duplicated علامة على الصفوف الأربعة في مجموعتي التكرار: الظهور الأول لكل صف ونسخته. إذن ليست الصفوف الأربعة كلها زائدة. وتؤكد مقارنتا equals تطابق الصفين في الموضعين 0 و 8، ثم الصفين في الموضعين 1 و 9، عبر الأعمدة العشرين.

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

حذف النسخ المؤكدة فقط

نعرف هنا مصدر التكرار؛ فقد أضفنا النسختين للتو. تحتفظ drop_duplicates(keep="first") بالظهور الأول وتعيد جدولًا جديدًا. يوضّح توثيق drop_duplicates هذا السلوك.

# Remove the two copies whose origin is known.
unique_workshop = workshop.drop_duplicates(keep="first")
print("Before:", len(workshop))
print("After:", len(unique_workshop))
print("Rows removed:", len(workshop) - len(unique_workshop))
Before: 10
After: 8
Rows removed: 2

يعود المقتطف إلى ثمانية صفوف. أما الحذف باستخدام keep=False فيزيل جميع مرات الظهور في كل مجموعة، بما فيها أول صفين؛ وهذه قاعدة مختلفة. وفي البيانات المجمّعة فعليًا، افحص أيضًا التواريخ والمفاتيح الخاصة بمجال العمل والمصادر قبل اختيار السجل الذي ستحتفظ به.

قد يجعل التعويض صفوفًا كانت مختلفة متطابقة. لذلك افحص أصل التكرارات في المصدر قبل ملء الخلايا المفقودة، ولا تُعِد إزالة التكرارات تلقائيًا بعد كل تعويض. لا تصبح الملاحظات المختلفة نسخًا مؤكدة لمجرد أن المعالجة جعلتها متشابهة.

5. توثيق القرارات والاستعداد للخطوات التالية

إبقاء المهمتين منفصلتين

نفحص إعداد مجموعة التصنيف بصورة مستقلة. تحتوي هذه المجموعة على الهدف Result بتسمياته المرفقة Success أو Fail، وتستبعد Exam_Score. تقرأ الخلية التالية التسميات كما هي دون إعادة بنائها. وإذا اخترت CSV، فاستخدم البديل المعلّق في هذه الخلية أيضًا:

# Read the supplied labels, excluding the other target.
df_classification = pd.read_excel(
    filename, sheet_name="Données", engine="openpyxl",
    usecols=lambda name: name != "Exam_Score",
)
# CSV alternative:
# df_classification = pd.read_csv("StudentPerformanceFactors_Reg_Classif.csv",
#                                usecols=lambda name: name != "Exam_Score")
print("Missing Result targets:", int(df_classification["Result"].isna().sum()))
print("Exact duplicates:", int(df_classification.duplicated().sum()))
Missing Result targets: 0
Exact duplicates: 0

لا توجد قيم مفقودة في هذا الهدف، ولا صفوف مكررة تمامًا في مجموعة التصنيف. ولو غابت قيمة الهدف في نسخة أخرى، فإن إسناد الفئة الأكثر شيوعًا إليها لا ينشئ تسمية متحققًا منها. عندئذ ينبغي استرجاع المعلومة أو استبعاد الصف من التدريب الخاضع للإشراف المعني. لذا لا يُعالَج الهدف تلقائيًا بوصفه متغير إدخال غير مكتمل.

تصدير نسخة موثّقة

لنحتفظ بالنسخة التي تجعل غياب القيم الفئوية صريحًا. يضم تصديرها أعمدة الانحدار العشرين وثلاثة مؤشرات للفقدان، أي 23 عمودًا. تمنع index=False إضافة فهرس pandas إلى CSV. ويحمل الناتج اسمًا يختلف عن اسم الملف الأصلي.

# Save a result separately from the original file.
output = "Regression_AbsencesDocumentees.csv"
documented.to_csv(output, index=False, encoding="utf-8")
print("File created:", output)
print("Shape:", documented.shape)
File created: Regression_AbsencesDocumentees.csv
Shape: (6607, 23)

يمكنك تنزيل نسخة CSV التي توثّق القيم المفقودة، الناتجة عن الـNotebook المرفق. تحتفظ النسخة بالملاحظات البالغ عددها 6607. يساعد هذا الملف على مواصلة الاستكشاف، ولا يحل محل البيانات الأصلية أو الإعداد الخاص بالتدريب.

أرفق النتيجة بملاحظة قصيرة تذكر الملف المصدر والأعمدة المعنية، والأعداد قبل المعالجة وبعدها، والقاعدة المطبقة وحدودها. في نسختنا، نذكر أن القيم الفئوية المفقودة البالغ عددها 235 أخذت التسمية Unknown، وأن ثلاثة مؤشرات تحفظ مواضعها، وأنه لم يُحذف أي صف من المصدر. أما النسختان المحذوفتان فتخصان المثال التطبيقي وحده.

تمرين: عدّ حالات الفقدان المتعدد والصفوف المتكررة

انطلاقًا من مجموعة الانحدار df، عدّ الملاحظات التي تفتقد قيمتين على الأقل ضمن الأعمدة الثلاثة المعنية. ثم عدّ النسخ الإضافية في workshop. فسّر لماذا لا يساوي عدد الصفوف المعلّمة باستخدام keep=False مباشرة عدد الصفوف التي ينبغي حذفها.

عرض تصحيح Python وتفسيره
# Count multiple missing entries and extra copies.
multiple_missing = df[missing_columns].isna().sum(axis=1).ge(2)
print("At least two missing values:", int(multiple_missing.sum()))
print("Extra copies in the working example:", int(workshop.duplicated().sum()))
At least two missing values: 6
Extra copies in the working example: 2

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

أخطاء شائعة

  • الخلط بين غياب المعلومة والصفر. الصفر قيمة مسجّلة؛ فلا تستخدمه للتعويض إلا إذا برّر معنى الحقل ذلك.
  • تطبيق dropna() دون فحص الأعمدة. قِس الخسارة أولًا، ثم حدّد الحقول اللازمة للتحليل.
  • استخدام == NaN لرصد القيم المفقودة. استخدم isna()، التي تتعامل مع علامات الفقدان المعروفة لدى pandas.
  • حذف صفوف تشترك في بعض القيم. لا تثبت المقارنة الجزئية أن الملاحظات متطابقة.
  • عرض قيمة معوّضة كما لو أنها معلومة مسترجعة. احتفظ بقاعدة التعويض، وبمؤشر للفقدان عند الحاجة.
  • تشغيل خلية قبل تحميل بياناتها. يعني خطأ NameError المرتبط بـ df هنا أنك تحتاج إلى تشغيل خلية قراءة البيانات في الجلسة الحالية.

ما ينبغي تذكّره ومواصلة المسار

افحص البيانات قبل تحويلها: عدّ القيم المفقودة، وميّز بين الخلايا والملاحظات، ثم برّر كل حذف أو تعويض. يشمل التكرار التام صفًا كاملًا، ويُفحص المعرّف المتكرر وفق قاعدة التفرد المتوقعة. نتائج المصنف محددة: 235 قيمة مفقودة موزعة على 229 صفًا، وثلاثة متغيرات فئوية معنية، ولا صفوف مكررة تمامًا في أي من المجموعتين المفحوصتين.

المصادر والتوثيق

  • Jake VanderPlas، Python Data Science Handbook، الطبعة الثانية، نسخة Early Release، الفصل الثالث «Data Manipulation with Pandas»، قسم «Handling Missing Data». يعرض هذا القسم علامات القيم المفقودة وعمليات الرصد والحذف والتعويض. ويتاح عرض لهذه المفاهيم على موقع المؤلف.
  • Aurélien Géron، Hands-On Machine Learning with Scikit-Learn and PyTorch، 2025، الفصل الثاني «End-to-End Machine Learning Project»، قسم «Clean the Data»، الصفحتان 70–71. يميّز المقطع بين حذف الصفوف وحذف متغير والتعويض؛ وتُتعلّم قيم التعويض من بيانات التدريب ثم تُستخدم في المجموعات الأخرى. وتتاح ملفات Notebook المرافقة للكتاب في مستودع المؤلف.

أُنتجت المخرجات باستخدام Python 3.14.8، وpandas 3.0.6 وopenpyxl 3.1.5، على المصنف المرفق. يشرح التوثيق المرتبط في المتن الدوال المستخدمة. للتوسع، راجع تمثيل البيانات المفقودة في pandas، واستراتيجيات التعويض في scikit-learn وتجنب تسرب البيانات. وتوفر صفحة مجموعة Student Performance Factors وصفها وترخيصها.