الوحدة 4 · التحليل: التجميع والدمج وإعادة التشكيل
التجميع (groupby) والحسابات التجميعية المسمّاة
20 دقراءة 4 دتمرين Python
الدرس في لمحة
لخّص البيانات بحسب المجموعة باستخدام groupby، واحسب عدة إحصاءات مسمّاة دفعة واحدة باستخدام agg، واختر بشكل صحيح بين size و count، وبين نتيجة مفهرسة ونتيجة مسطّحة.
- 1التقسيم، والتطبيق، وضمّ النتائج
- 2عدة إحصاءات دفعة واحدة: الحسابات التجميعية المسمّاة
- 3الفرق بين size و count
- 4عدة مفاتيح ونتائج مسطّحة
- 5نتائج المجموعة على كل صف: transform
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
“ما متوسط قيمة الطلب لدينا؟” يبدو رقمًا واحدًا، لكن order_items ليس فيه عمود لقيمة الطلب: الطلب الذي فيه أربعة منتجات له أربعة صفوف. تحتاج أولًا صفًا واحدًا لكل طلب، يُجمع فيه إيراد الطلب من بنوده. و"الإيراد لكل منتج" و"الطلبات لكل قناة" و"المبيعات لكل دولة" أسئلة لها الشكل نفسه. في جدول البيانات كنت ستبني جدولًا محوريًا؛ وفي pandas تستخدم التجميع (groupby)، الـ method التي ستستدعيها أكثر من أي method أخرى.
التقسيم، والتطبيق، وضمّ النتائج
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 افتراضيًا) أو تكرّرت قبل أن تجمّع.
عدة إحصاءات دفعة واحدة: الحسابات التجميعية المسمّاة
رقم واحد لكل مجموعة نادرًا ما يكفي. تحسب agg مع الحسابات التجميعية المسمّاة (named aggregation) عدة أرقام، وتسمّي كل عمود ناتج:
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
طريقتان للعدّ، وتجيبان عن سؤالين مختلفين:
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() المستوى الداخلي إلى أعمدة، فيُقرأ كجدول محوري:
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 حساب المجموعة نفسه، لكنها تعيد نتيجة محاذية للصفوف الأصلية:
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()تعيدان المفاتيح أعمدة عادية. - تحقّق من أن نتائج المجموعات تعود فتساوي المجموع الكلي، كي تعرف أنه لم يضِع صف ولم يُحسب مرتين.