الأسبوع 10: تحليل استكشافي موجَّه — مجموعة بيانات تايتانيك
🎯 أهداف التعلّم
بنهاية هذا الأسبوع ستكون قادرًا على:
- تحميل وتنظيف مجموعة بيانات بشكل حقيقي (
titanic.csv) من البداية للنهاية. - الإجابة عن أسئلة تحليلية محددة باستخدام الاختيار، التصفية، و
.groupby()معًا. - تقسيم عمود مستمر إلى نطاقات بـ
pd.cutللتحليل المُجمَّع. - إعادة إنتاج بنية دفتر ملاحظات تحليل استكشافي كلاسيكي على طراز Kaggle، خلية تلو الأخرى، وتلخيص النتائج بلغة إنجليزية بسيطة.
الدرس
هذا الأسبوع أقل عمدًا في "مفهوم جديد، صياغة جديدة" وأكثر في "طبّق كل شيء، بالتسلسل، على مجموعة بيانات واحدة." أصبحت مجموعة بيانات تايتانيك (المصادر هنا) معيارًا قياسيًا للمبتدئين لهذا السبب بالتحديد: إنها صغيرة، لديها عمود نتيجة واضح (Survived)، ولديها نسيج فوضوي واقعي كافٍ (أعمار مفقودة، أنواع مختلطة) يحتاج كل أداة من هذا القسم.
الخطوة 1: التحميل والفحص
import pandas as pd
df = pd.read_csv("titanic.csv")
df.shape
df.head()
df.info()
df.describe()
قبل تحليل أي شيء، اسأل دائمًا: كم صفًا، أي أعمدة، أي أنواع بيانات، ما المفقود؟ هذه مادة الأسبوعين 6 و8، مُطبَّقة كأول خطوة بالضبط في أي تحليل حقيقي — ليست شكلية، بل الأساس الذي يعتمد عليه كل شيء بعدها.
الخطوة 2: معالجة البيانات المفقودة
df.isna().sum()
عادة ما يحتوي Age قيمًا مفقودة في مجموعة البيانات هذه. بدلًا من حذف تلك الصفوف تمامًا (فقدان معلومات أخرى عن أولئك الركاب)، خيار شائع هو الملء بمتوسط العمر الوسيط (median) — الوسيط، وليس المتوسط الحسابي، لأن توزيعات الأعمار غالبًا ما تكون منحرفة ببضعة ركاب صغار جدًا أو كبار جدًا في السن:
df["Age"] = df["Age"].fillna(df["Age"].median())
الخطوة 3: طرح أسئلة، الإجابة بالتصفية + التجميع
سؤال: هل اختلف معدل النجاة حسب فئة الراكب؟
df.groupby("Pclass")["Survived"].mean()
متوسط كل مجموعة لعمود 0/1 هو بالضبط معدل النجاة لتلك المجموعة — نفس حيلة "متوسط عمود شبيه منطقي = نسبة" التي ستستخدمها باستمرار في تحليل البيانات.
سؤال: هل اختلف معدل النجاة حسب الجنس؟
df.groupby("Sex")["Survived"].mean()
سؤال: بين الركاب الذين دفعوا أعلى 25% من الأجرة، ما كان معدل النجاة؟
fare_threshold = df["Fare"].quantile(0.75)
top_fare_passengers = df[df["Fare"] >= fare_threshold]
top_fare_passengers["Survived"].mean()
يدمج هذا تصفية (الأسبوع 7) مع تلخيص (.mean()، الأسبوع 6) — نفس النمط ذي الخطوتين لكل سؤال تقريبًا ستطرحه على مجموعة بيانات حقيقية: ضيّق للصفوف التي تهمك، ثم لخّصها.
الخطوة 4: دمج أبعاد التجميع
تقبل .groupby() قائمة من الأعمدة، مُقسِّمة حسب كل مجموعة من قيمها دفعة واحدة:
df.groupby(["Pclass", "Sex"])["Survived"].mean()
يُجيب هذا عن سؤال أكثر تحديدًا من أي تجميع بمفرده: هل يبقى أثر الفئة على النجاة داخل كل جنس، أم يختفي بمجرد التحكم بالجنس؟
الخطوة 5: تقسيم عمود مستمر بـ pd.cut
Age مستمر، لكن "معدل النجاة حسب العمر بالضبط" دقيق جدًا لقراءته بسهولة — التجميع حسب نطاقات العمر (تقطيع منفصل، نفس فكرة صناديق (bins) المدرج التكراري) عادة أكثر فائدة. تفعل pd.cut هذا بالضبط:
df["age_group"] = pd.cut(df["Age"], bins=[0, 12, 18, 35, 60, 100],
labels=["Child", "Teen", "Adult", "Middle-aged", "Senior"])
df.groupby("age_group")["Survived"].mean()
تُعطي bins حواف كل نطاق؛ تُسمّيها labels. الآن age_group مجرد عمود فئوي آخر، قابل للاستخدام مع .groupby() تمامًا مثل Pclass أو Sex.
الخطوة 6: تلخيص النتائج بلغة بسيطة
لا يكتمل التحليل الاستكشافي الحقيقي حتى تصبح أرقامه جملة يمكن لشخص آخر قراءتها دون إعادة تشغيل كودك — نفس الانضباط الذي يُعامله إطار عمل المسار الصعب للتحليل الاستكشافي كمحوري:
class_survival = df.groupby("Pclass")["Survived"].mean()
sex_survival = df.groupby("Sex")["Survived"].mean()
print(f"Overall survival rate: {df['Survived'].mean():.1%}")
print(f"1st class survival rate: {class_survival[1]:.1%}, "
f"3rd class survival rate: {class_survival[3]:.1%}")
print(f"Female survival rate: {sex_survival['female']:.1%}, "
f"male survival rate: {sex_survival['male']:.1%}")
{value:.1%} هي مواصفة تنسيق f-string — قدّم الأسبوع 1 من بايثون 101 :.2f؛ تعني .1% بالمثل "كنسبة مئوية، منزلة عشرية واحدة"، مُحوّلة 0.629 إلى "62.9%" تلقائيًا.
⚠️ أخطاء شائعة
- الإجابة عن سؤال بشريحة بيانات خاطئة. تحقق دائمًا مرتين أن شرط تصفية منطقي يقول فعليًا ما تقصده —
df["Age"] < 18وdf["Age"] <= 18تُعطيان إجابتين مختلفتين (وإن كانتا متشابهتين)، والفرق يهم لحالات الحدود. - نسيان أن صناديق
pd.cutنصف مفتوحة باتجاه محدد. افتراضيًا، صناديقpd.cutهي(يسار، يمين]— الحافة اليسرى مستبعدة، اليمنى مُضمَّنة — يستحق التحقق إن كانت قيمة قد تقع بالضبط على حد. - الإبلاغ عن متوسط مجموعة دون الإبلاغ عن حجمها أيضًا. معدل نجاة يبدو دراميًا لمجموعة من 3 ركاب يستحق ثقة أقل بكثير من نفس المعدل لمجموعة من 300 — انظر دائمًا لـ
.count()جنبًا إلى جنب مع.mean()عند مقارنة المجموعات، نفس الحذر الذي يُشدّد عليه الأسبوع 6 من المسار الصعب.
🧩 تحديات
احسب معدل النجاة حسب ميناء Embarked. أي ميناء كان له أعلى معدل نجاة في مجموعة البيانات هذه؟
قارن معدل نجاة الركاب تحت 18 عامًا بالركاب 18 عامًا فأكثر.
باستخدام التجميع المدمج ["Pclass", "Sex"]، هل لا تزال فئة الراكب مهمة للنجاة داخل الراكبات تحديدًا؟ اقرأ الصفوف ذات الصلة من النتيجة.
أنشئ عمودًا جديدًا family_size كـSibSp + Parch + 1 (الإخوة/الأزواج + الآباء/الأبناء + الراكب نفسه)، ثم احسب معدل النجاة مُجمَّعًا حسب family_size.
باستخدام عمود age_group من الخطوة 5، ما معدل النجاة لصندوق "Child"؟ كيف يقارن بإجابة التحدي 2 لديك للركاب تحت 18؟
أعد تجميع معدل نجاة Pclass، لكن هذه المرة أدرج كلًا من متوسط وعدد الركاب في كل فئة، في استدعاء .agg(...) واحد — بحيث يمكنك الحكم على مدى ثقتك برقم كل فئة.
🤔 أسئلة سقراطية
- عادة ما تُظهر نتيجة
.groupby(["Pclass", "Sex"])المدمجة فجوة أكبر بكثير حسب الجنس منها حسب الفئة بمفردها. بماذا يوحي هذا عن أي متغيّر كان يقوم بمعظم "العمل" في نتائج التجميع أحادية المتغيّر السابقة؟ - ملء قيم
Ageالمفقودة بالوسيط يفترض أن الأعمار المفقودة ليست مختلفة بشكل منهجي عن المعروفة. هل يمكنك التفكير في سبب قد يجعل ذلك الافتراض خاطئًا لمجموعة البيانات هذه تحديدًا (أي سبب يجعل أنواعًا معينة من الركاب أكثر احتمالًا لعمر مفقود)؟ - أجبت للتو عن عدة أسئلة تحليلية حقيقية باستخدام فقط أدوات من الأسابيع 6-9. أي دالة واحدة (
.groupby()، القناع المنطقي،.fillna()،.merge()) انتهى بك الأمر لاستخدامها أكثر؟ هل يتطابق هذا مع توقعك لأي مهارة pandas هي الأهم عمليًا؟ - حواف صناديق
pd.cut([0, 12, 18, 35, 60, 100]) اختِيرت بشكل عشوائي إلى حد ما في هذا الدرس. كم تعتقد أن معدل نجاة "Child" قد يتغير لو نقلت حد الطفل/المراهق من 12 إلى، لنقل، 15؟ بماذا يوحي هذا عن الشفافية بخصوص خيارات التقسيم إلى صناديق في تقرير حقيقي؟ - تُبلغ الخطوة 6 بلغة بسيطة فقط عن متغيّرين (الفئة، الجنس) رغم أنك استكشفت عدة أخرى في التحديات. لو كنت تكتب هذا لشخص لم يرَ الأرقام الخام قط، أي نتيجة إضافية واحدة من التحديات تعتبرها الأكثر استحقاقًا للتضمين، ولماذا تلك بالذات؟
✅ اختبار الأسبوع
✅ اختبار الأسبوع
🎉 أنهيت مسار Pandas وتحليل البيانات العادي — والدورة كاملة، إن أخذت المسار العادي في كلا القسمين. توجّه إلى تقدّمي لرؤية أوسمتك، وإن أنهيت كل أسبوع، افتح مشاريع المشروع الختامي: تثبيت بايثون فعليًا وبناء أول وكيل ذكاء اصطناعي خاص بك.