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

الوحدة 7 — الدمج والتجميع والجداول المحورية

تصل البيانات الحقيقية قطعاً: الطلبات في جدول، والعملاء في آخر، والمنتجات في ثالث. والأسئلة التي تهمّ أهل المهنة مجاميع: الإيراد لكلّ شريحة، ومتوسّط السلّة لكلّ شهر. تغطّي هذه الوحدة الآليتين اللتين تصلان القطع بالإجابات — وفخاخهما التي تنتج أرقاماً خاطئة دون رفع أيّ خطأ.

merge: دمج جدولين

enriched_orders = orders.merge(
clients,
on="client_id", # مفتاح الدمج
how="left", # نوع الدمج
)

الأنواع الأربعة ومتى تُستعمل:

how=ما يُحتفَظ بهالاستعمال النمطي
innerالمفاتيح الحاضرة في الجانبينتقاطع مصدرين موثوقين
leftكامل الجدول الأيسرإثراء بلا فقدان صفوف — الافتراضي العاقل
rightكامل الجدول الأيمننادر — أعد كتابته left معكوساً
outerكلّ شيء من الجانبينتدقيقات مطابقة المصادر

مع how="left"، تتلقّى الطلبات بلا عميل مطابق NaN في أعمدة العملاء — مرئي وقابل للتتبّع. أمّا مع inner فتختفي: inner غير المدروس هو أهدأ طريقة لفقدان 8% من الإيراد في تحليل.

الفحصان غير القابلين للتفاوض

عُدّ قبل وبعد. يجب أن يعيد left عدد صفوف الجدول الأيسر بالضبط؛ فإن أعاد أكثر فمفتاح اليمين فيه تكرارات وكلّ صفّ أيسر تضاعف — وستنتفخ المجاميع اللاحقة في صمت. وعُدّ غير المتطابق: result["عمود_من_اليمين"].isna().sum(). هذه الأسطر الأربعة من الضبط تمنع أغلى حادثي دمج.

assert len(result) == len(orders), "تضاعف صفوف: مفتاح يمين غير فريد"
print(f"بلا مطابقة: {result['segment'].isna().mean():.1%}")

حين يختلف اسما المفتاحين: left_on="client_id", right_on="id". وحين تتعايش أعمدة متجانسة الاسم: suffixes=("", "_client").

groupby: قسّم، طبّق، اجمع

النمط المفاهيمي: قسّم الجدول إلى فئات، طبّق تجميعاً على كلّ فئة، اجمع النتائج.

df.groupby("segment")["amount"].sum()          # تجميع واحد
df.groupby("segment")["amount"].agg(["count", "mean", "sum"]) # عدّة تجميعات

# عدّة مفاتيح وتجميعات مسمّاة — الصيغة الأكثر مقروئية
recap = df.groupby(["segment", "country"]).agg(
order_count=("order_id", "count"),
revenue=("amount", "sum"),
avg_basket=("amount", "mean"),
).reset_index()

reset_index() في النهاية تحوّل الناتج المفهرس جدولاً مسطّحاً — وهو ما تريده لاحقاً في الغالب (دمج، تصدير، رسم).

ويستحقّ التجميع الزمني ذكره: مع pd.Grouper أو resample ينتج السلاسل الشهرية التي تملأ كلّ التقارير:

monthly_revenue = df.set_index("date")["amount"].resample("ME").sum()

عادتا موثوقية: افحص أحجام الفئات (.size()) — متوسّطٌ على ثلاثة صفوف لا يزن وزن متوسّطٍ على ثلاثين ألفاً — وتذكّر أنّ NaN مستثناة من التجميعات، فقد يختلف count من عمود لآخر.

pivot_table: الجدول المحوري

لعرض تجميع في بُعدين — صفوف × أعمدة — كما في جداول البيانات:

tab = df.pivot_table(
values="amount",
index="segment", # الصفوف
columns="year", # الأعمدة
aggfunc="sum",
fill_value=0,
margins=True, # صفّ وعمود "All" للمجاميع
)

pivot_table هو groupby بعرضٍ مختلف: المحرّك نفسه والتقديم مختلف. وهو يجمع التكرارات (بخلاف pivot الذي يفشل إن وُجدت) — وهو المطلوب دائماً تقريباً. والعملية العكسية، melt، تفرد الجدول العريض إلى الصيغة الطويلة، المفضّلة لدى Seaborn ومعظم الأدوات.

التركيب: استعلام التحليل الكامل

تتسلسل الأدوات الثلاث طبيعياً، بأسلوب الوحدة 5 المتسلسل:

top_segments = (
orders
.merge(clients[["client_id", "segment"]], on="client_id", how="left")
.groupby("segment")
.agg(revenue=("amount", "sum"), clients=("client_id", "nunique"))
.assign(revenue_per_client=lambda d: d["revenue"] / d["clients"])
.sort_values("revenue_per_client", ascending=False)
)

دمجٌ فتجميعٌ فاشتقاقٌ فترتيب: إنه مكافئ pandas لاستعلام SQL كامل، والشكل الذي ستتّخذه معظم تحليلاتك. وكلّ خطوة من السلسلة قابلة للتشغيل وحدها للفحص — الميزة الكبرى لهذا الأسلوب على متغيّرٍ يُكتَب فوقه عشرين مرّة.

الخلاصة

  • merge بـhow="left" افتراضياً؛ وعُدّ دائماً الصفوف قبل/بعد ونسبة غير المتطابق — التضاعف بمفتاح مكرّر هو أغلى أخطاء الوحدة.
  • groupby = قسّم-طبّق-اجمع؛ تجميعات مسمّاة للمقروئية، وreset_index() في النهاية، وعين على أحجام الفئات.
  • resample للمجاميع الزمنية؛ وpivot_table للعرض المتقاطع؛ وmelt للعودة إلى الصيغة الطويلة.
  • التحليلات الحقيقية تركّب الثلاثة في سلسلة قابلة للفحص خطوة خطوة.

الوحدة التالية: التصوير البياني — تحويل هذه المجاميع إلى رسوم تُظهر التوزيع والاتجاه والشذوذ.