الأسبوع 6: أساسيات Series وDataFrame
🎯 أهداف التعلّم
بنهاية هذا الأسبوع ستكون قادرًا على:
- شرح ما هما
SeriesوDataFrame، وكيف يرتبطان بمتجه ومصفوفة. - بناء
DataFrameمن بنى بيانات بايثون، وتحميل واحد من CSV بـpd.read_csv. - فحص شكل
DataFrameوأعمدته وأنواع بياناته وفهرسه وإحصاءاته الملخّصة. - اختيار عمود واحد كـ
Series، ومعرفة الفرق بين الطريقتين الرئيسيتين لفعل ذلك.
الدرس
من قاموس-من-قوائم إلى DataFrame
حسب مشروع الأسبوع 5 المصغّر من بايثون 101 معدلات كل طالب باستخدام dict. DataFrame هي بنية pandas المُصمَّمة خصيصًا لهذا النوع من البيانات الجدولية — صفوف وأعمدة، بعلامات على كلا المحورين:
import pandas as pd
df = pd.DataFrame({
"name": ["Amina", "Youssef", "Sara"],
"score": [88, 74, 95],
})
df
يمكنك أيضًا بناء واحدة من قائمة قواميس — نفس شكل "قائمة السجلات" من مثال تضمين المجموعات في الأسبوع 3 من المسار العادي لبايثون 101:
records = [
{"name": "Amina", "score": 88},
{"name": "Youssef", "score": 74},
]
pd.DataFrame(records)
كلا الشكلين يُنتجان نفس نوع الكائن؛ أيهما أكثر ملاءمة يعتمد فقط على الشكل الذي تكون بياناتك عليه بالفعل — قاموس-من-قوائم عندما تفكر "عمود تلو الآخر"، وقائمة-من-قواميس عندما تفكر "سجل تلو الآخر".
Series هو عمود واحد بعلامة — فكّر فيه كمتجه ، إلا أن كل مُدخل يحمل أيضًا علامة (فهرسه)، لا موضعًا فقط:
df["score"] # Series
type(df["score"]) # pandas.core.series.Series
DataFrame هو جدول ثنائي الأبعاد من هذه الأعمدة يشترك في فهرس صفوف واحد — النظير الجدولي لمصفوفة ، إلا أن الأعمدة يمكن أن تحمل أنواع بيانات مختلفة وكلا المحورين يحملان علامات، لا مواضع رقمية فقط.
الفهرس (Index)
يمتلك كل DataFrame (وSeries) فهرس صفوف — العلامات على الحافة اليسرى — مرئي كلما طبعت واحدًا. افتراضيًا هو فقط 0, 1, 2, ...، لكن لا يجب أن يكون كذلك:
df.index # RangeIndex(start=0, stop=3, step=1) افتراضيًا
df_named = df.set_index("name") # استخدم عمود "name" كفهرس بدلًا من ذلك
df_named.loc["Amina"] # يمكنك الآن البحث عن صف بالاسم مباشرة
لا تُعدّل set_index الـdf في مكانه افتراضيًا — تُعيد DataFrame جديدًا بالتغيير، نفس نمط "يُعيد قيمة جديدة، لا يُعدّل" الذي رأيته بالفعل مع sorted() في بايثون 101.
قراءة CSV
يُحمَّل نفس students-normal.csv من بايثون 101 في استدعاء واحد — بلا حلقة csv.DictReader يدوية، بلا تحويلات int(...) يدوية:
df = pd.read_csv("students-normal.csv")
df.head() # أول 5 صفوف
df.tail(3) # آخر 3 صفوف
تستنتج pd.read_csv نوع بيانات كل عمود تلقائيًا (الأرقام تصبح int64/float64، النصوص تبقى object)، وهو معظم ما فعله الأسبوع 5 من المسار العادي لبايثون 101 يدويًا، مُنجَز لك في سطر واحد.
فحص DataFrame
مجموعة صغيرة من الدوال تُجيب عن "كيف تبدو مجموعة البيانات هذه فعليًا؟" قبل أن تفعل أي شيء آخر بها:
df.shape # (صفوف، أعمدة) — مثلًا (10, 4)
df.columns # أسماء الأعمدة
df.dtypes # نوع بيانات كل عمود
df.info() # الشكل + أنواع البيانات + عدّات القيم غير الفارغة، كلها دفعة واحدة
df.describe() # العدّ، المتوسط، الانحراف المعياري، الأدنى، الأرباع، الأعلى — للأعمدة الرقمية
يستحق df.describe() التمعّن فيه: المتوسط والانحراف المعياري هما بالضبط الإحصاءات التي تعرفها بالفعل من مقرر إحصاء، محسوبة فوريًا عبر عمود كامل بدلًا من يدويًا. يمكنك إعادة تسمية الأعمدة لاحقًا إن لم تكن أسماء الملف المصدر مريحة للعمل بها:
df = df.rename(columns={"quiz1": "quiz_1"})
⚠️ أخطاء شائعة
- نسيان أن معظم عمليات DataFrame تُعيد كائنًا جديدًا.
df.rename(...)،df.set_index(...)، وكثيرات غيرها لا تُغيّرdfنفسه إلا إن أعدت الإسناد (df = df.rename(...)) أو مرّرتinplace=True. - الخلط بين
df.shape(بلا أقواس) واستدعاء دالة..shapeخاصية، وليست دالة —df.shape()تُطلقTypeError. - افتراض أن
.describe()تغطي كل عمود. افتراضيًا تُلخّص فقط الأعمدة الرقمية؛ الأعمدة النصية تحتاج نظرة مختلفة (يغطي الأسبوع 8 تنظيفها وفحصها).
🧩 تحديات
حمّل students-normal.csv (من الأسبوع 5 لبايثون 101 — أعد استخدام نفس الملف) إلى DataFrame واطبع كم صفًا وعمودًا يحتوي.
اختر فقط عمود quiz1 كـSeries. ما الصياغتان المختلفتان لفعل هذا؟
احسب متوسط عمود quiz1 باستخدام دالة Series (وليس sum()/len() يدويًا).
شغّل df.describe() على DataFrame الطلاب. هل يتضمن عمود name؟ لماذا أو لماذا لا؟
اجعل name فهرس DataFrame الطلاب، ثم ابحث عن صف Amina مباشرة باسمها بدلًا من رقم الصف.
ابنِ DataFrame صغيرًا يدويًا (وليس من CSV) بعمودين، city وpopulation، لـ 3 مدن من اختيارك — باستخدام إما أسلوب قاموس-من-قوائم أو قائمة-من-قواميس.
🤔 أسئلة سقراطية
- تُقارَن
Seriesغالبًا بقائمة بايثون بعلامات. ماذا يمكنك فعله بـ.mean()/.std()الخاصتين بـSeries في استدعاء واحد لم تكن لتستطيع فعله بقائمة عادية دون كتابة دالتك الخاصة؟ - تُظهر
df.dtypesالنوع المستنتَج لكل عمود. ماذا قد يحدث خطأً لو احتوى عمود يبدو رقميًا (مثلquiz1) فعليًا على صف واحد بنص فيه، مثل"absent"؟ أي نوع بيانات ستستنتجه pandas على الأرجح للعمود كاملًا؟ - قضيت 5 أسابيع من بايثون 101 تبني منطق قراءة CSV وحساب المتوسطات يدويًا. أي أسطر بالتحديد من ذلك المنطق يستبدلها
pd.read_csv(...).describe()؟ هل يُفقَد أي شيء فعليًا باستخدام الاختصار، أم وقت فقط يُوفَّر؟ - تستبدل
df.set_index("name")الفهرس الرقمي الافتراضي بواحد ذي معنى. ماذا سيحدث خطأً لو كان عمودnameيحتوي قيمة مكررة — هل يمكن لا يزال تمييز طالبين مختلفين بعد ذلك؟