إنتقل إلى المحتوى الرئيسي

الأسبوع 7: الاختيار، التصفية، والفهرسة

🔍 نظرت الأسبوع الماضي إلى الجدول كاملًا. هذا الأسبوع تتعلم طرح أسئلة عليه.

🎯 أهداف التعلّم

بنهاية هذا الأسبوع ستكون قادرًا على:

  • اختيار صفوف وأعمدة بـ .loc و.iloc.
  • تصفية الصفوف باستخدام قناع منطقي (boolean mask)، النظير في pandas لترميز بناء المجموعات.
  • دمج شروط متعددة بـ &، |، و~.
  • استخدام .isin() و.between() لاختصارات تصفية شائعة.

الدرس

.loc مقابل .iloc

كلاهما يختار صفوفًا/أعمدة، لكن بنوعين مختلفين من العنونة:

df.loc[0, "name"] # بالعلامة: الصف المُعنون 0، العمود المُعنون "name"
df.iloc[0, 0] # بالموضع: أول صف، أول عمود، بغض النظر عن العلامات
df.loc[0:2] # الصفوف المُعنونة من 0 حتى 2، شاملة
df.iloc[0:2] # الصفوف عند المواضع 0، 1 — نهاية غير شاملة، مثل تقطيع بايثون

.loc شاملة على كلا الطرفين لأنها تعنون بـالعلامة، لا بالموضع — فرق مهم يسهل تفويته عن تقطيع بايثون المعتاد نصف المفتوح (الذي تتبعه .iloc). كلاهما يقبل دمجًا لمُحدِّدات صفوف وأعمدة، مُصفّيًا كلا المحورين في استدعاء واحد:

df.loc[0:2, "name"] # الصفوف 0-2، فقط عمود name، كـSeries
df.loc[0:2, ["name", "quiz1"]] # الصفوف 0-2، عمودان، كـDataFrame

الأقنعة المنطقية: ترميز بناء المجموعات في pandas

تذكّر ترميز بناء المجموعات: {xS:P(x)}\{x \in S : P(x)\}. في pandas، شرط مثل df["score"] >= 60 يُنتج Series من قيم True/Falseقناع منطقي — وفهرسة DataFrame بذلك القناع تُبقي فقط الصفوف حيث هو True:

mask = df["quiz1"] >= 60
mask # Series من True/False، واحدة لكل صف
df[mask] # فقط الصفوف حيث quiz1 >= 60

# تُكتب أكثر شيوعًا في سطر واحد:
df[df["quiz1"] >= 60]

هذه مباشرة الصيغة البرمجية لـ {rowdf:row.quiz160}\{ \text{row} \in df : \text{row.quiz1} \ge 60 \} — نفس فكرة تصفية list comprehension في بايثون 101، لكن تعمل على عمود كامل دفعة واحدة بدلًا من التكرار عنصرًا بعنصر. يمكن أيضًا دمج قناع مع .loc لتصفية الصفوف واختيار الأعمدة في استدعاء واحد:

df.loc[df["quiz1"] >= 60, ["name", "quiz1"]] # فقط الطلاب الناجحون، هذان العمودان فقط

دمج الشروط

استخدم & (و)، | (أو)، ~ (ليس) — وليس and/or/not بايثون، التي لا تعمل عنصرًا-بعنصر على Series. يحتاج كل شرط أقواسه الخاصة بسبب أسبقية العوامل:

df[(df["quiz1"] >= 60) & (df["quiz2"] >= 60)] # نجح في كلا الاختبارين
df[(df["quiz1"] < 60) | (df["quiz2"] < 60)] # رسب في واحد على الأقل
df[~(df["quiz1"] >= 60)] # لم ينجح في quiz1 — نفس df["quiz1"] < 60

اختصارات التصفية: .isin() و.between()

نمطا تصفية شائعان لهما دوال مخصصة أوضح للقراءة بدلًا من سلاسل |/مقارنات:

df[df["name"].isin(["Amina", "Sara"])] # الصفوف حيث الاسم إحدى قيم قائمة
df[df["quiz1"].between(60, 80)] # الصفوف حيث 60 <= quiz1 <= 80، شاملة

df["name"].isin([...]) هو المكافئ المُوجَّه لاختبار الانتماء value in some_list في بايثون 101، مُطبَّقًا على name كل صف دفعة واحدة — ويُغنيك عن كتابة (df["name"] == "Amina") | (df["name"] == "Sara") يدويًا.

اختيار الأعمدة

df["name"] # عمود واحد، كـSeries
df[["name", "quiz1"]] # عدة أعمدة، كـDataFrame (لاحظ الأقواس المزدوجة)

⚠️ أخطاء شائعة

  • استخدام and/or من بايثون بدلًا من &/|. df["quiz1"] >= 60 and df["quiz2"] >= 60 تُطلق ValueError: The truth value of a Series is ambiguous — تتوقع and/or في بايثون قيمة واحدة True/False، وليس Series كاملة منها.
  • نسيان الأقواس حول كل شرط. df[df["quiz1"] >= 60 & df["quiz2"] >= 60] (بلا أقواس) فخ أسبقية — & ترتبط أقوى من >=، لذا تُحلَّل هذه بشكل مختلف جدًا عمّا قصدت. ضع دائمًا أقواسًا حول كل شرط عند الدمج بـ&/|.
  • الخلط بين .loc[0:2] (شاملة) و.iloc[0:2] (غير شاملة). هذا هو أشيع خطأ منفرد لـ.loc/.iloc — تحقق دائمًا مرتين أيهما تستخدم كلما بدا عدد صفوف التقطيع خاطئًا بواحد.
  • أقواس مفردة عندما تقصد DataFrame. df["name", "quiz1"] (أقواس مفردة، فاصلة بالداخل) غير صالحة — تحتاج صيغة الأقواس المزدوجة df[["name", "quiz1"]].

🧩 تحديات

باستخدام students-normal.csv، اختر كل الصفوف حيث quiz1 يساوي 90 أو أكثر.

اختر الطلاب الذين نجحوا (≥60) في الاختبارات الثلاثة دفعة واحدة، بدمج ثلاثة شروط.

اختر أول 3 صفوف من DataFrame باستخدام .iloc. ثم جرّب .loc[0:3] — كم صفًا تُعيد، ولماذا يختلف ذلك عن .iloc[0:3]؟

اختر عمودي name وquiz1 معًا، كـDataFrame (وليس Series واحدة).

باستخدام .isin()، اختر صفوف ثلاثة طلاب محددين بالاسم (اختر أي ثلاثة أسماء من مجموعة البيانات).

باستخدام .loc مع قناع منطقي وقائمة أعمدة في نفس الاستدعاء، اختر فقط عمودي name وquiz1 للطلاب الذين رسبوا في quiz1 (تحت 60).

🤔 أسئلة سقراطية

  • لماذا تُطلق df[df["quiz1"] >= 60 and df["quiz2"] >= 60] (باستخدام and بايثون) خطأً، بينما تعمل df[(df["quiz1"] >= 60) & (df["quiz2"] >= 60)] (باستخدام &)؟ ما الذي تحاول and فعله بـSeries كاملتين ولا يُعطي معنى؟
  • القناع المنطقي هو نفسه فقط Series من قيم True/False، بنفس شكل فهرس صفوف DataFrame. ماذا سيحسب mask.sum()، ولماذا قد يكون ذلك الرقم ذا معنى؟
  • كون .loc[0:2] شاملة بينما .iloc[0:2] غير شاملة مصدر شائع لأخطاء الحدود. هل يمكنك التفكير في حالة لا تكون فيها علامات الصفوف أعدادًا صحيحة حتى (مثلًا بعد تصفية ما) — ماذا سيعني .loc[0:2] حينها؟
  • تُنتج df["name"].isin([...]) وسلسلة مقارنات | نفس الصفوف. بخلاف كونها أقصر للكتابة، هل يمكنك التفكير في سبب قد يجعل .isin() أيضًا أقل عرضة للخطأ لقائمة بها قيم كثيرة؟
  • تنفي ~ قناعًا منطقيًا. هل ~(df["quiz1"] >= 60) دائمًا مطابقة تمامًا لـdf["quiz1"] < 60؟ ما الذي قد يجعلهما مختلفتين لو احتوى العمود قيمًا مفقودة (NaN) — موضوع يغطيه الأسبوع القادم بعمق؟

✅ اختبار الأسبوع

✅ اختبار الأسبوع

1. أي عامل يدمج قناعين منطقيين في pandas (وليس and/or بايثون)؟
2. تختار .loc بـ:
3. تعمل df[df["score"] >= 60] لأن df["score"] >= 60 تُنتج:
4. تُعيد df[["name", "quiz1"]] (أقواس مزدوجة):
5. تختار df["quiz1"].between(60, 80) الصفوف حيث quiz1: