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

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

التحليل القابل لإعادة الإنتاج

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

الدرس في لمحة

حوّل تحليلًا لمرة واحدة إلى تحليل قابل لإعادة الإنتاج باستخدام المُعامِلات والدوال والـ assertions والمخرجات المحفوظة و notebook نظيف يعمل من الأعلى إلى الأسفل، واعرف إلى أين تتجه بعد هذه الدورة.

  1. 1شكل التحليل القابل لإعادة التشغيل
  2. 2نظافة الـ notebook
  3. 3ما تستطيع فعله الآن

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

إجابة الربع الرابع مكتوبة. بعد ثلاثة أشهر سيسأل رئيس النمو السؤال نفسه عن الربع الأول، وقد يكون من يجيب أنت، أو زميل، أو أنت وقد نسيت كل التفاصيل. التحليل الذي لا يعمل إلا في الـ notebook الذي وُلد فيه عمل لمرة واحدة. أما التحليل الذي يستطيع أيّ شخص إعادة تشغيله من الملفات الخام والحصول على الأرقام نفسها فهو أصل ذو قيمة. والفرق حفنة من العادات.

شكل التحليل القابل لإعادة التشغيل

Python
import pandas as pd

# Parameters: everything that changes between runs lives here.
ORDERS_FILE = "orders.csv"
ITEMS_FILE = "order_items.csv"
QUARTER = "2025Q4"
EXCLUDE_STATUSES = ["cancelled"]

def load_orders(orders_file, items_file):
    """One row per order with revenue (after discounts, no shipping)."""
    items = pd.read_csv(items_file)
    orders = pd.read_csv(orders_file, parse_dates=["order_date"])
    items["revenue"] = items["quantity"] * items["unit_price"] * (1 - items["discount"])
    revenue = items.groupby("order_id", as_index=False)["revenue"].sum()
    df = orders.merge(revenue, on="order_id", how="left", validate="one_to_one")
    assert df["order_id"].is_unique, "duplicate orders"
    assert df["revenue"].notna().all(), "orders without items"
    return df

def quarter_summary(df, quarter, exclude):
    kept = df[~df["status"].isin(exclude)]
    in_q = kept[kept["order_date"].dt.to_period("Q") == quarter]
    assert len(in_q) > 0, f"no orders in {quarter}"
    return {
        "quarter": quarter,
        "revenue": round(in_q["revenue"].sum(), 2),
        "orders": len(in_q),
        "customers": in_q["customer_id"].nunique(),
        "aov": round(in_q["revenue"].mean(), 2),
    }

df = load_orders(ORDERS_FILE, ITEMS_FILE)
summary = pd.DataFrame([quarter_summary(df, q, EXCLUDE_STATUSES) for q in ["2025Q3", QUARTER]])
print(summary)
print(f"pandas {pd.__version__}")

أربع أفكار تحمل هذا الكود. المُعامِلات (parameters) في الأعلى تسمّي كل مُدخل وكل قرار، ومنها تعريف الإيراد، فيغيّر تشغيل الربع القادم سطرًا واحدًا. والدوال تحمل الخطوات، وتأخذ مُدخلاتها كوسائط وتعيد النتائج، فلا تعتمد أبدًا على متغيّر متبقٍّ من مكان آخر. والـ assertions تحدّد ما يجب أن يكون صحيحًا في البيانات قبل حساب أي شيء منها، كما فعلت في الدرس 3.5. وطباعة الإصدار تسجّل أيّ إصدار من pandas أنتج الأرقام، لأن القيم الافتراضية تتغيّر فعلًا بين الإصدارات الرئيسية.

نظافة الـ notebook

الـ notebooks ممتازة للاستكشاف وخطيرة لإعداد التقارير، لأن الخلايا يمكن أن تُشغَّل بأي ترتيب، والمتغيّرات تبقى بعد زوال الخلايا التي صنعتها. قبل أن تشارك واحدًا:

  • شغّل Restart and Run All. إذا فشل أو تغيّرت الأرقام، فقد كان الـ notebook يعتمد على حالة مخفية.
  • اقرأ الملفات ولا تعدّلها أبدًا. احتفظ بالملفات المُصدَّرة الخام كما هي في مجلد data/raw؛ التنظيف يحدث في الكود، فيمكن تكراره.
  • انقل الكود القابل لإعادة الاستخدام إلى الخارج. حين يحتاج اثنان من الـ notebooks إلى clean_orders، فمكانها ملف .py تستورده.
  • اكتب المخرجات في ملفات. تنتج summary.to_csv("q4_summary.csv", index=False) الأرقام التي تستخدمها الشريحة؛ والنسخ من الشاشة يفتح الباب للأخطاء الكتابية.
  • اكتب التعريف بجانب الرقم. خلية Markdown قصيرة تقول ما الذي يشمله “الإيراد” توفّر عليك جدالًا لاحقًا.

ما تستطيع فعله الآن

بدأت هذه الدورة وأنت لا تستطيع كتابة سطر Python واحد. أنت الآن تستطيع تحميل أي ملف CSV وفحصه، وتنظيف فوضى العالم الحقيقي (الخانات الفارغة، والأخطاء الكتابية، والمبالغ المكتوبة كنصوص، والتواريخ الملتبسة، والمكرّرات)، وربط الجداول وإعادة تشكيلها، وتلخيصها بحسب المجموعة وعبر الزمن، ووصف التوزيعات بصدق، وتحويل كل ذلك إلى إجابة مدعومة بالأدلة والتحفّظات. هذا هو العمل اليومي لمحلل البيانات.

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

أهم النقاط

  • التحليل القابل لإعادة الإنتاج (reproducible) يعطي النتيجة نفسها حين يشغّله شخص آخر من الملفات الخام، من الأعلى إلى الأسفل، دون خطوات يدوية.
  • ضع المُدخلات والاختيارات في مُعامِلات مسمّاة في الأعلى (مسارات الملفات، والربع، وتعريف الإيراد)، كي تعني إعادة التشغيل لفترة جديدة تغيير سطر واحد.
  • الدوال تحمل الخطوات التي تكرّرها؛ والـ assertions تحدّد ما يجب أن تبدو عليه البيانات قبل الثقة بالنتائج.
  • استخدم Restart and Run All قبل مشاركة أي notebook، واحفظ النتائج بـ to_csv(..., index=False) بدلًا من نسخ الأرقام بيدك.

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

تحقّق من فهمك

3 أسئلة

السؤال 1 من 3

يعطي notebook رقم الربع الرابع الصحيح على حاسوبك، لكن زميلك يحصل على رقم مختلف من الملف نفسه. ما السبب الأرجح؟

السؤال 2 من 3

في الربع القادم تحتاج التقرير نفسه للربع 2026Q1. في تحليل حسن البناء، ما الذي يجب أن تغيّره؟

السؤال 3 من 3

ماذا يضيف assertion مثل assert df["order_id"].is_unique إلى تحليل قابل لإعادة الإنتاج؟

تدريب

تمرين Python

اجعل التقرير الفصلي قابلًا لإعادة التشغيل

أكمل quarter_summary(df, quarter) بحيث تعيد قاموسًا بالمفاتيح quarter وrevenue وorders وaov للربع المعطى (نص مثل "2025Q4")، باستخدام الطلبات غير الملغاة فقط، مع تقريب revenue وaov إلى منزلتين عشريتين. يجب أن تستخدم الوسيط df، لا متغيّرًا عامًا (global)، وأن تتأكّد بـ assert من أن في الربع طلبًا واحدًا على الأقل. ثم ابنِ summary، جدولًا (DataFrame) فيه صف لكل ربع في QUARTERS، واحفظه في quarter_summary.csv دون الفهرس.

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

import pandas as pd

ORDERS_FILE = "orders.csv"
ITEMS_FILE = "order_items.csv"
QUARTERS = ["2024Q4", "2025Q3", "2025Q4"]


def load_orders(orders_file, items_file):
    items = pd.read_csv(items_file)
    orders = pd.read_csv(orders_file, parse_dates=["order_date"])
    items["revenue"] = items["quantity"] * items["unit_price"] * (1 - items["discount"])
    revenue = items.groupby("order_id", as_index=False)["revenue"].sum()
    return orders.merge(revenue, on="order_id", how="left", validate="one_to_one")


def quarter_summary(df, quarter):
    """Return a dict with keys quarter, revenue, orders, aov for one quarter
    (non-cancelled orders only), revenue and aov rounded to 2 decimals.
    Assert that the quarter has at least one order."""
    return {}


df = load_orders(ORDERS_FILE, ITEMS_FILE)

# summary: a DataFrame with one row per quarter in QUARTERS, built with quarter_summary
summary = None

# Save summary to "quarter_summary.csv" without the index
جارٍ تحميل المحرّر…

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

(لا مخرجات)

الفحوص

  • تعيد quarter_summary(df, "2025Q4") إيرادًا قدره 146,825.44 و642 طلبًا ومتوسط قيمة طلب 228.70
  • تعمل مع أرباع أخرى أيضًا، مثل 2024Q4
  • تستخدم df المعطى لها، لا جدولًا عامًا
  • في summary صف لكل ربع في QUARTERS
  • حُفظ quarter_summary.csv بثلاثة صفوف ودون عمود فهرس