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

الوحدة 5 · الإجابة عن أسئلة حقيقية

الإحصاء الوصفي والتوزيعات

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

الدرس في لمحة

صِف عمودًا رقميًا بصدق باستخدام الوسيط والمئينات والتشتّت، وتعرّف على التوزيعات الملتوية والقيم الشاذة، واقرأ الارتباطات دون أن تخلط بينها وبين الأسباب.

  1. 1المتوسط والوسيط والمئينات
  2. 2الشكل دون رسم بياني
  3. 3القيم الشاذة: ضع علامة ثم قرّر
  4. 4الارتباط: التحرّك معًا ليس تسبّبًا

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

يسألك أحدهم: “ما الطلب النموذجي في Cartwheel؟” فتحسب المتوسط، 228 دولارًا، وتقول ذلك. ثم يصمّم فريق التسويق حدًّا للشحن المجاني حول 228 دولارًا، فيكتشف أن ثلثي الطلبات لا تصل إليه أبدًا. كان المتوسط صحيحًا والإجابة خاطئة، لأن قيم الطلبات ليست موزّعة بالتساوي حول متوسطها. وصف البيانات بصدق يعني وصف شكلها، لا مركزها فقط.

المتوسط والوسيط والمئينات

Python
import pandas as pd

items = pd.read_csv("order_items.csv")
orders = pd.read_csv("orders.csv", parse_dates=["order_date"])
items["revenue"] = items["quantity"] * items["unit_price"] * (1 - items["discount"])
orders = orders.merge(items.groupby("order_id", as_index=False)["revenue"].sum(), on="order_id", validate="one_to_one")
orders = orders[orders["status"] != "cancelled"]
value = orders["revenue"]

print(value.describe(percentiles=[0.1, 0.25, 0.5, 0.75, 0.9, 0.99]).round(2))
print("skew:", round(value.skew(), 2))

اقرأها من الوسط إلى الخارج. الوسيط (median) ‏(50%) هو 147 دولارًا: نصف الطلبات كلها أصغر منه. ويقول الرُّبيعان إن النصف الأوسط من الطلبات يقع بين 68 و303 دولارات. المئين التسعون 505 والمئين التاسع والتسعون 1,168، بينما الحد الأقصى 2,117. أما المتوسط (mean)، 228، فيقع فوق الوسيط بكثير بسبب ذلك الذيل الأيمن الطويل، ويؤكّد ذلك الالتواء الموجب (skew) البالغ 2.43. للقيمة النموذجية في البيانات الملتوية، اذكر الوسيط؛ وللمجاميع والتوقّعات، المتوسط هو الرقم الصحيح، لأن المتوسط × العدد = المجموع.

std، أي الانحراف المعياري، يساوي 241 دولارًا، أكبر من الوسيط نفسه. هذه علامة أخرى على توزيع (distribution) منتشر وملتوٍ؛ وفي هذه الحالة تصف المئينات التشتّت أفضل بكثير من “المتوسط زائد أو ناقص انحراف معياري واحد”.

الشكل دون رسم بياني

تعدّ value_counts مع الفئات عدد القيم التي تقع في كل نطاق، وهذا مدرّج تكراري في صيغة نصية:

Python
import pandas as pd

items = pd.read_csv("order_items.csv")
orders = pd.read_csv("orders.csv", parse_dates=["order_date"])
items["revenue"] = items["quantity"] * items["unit_price"] * (1 - items["discount"])
orders = orders.merge(items.groupby("order_id", as_index=False)["revenue"].sum(), on="order_id", validate="one_to_one")
orders = orders[orders["status"] != "cancelled"]

print(orders["revenue"].value_counts(bins=[0, 50, 100, 200, 400, 800, 3000]).sort_index())
رسم أعمدة لأعداد الطلبات بحسب نطاق القيمة: 338 طلبًا حتى 50 دولارًا، و 398 من 50 إلى 100، و 476 من 100 إلى 200، و 420 من 200 إلى 400، و 256 من 400 إلى 800، و 73 فوق 800. علامة تبيّن الوسيط عند 147 دولارًا في النطاق الثالث والمتوسط عند 228 دولارًا في النطاق الرابع. 338 398 476 420 256 73 0-50 50-100 100-200 200-400 400-800 800+ الوسيط 147 المتوسط 228 قيمة الطلب بالدولار (النطاقات غير متساوية العرض)
قيم الطلبات ملتوية: معظم الطلبات صغيرة، وذيل طويل كبير

معظم الطلبات تقع بين 50 و400 دولار، وذيل يرقّ تدريجيًا يمتد إلى ما بعد 800. لاحظ أن النطاقات تتّسع كلما اتجهت يمينًا؛ النطاقات المتساوية العرض بمقدار 100 دولار كانت ستضغط كل شيء تقريبًا في الأعمدة الأولى.

القيم الشاذة: ضع علامة ثم قرّر

قاعدة عملية شائعة تضع علامة على القيم التي تبعد عن الرُّبيعين أكثر من 1.5 ضعف المدى الرُّبيعي (IQR، المسافة بين الرُّبيعين):

Python
import pandas as pd

items = pd.read_csv("order_items.csv")
orders = pd.read_csv("orders.csv", parse_dates=["order_date"])
items["revenue"] = items["quantity"] * items["unit_price"] * (1 - items["discount"])
orders = orders.merge(items.groupby("order_id", as_index=False)["revenue"].sum(), on="order_id", validate="one_to_one")
orders = orders[orders["status"] != "cancelled"]
value = orders["revenue"]

q1, q3 = value.quantile([0.25, 0.75])
upper = q3 + 1.5 * (q3 - q1)
big = value > upper
print(f"fence {upper:.2f}: {big.sum()} orders, {value[big].sum() / value.sum():.1%} of revenue")

125 طلبًا فوق 656.62 دولارًا، وتجلب معًا 25.9% من الإيراد. هذه ليست أخطاء تُنظَّف: إنها طلبات متعددة العناصر من مكاتب وكراسٍ وأوزان رياضية، حقيقية ومهمة. قاعدة القيم الشاذة (outliers) تخبرك أين تنظر. أخطاء الإدخال، مثل مجموع ضاعت فاصلته العشرية، تُصلَح؛ والقيم المتطرّفة الحقيقية تبقى، وتتحقّق أنت من أن استنتاجك يصمد معها وبدونها.

العدسة نفسها عبر الزمن

يمكن مقارنة التوزيعات عبر الفترات أيضًا، وهذه المقارنة تحمل دليلًا لدراسة الحالة. إذا قفز إيراد الربع الرابع من 2025 لأن العملاء ملؤوا سلات شراء أكبر، فيجب أن يكون الطلب النموذجي قد كبر:

Python
import pandas as pd

items = pd.read_csv("order_items.csv")
orders = pd.read_csv("orders.csv", parse_dates=["order_date"])
items["revenue"] = items["quantity"] * items["unit_price"] * (1 - items["discount"])
orders = orders.merge(items.groupby("order_id", as_index=False)["revenue"].sum(), on="order_id", validate="one_to_one")
orders = orders[orders["status"] != "cancelled"]

quarter = orders["order_date"].dt.to_period("Q")
print(orders.groupby(quarter)["revenue"].agg(["count", "median"]).round(1).tail(4))

لم يكبر. بلغ وسيط الطلب في الربع الرابع من 2025 قيمة 151.70 دولارًا، أي أقل قليلًا من 159.20 في الربع الثالث، بينما تضاعف عدد الطلبات. أيًّا كان ما حدث في الربع الرابع، فقد تعلّق بعدد الطلبات التي وصلت، لا بحجمها. جدول واحد من الوسيطات استبعد بالفعل تفسيرًا شائعًا.

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

تقيس corr() مدى قوة تحرّك عمودين رقميين معًا، من ‎-1 (متعاكسان تمامًا) مرورًا بـ 0 (لا علاقة خطية) إلى 1 (متلازمان تمامًا). في churn.csv صف لكل عميل وعلَم churned للانصراف:

Python
import pandas as pd

churn = pd.read_csv("churn.csv")
cols = ["tenure_days", "orders", "days_since_last_order", "support_tickets", "churned"]
print(churn[cols].corr().round(2)["churned"])
print(churn.groupby("churned")["days_since_last_order"].median())

العملاء الذين انصرفوا مضت عليهم مدة أطول بكثير منذ آخر طلب: وسيط قدره 214 يومًا مقابل 46، وارتباط (correlation) قدره 0.45. هذا يجعل الفجوة إشارة تحذير جيدة. لكنه لا يبيّن أن الفجوة تسبّب الانصراف؛ فالعميل الذي فقد اهتمامه أصلًا يتوقّف عن الطلب وينصرف، والقياسان كلاهما يعكسان تلك الحقيقة الواحدة. عامل الارتباطات كخيوط تستحق التحقيق، واحتفظ بكلمة “يسبّب” للتجارب.

في التمرين تصف قيم طلبات Cartwheel بنفسك. وفي الدرس التالي تحوّل أرقامًا كهذه إلى رسوم بيانية.

أهم النقاط

  • في البيانات الملتوية مثل قيم الطلبات، يقع المتوسط فوق الوسيط بكثير؛ أبلغ عن الوسيط (وبضعة مئينات) حين تصف القيمة النموذجية.
  • تعطي quantile([0.25, 0.75]) الرُّبيعين؛ والقيم التي تبعد عنهما أكثر من 1.5 ضعف المدى الرُّبيعي تستحق نظرة، لا حذفًا تلقائيًا.
  • value_counts(bins=[...]) مدرّج تكراري (histogram) نصي سريع يبيّن شكل التوزيع دون رسم بياني.
  • قارن المجموعات بمتوسطها ووسيطها وحجمها معًا، كي لا يقود الاستنتاجَ طلبٌ كبير واحد أو مجموعة صغيرة جدًا.
  • تقيس corr() مدى تحرّك عمودين معًا؛ والارتباط القوي خيط يستحق التحقيق، وليس أبدًا دليلًا على أن أحدهما يسبّب الآخر.

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

تحقّق من فهمك

3 أسئلة

السؤال 1 من 3

متوسط قيم الطلبات في Cartwheel ‏228 دولارًا والوسيط 147. ماذا يخبرك ذلك عن التوزيع؟

السؤال 2 من 3

وجدت 125 طلبًا فوق الحد الأعلى 1.5 × IQR. ماذا تفعل بها؟

السؤال 3 من 3

في churn.csv، ارتباط days_since_last_order بـ churned يساوي 0.45. أيّ عبارة لها ما يبرّرها؟

تدريب

تمرين Python

صِف الطلب النموذجي

تحتوي value على إيراد كل طلب غير ملغى. اجعل median_value وp90_value (المئين التسعين)، كلٌّ منهما مقرّبًا إلى منزلتين عشريتين. واجعل below_mean_share حصة الطلبات التي قيمتها أقل من المتوسط، مقرّبة إلى 3 منازل عشرية. احسب upper_fence على أنه Q3 + 1.5 × (Q3 − Q1)، وn_outliers على أنه عدد الطلبات التي فوقه. وأخيرًا ابنِ by_channel، جدولًا (DataFrame) فهرسه القناة وأعمدته count وmean وmedian لقيمة الطلب، بهذا الترتيب.

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

import pandas as pd

items = pd.read_csv("order_items.csv")
orders = pd.read_csv("orders.csv", parse_dates=["order_date"])
items["revenue"] = items["quantity"] * items["unit_price"] * (1 - items["discount"])
orders = orders.merge(items.groupby("order_id", as_index=False)["revenue"].sum(), on="order_id", validate="one_to_one")
orders = orders[orders["status"] != "cancelled"]
value = orders["revenue"]

# 1. Median and 90th percentile of order value, each rounded to 2 decimals
median_value = None
p90_value = None

# 2. Share of orders below the mean order value, rounded to 3 decimals
below_mean_share = None

# 3. Upper outlier fence (Q3 + 1.5 * IQR) and the number of orders above it
upper_fence = None
n_outliers = None

# 4. A DataFrame indexed by channel with columns count, mean, median of order value
by_channel = None
جارٍ تحميل المحرّر…

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

(لا مخرجات)

الفحوص

  • median_value يساوي 147.26
  • p90_value هو المئين التسعون
  • 66.3% من الطلبات أقل من المتوسط
  • الحد يساوي 656.62 وفوقه 125 طلبًا
  • في by_channel العدد والمتوسط والوسيط لكل قناة