الوحدة 5 — pandas: السلاسل وإطار البيانات والفهرسة
pandas هي الأداة التي يقضي فيها ممارس البيانات معظم يومه. مفهومياً الأمر بسيط: إطار البيانات (DataFrame) مصفوفة NumPy ثنائية الأبعاد تحمل صفوفها وأعمدتها تسميات. كلّ القوّة — والفخاخ القليلة — تنبع من هذه الفكرة.
Series وDataFrame: الكائنان
السلسلة (Series) عمود واحد: قيم NumPy زائد فهرس من التسميات. وإطار البيانات مجموعة سلاسل تتقاسم الفهرس نفسه.
import pandas as pd
df = pd.read_csv("orders.csv", parse_dates=["date"])
df.shape # (12480, 6) — صفوف، أعمدة
df.dtypes # نوع كلّ عمود — اقرأه بانتظام
df["amount"] # عمود واحد ← Series
df[["client", "amount"]] # عدّة أعمدة ← DataFrame
أول ما يُفع ل أمام أيّ ملفّ مجهول يتلخّص في أربعة أوامر:
df.head() # الصفوف الخمسة الأولى — كيف تبدو البيانات؟
df.info() # الأنواع وغير الفارغ والذاكرة — هل الأعمدة منمّطة صحيحاً؟
df.describe() # إحصاءات الأعمدة الرقمية — رتب المقادير والقيم القصوى
df["country"].value_counts() # توزيع عمود فئوي — القيم والأخطاء الإملائية
تكشف هذه الأوامر الأربعة جلّ المشكلات قبل أن تصير أخطاء: عمود المبالغ المقروء نصّاً (بسبب «N/A» أو فاصلة عشرية)، والتاريخ الباقي سلسلة نصّية، والفئة المكتوبة بثلاث صيغ.
loc وiloc: انتقاء بلا لبس
تقدّم pandas وصولين صريحين، والانضباط في استخدامهما يجنّب معظم الالتباس:
df.loc[42, "amount"] # بالتسميات: تسمية الفهرس 42، عمود "amount"
df.iloc[0, 3] # بالمواضع: الصفّ الأول، العمود الرابع
df.loc[df["country"] == "المغرب", ["client", "amount"]] # ترشيح + أعمدة
df.iloc[:100] # أول 100 صفّ
يصبح الفرق حاسماً ما إن يكفّ الفهرس عن كونه 0، 1، 2…: بعد ترتيب أو ترشيح، loc[5] يعني التسمية 5 (وقد تكون في أيّ مكان)، وiloc[5] الصفّ السادس. قاعدة بسيطة: loc افتراضياً، وiloc حين تفكّر بالمواضع.
الترشيح: الأقنعة المنطقية، كما في NumPy
آلية الوحدة 4 تنطبق كما هي، مع التسميات:
large = df[df["amount"] > 1000]
target = df[(df["amount"] > 1000) & (df["country"] == "كندا")] # الأقواس إلزامية
recent = df[df["date"] >= "2026-01-01"] # مقارنات تواريخ مباشرة
gulf = df[df["country"].isin(["السعودية", "الإمارات", "قطر"])]
no_email = df[df["email"].isna()]
isin تستبدل سلاسل |؛ وisna()/notna() تختبران النواقص. والنسبة تُقرأ مباشرة: (df["amount"] > 1000).mean().
إنشاء الأعمدة وتحويلها
df["amount_incl_tax"] = df["amount"] * 1.15 # موجَّه
df["year"] = df["date"].dt.year # مِلحق التواريخ
df["domain"] = df["email"].str.split("@").str[1] # مِلحق السلاسل
df["segment"] = np.where(df["amount"] > 1000, "مميّز", "عادي")
المِلحقان .dt (التواريخ) و.str (السلاسل) يوجّهان عمليات كانت ستستلزم حلقة. apply بدالّة على كلّ صفّ موجودة، لكنها الملاذ الأخير: أبطأ مئة مرّة من العمليات الموجَّهة، ولا تُبرَّر إلا لمنطق غير قابل فعلاً للاختزال.
تعديل نتيجة ترشيح (sub_df = df[mask] ثم sub_df["x"] = …) يطلق أشهر تحذيرات pandas: لا تضمن pandas هل تعدّل نسخة أم الجدول الأصلي. الحركتان اللتان تجنّبانه: .copy() صريحة حين تريد جزءاً مستقلاً، و**df.loc[mask, "x"] = value** حين تريد تعديل ال أصل. إنها النتيجة المباشرة للعروض/المراجع في الوحدتين 2 و4.
الترتيب وإعادة التسمية وإعادة الفهرسة
df.sort_values("amount", ascending=False) # ترتيب بسيط
df.sort_values(["country", "amount"], ascending=[True, False]) # ترتيب متعدّد
df.rename(columns={"amt": "amount"}) # إعادة تسمية مقروءة
df.reset_index(drop=True) # فهرس يعود 0..n-1 بعد ترشيح/ترتيب
نقطة معمارية مهمّة: هذه الطرق تعيد إطار بيانات جديداً بدل تعديل الأصل. والأسلوب الاصطلاحي يسلسل التحويلات، وكلّ خطوة تبقى قابلة للفحص:
top_clients = (
df[df["date"] >= "2026-01-01"]
.groupby("client")["amount"].sum()
.sort_values(ascending=False)
.head(20)
)
هذه السلسلة — ترشيح، تجميع، ترتيب، رأس — هي الجملة النمطية لتحليل pandas؛ وgroupby الذي يحرّكها موضوع الوحدة 7.
الخلاصة
- إطار البيانات = مصفوفة NumPy + تسميات صفوف (فهرس) وأعمدة؛ والسلسلة = عمود واحد.
- على كلّ ملفّ جديد:
headوinfoوdescribeوvalue_counts— أربعة أوامر تكشف الأنواع الخاطئة والنواقص والفئات الملوّثة. locبالتسميات وilocبالمواضع؛ أقنعة منطقية بأقواس، وisinوisna.- أعمدة جديدة بعمليات موجَّهة ومِلحقي
.dtو.str؛ وapplyملاذاً أخيراً. .copy()أوdf.loc[mask, col] = …ضدّ SettingWithCopyWarning؛ تحويلات مسلسلة قابلة للفحص خطوة خطوة.
الوحدة التالية: التنظيف المنهجي — القيم الناقصة والتكرارات والأنواع — الخطوة التي تقرّر موثوقية كلّ ما بعدها.