EduFlow
Python لتحليل البيانات
⁦0%⁩

الوحدة 4 · التحليل: التجميع والدمج وإعادة التشكيل

التجميع (groupby) والحسابات التجميعية المسمّاة

20 دقراءة 4 دتمرين Python

الدرس في لمحة

لخّص البيانات بحسب المجموعة باستخدام groupby، واحسب عدة إحصاءات مسمّاة دفعة واحدة باستخدام agg، واختر بشكل صحيح بين size و count، وبين نتيجة مفهرسة ونتيجة مسطّحة.

  1. 1التقسيم، والتطبيق، وضمّ النتائج
  2. 2عدة إحصاءات دفعة واحدة: الحسابات التجميعية المسمّاة
  3. 3الفرق بين size و count
  4. 4عدة مفاتيح ونتائج مسطّحة
  5. 5نتائج المجموعة على كل صف: transform

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

“ما متوسط قيمة الطلب لدينا؟” يبدو رقمًا واحدًا، لكن order_items ليس فيه عمود لقيمة الطلب: الطلب الذي فيه أربعة منتجات له أربعة صفوف. تحتاج أولًا صفًا واحدًا لكل طلب، يُجمع فيه إيراد الطلب من بنوده. و"الإيراد لكل منتج" و"الطلبات لكل قناة" و"المبيعات لكل دولة" أسئلة لها الشكل نفسه. في جدول البيانات كنت ستبني جدولًا محوريًا؛ وفي pandas تستخدم التجميع (groupby)، الـ method التي ستستدعيها أكثر من أي method أخرى.

التقسيم، والتطبيق، وضمّ النتائج

Python
import pandas as pd

items = pd.read_csv("order_items.csv")
items["revenue"] = items["quantity"] * items["unit_price"] * (1 - items["discount"])

by_product = items.groupby("product_id")["revenue"].sum()
print(by_product.sort_values(ascending=False).head(3).round(2))
print(round(by_product.sum(), 2), round(items["revenue"].sum(), 2))

اقرأ السطر الأوسط كثلاث خطوات. قسّم الصفوف إلى مجموعات، واحدة لكل product_id. طبّق حسابًا، sum، على عمود revenue في كل مجموعة. ضُمّ النتائج في Series فهرسها مفتاح المجموعة. المنتج 21، Standing Desk، حقّق 60,017.83 دولارًا، أي نحو ضعف المنتج الذي يليه.

السطر الأخير عادة تستحق أن تنسخها: مجاميع المجموعات تساوي المجموع الكلي. إذا لم تتساوَ، فهناك صفوف ضاعت (مثل الصفوف التي مفتاحها NaN، والتي تُسقطها groupby افتراضيًا) أو تكرّرت قبل أن تجمّع.

ستة بنود طلبات بمفاتيح A و A و B و A و C و C. يجمعها التقسيم في ثلاثة صناديق بحسب المفتاح. ويجمع التطبيق الإيراد في كل صندوق. ويُنتج الضمّ نتيجة من ثلاثة صفوف: A و B و C مع مجاميعها. الصفوف A 88.20 A 33.60 B 19.95 A 270.90 C 96.60 C 96.60 التقسيم A: ثلاثة صفوف B: صف واحد C: صفّان تطبيق sum 392.70 19.95 193.20 الضمّ A 392.70 B 19.95 C 193.20
تقسم groupby الصفوف بحسب المفتاح، وتطبّق دالة على كل مجموعة، وتضمّ النتائج

عدة إحصاءات دفعة واحدة: الحسابات التجميعية المسمّاة

رقم واحد لكل مجموعة نادرًا ما يكفي. تحسب agg مع الحسابات التجميعية المسمّاة (named aggregation) عدة أرقام، وتسمّي كل عمود ناتج:

Python
import pandas as pd

items = pd.read_csv("order_items.csv")
items["revenue"] = items["quantity"] * items["unit_price"] * (1 - items["discount"])

per_order = items.groupby("order_id").agg(
    revenue=("revenue", "sum"),
    lines=("order_item_id", "count"),
    units=("quantity", "sum"),
    max_discount=("discount", "max"),
)
print(per_order.head().round(2))
print(per_order.shape, "| average order value:", round(per_order["revenue"].mean(), 2))

كل كلمة مفتاحية عمود ناتج، وكل قيمة زوج: (عمود المُدخل، الدالة). تُعطى الدوال بأسمائها كنصوص: "sum" و"mean" و"median" و"min" و"max" و"count" و"size" و"nunique" و"first" و"last" و"std". للنتيجة صف لكل طلب، 2,066 صفًا، ومتوسط قيمة الطلب (AOV) هو 228.43 دولارًا، عبر كل الحالات، والإلغاءات منها، حتى الآن.

سترى أيضًا الأسلوب الأقدم agg({"revenue": "sum", "discount": "max"})، أو agg(["sum", "mean"]) على عمود واحد. إنهما يعملان، لكنهما يعيدان استخدام أسماء المُدخلات، فينتهي جدول فيه إحصاءان للعمود نفسه بعناوين مربكة. أما الحسابات التجميعية المسمّاة فتقول بالضبط ما هو كل عمود.

الفرق بين size و count

طريقتان للعدّ، وتجيبان عن سؤالين مختلفين:

Python
import pandas as pd

orders = pd.read_csv("orders.csv")
print(orders.groupby("channel").size())                   # rows per group
print(orders.groupby("channel")["coupon_code"].count())   # non-missing coupons per group

تعدّ size() الصفوف: 1,146 طلب ويب. وتعدّ count() القيم غير المفقودة في عمود: 208 من طلبات الويب تلك فيها كود كوبون. استخدم size (أو count على عمود لا يُفقد أبدًا، مثل المعرّف) لسؤال “كم عددها”، وcount لسؤال “كم منها له قيمة”.

عدة مفاتيح ونتائج مسطّحة

جمّع بقائمة أعمدة لتقاطع بُعدين. للنتيجة فهرس من مستويين (MultiIndex)، وتنشر unstack() المستوى الداخلي إلى أعمدة، فيُقرأ كجدول محوري:

Python
import pandas as pd

orders = pd.read_csv("orders.csv")
by_channel_status = orders.groupby(["channel", "status"]).size()
print(by_channel_status.head(4))
print(by_channel_status.unstack())

flat = orders.groupby("channel", as_index=False).agg(
    orders=("order_id", "count"),
    avg_fee=("shipping_fee", "mean"),
)
print(flat.round(2))

تُبقي as_index=False مفاتيح المجموعات أعمدة عادية بدلًا من نقلها إلى الفهرس، وهذا مفيد حين تغذّي النتيجة عملية دمج أو رسمًا بيانيًا أو تصدير CSV. وتفعل reset_index() الشيء نفسه لاحقًا على نتيجة مفهرسة.

نتائج المجموعة على كل صف: transform

أحيانًا تريد رقم المجموعة بجانب كل صف بدلًا من صف لكل مجموعة. سؤال “ما الحصة التي يمثّلها كل بند من طلبه؟” يحتاج أن يعرف كل بند مجموع طلبه. تجري transform حساب المجموعة نفسه، لكنها تعيد نتيجة محاذية للصفوف الأصلية:

Python
import pandas as pd

items = pd.read_csv("order_items.csv")
items["revenue"] = items["quantity"] * items["unit_price"] * (1 - items["discount"])
items["order_revenue"] = items.groupby("order_id")["revenue"].transform("sum")
items["share_of_order"] = items["revenue"] / items["order_revenue"]
print(items[["order_id", "revenue", "order_revenue", "share_of_order"]].head(5).round(3))

للطلب 1001 أربعة بنود، وبند Burr Coffee Grinder يحمل 62% من قيمته. القاعدة العملية: agg حين تريد جدول ملخّص، وtransform حين تريد عمودًا جديدًا في الجدول التفصيلي.

يبني التمرين جدول “صف لكل طلب” الذي ستستخدمه من الآن فصاعدًا. وفي الدرس التالي تدمجه مع orders وcustomers، لأن الأسئلة المهمة، مثل “الإيراد بحسب الدولة، دون الإلغاءات”، تحتاج أعمدة من أكثر من جدول.

أهم النقاط

  • يقسم التجميع (groupby) الصفوف إلى مجموعات بحسب مفتاح، ويطبّق حسابًا على كل مجموعة، ويضمّ النتائج في جدول واحد.
  • الحسابات التجميعية المسمّاة، agg(revenue=("revenue", "sum"), lines=("order_item_id", "count"))، تمنح كل عمود ناتج اسمًا واضحًا.
  • size() تعدّ الصفوف في كل مجموعة؛ وcount() تعدّ القيم غير المفقودة في عمود، فيختلفان حين يكون في العمود فجوات.
  • التجميع بعدة مفاتيح يعطيك MultiIndex؛ وas_index=False أو reset_index() تعيدان المفاتيح أعمدة عادية.
  • تحقّق من أن نتائج المجموعات تعود فتساوي المجموع الكلي، كي تعرف أنه لم يضِع صف ولم يُحسب مرتين.

قراءات إضافية

تحقّق من فهمك

3 أسئلة

السؤال 1 من 3

تعطي orders.groupby("channel")["coupon_code"].count() القيمة 208 للويب، لكن orders.groupby("channel").size() تعطي 1,146. لماذا؟

السؤال 2 من 3

أيّ استدعاء يعيد صفًا لكل طلب بعمودين اسمهما revenue وlines؟

السؤال 3 من 3

جمّعت items بحسب الطلب وجمعت الإيراد، ثم جمعت تلك النتيجة. المجموع الكلي أعلى بنسبة 3% من items["revenue"].sum(). ما التفسير الأرجح؟

تدريب

تمرين Python

ابنِ جدول "صف لكل طلب"

باستخدام items (مع إضافة عمود الإيراد مسبقًا)، ابنِ per_order: صف لكل order_id بالأعمدة revenue (المجموع) وlines (عدد بنود الطلب) وunits (مجموع quantity). ثم اجعل avg_order_value متوسط إيراد الطلب مقرّبًا إلى منزلتين عشريتين، وmulti_line_share حصة الطلبات التي فيها بندان أو أكثر مقرّبة إلى 3 منازل عشرية، وtop_product قيمة product_id صاحب أعلى إيراد إجمالي.

ملفات البيانات: order_items.csv

import pandas as pd

items = pd.read_csv("order_items.csv")
items["revenue"] = items["quantity"] * items["unit_price"] * (1 - items["discount"])

# 1. One row per order_id with columns: revenue (sum), lines (number of rows), units (sum of quantity)
per_order = None

# 2. Average order value (mean revenue per order), rounded to 2 decimals
avg_order_value = None

# 3. Share of orders with 2 or more lines, rounded to 3 decimals
multi_line_share = None

# 4. product_id with the highest total revenue
top_product = None
جارٍ تحميل المحرّر…

Ctrl/⌘ + Enter للتشغيل · يعمل في بيئة معزولة داخل متصفّحك. لا شيء يغادر جهازك.

(لا مخرجات)

الفحوص

  • في per_order 2,066 صفًا والأعمدة revenue و lines و units
  • إيراد الطلب 1001 يساوي 438.90
  • البنود والوحدات تعود فتساوي مجاميع جدول البنود
  • avg_order_value يساوي 228.43
  • multi_line_share يساوي 0.551
  • top_product يساوي 21