{"id":"l-pda-4-2","courseId":"python-data-analysis","lang":"ar","kind":"lesson","summary":"ادمج الجداول باستخدام merge، واختر نوع الربط المناسب، واحمِ نفسك من الصفوف المكرّرة باستخدام validate= وعدّ الصفوف، واستخدم indicator=True لتجد السجلات التي لا مطابق لها.","html":"<p>سؤال رئيس النمو يحتاج أعمدة من أربعة جداول. الإيراد موجود في <code dir=\"ltr\">order_items</code>. أما هل أُلغي الطلب ومتى قُدّم فمعلومتان في <code dir=\"ltr\">orders</code>. وفئة المنتج في <code dir=\"ltr\">products</code>، ودولة العميل في <code dir=\"ltr\">customers</code>. في جدول البيانات كنت ستكتب صيغ بحث (lookup)؛ وفي pandas تستخدم <strong>الدمج (merge)</strong>. عمليات الدمج هي المكان الذي تحدث فيه معظم الأخطاء الصامتة في التحليل، لذا يقضي هذا الدرس في فحص عملية الدمج وقتًا يساوي ما يقضيه في كتابتها.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">أول عملية دمج</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"0\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pandas </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">items </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_items.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">parse_dates</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quantity\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"unit_price\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> -</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"discount\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">lines </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items.merge(orders, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">on</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">how</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"left\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">validate</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"many_to_one\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">len</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(items), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"->\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">len</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(lines))</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(lines[[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_item_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"status\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]].head(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>لكل صف في الجدول الأيسر، تبحث pandas عن صف <code dir=\"ltr\">orders</code> الذي له <code dir=\"ltr\">order_id</code> نفسه، وتنسخ أعمدته إليه. بنود كثيرة تشير إلى طلب واحد، فالعلاقة <strong>كثير إلى واحد (many to one)</strong>، و<code dir=\"ltr\">validate=&quot;many_to_one&quot;</code> تطلب من pandas أن تتحقّق من ذلك: لو كان في <code dir=\"ltr\">orders</code> صفان لمعرّف واحد، لأطلق الدمج الخطأ <code dir=\"ltr\">MergeError</code> بدلًا من مضاعفة تلك البنود بهدوء. وعدد الصفوف قبل وبعد هو الفحص الثاني. الدمج الأيسر على مفتاح فريد لا يغيّره أبدًا.</p>\n<p>الآن يستطيع الإيراد أخيرًا أن يحترم قاعدة العمل التي أجّلتها في الدرس 2.5: الطلبات الملغاة ليست إيرادًا.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"1\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pandas </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">items </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_items.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">parse_dates</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quantity\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"unit_price\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> -</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"discount\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">lines </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items.merge(orders, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">on</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">how</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"left\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">validate</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"many_to_one\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">kept </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> lines[lines[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"status\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"cancelled\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(kept[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].sum(), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue excluding cancelled orders\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>446,666.32 دولارًا. هذا هو تعريف الإيراد في بقية الدورة: إيراد البنود بعد الخصومات، دون رسوم الشحن ودون الطلبات الملغاة. الطلبات المُرجَعة تبقى، لأن مبالغها المستردّة تُتتبَّع منفصلة في <code dir=\"ltr\">returns</code>. اكتب تعريفات كهذه حيث يراها القرّاء؛ فالرقم الذي بلا تعريف يدعو إلى الجدال.</p>\n<p>حين يكون للمفتاح اسم مختلف في كل طرف، سمِّ الاثنين: <code dir=\"ltr\">left_on=&quot;customer_id&quot;, right_on=&quot;referred_by&quot;</code> في دمج <code dir=\"ltr\">customers</code> مع نفسه يطابق كل عميل بالأشخاص الذين أحالهم. واختر المفتاح الذي يحدّد ما تطابقه. المرتجعات تخصّ <strong>بنود</strong> الطلبات، فيُربط <code dir=\"ltr\">returns</code> بـ <code dir=\"ltr\">order_items</code> على <code dir=\"ltr\">order_item_id</code>؛ أما ربطه على <code dir=\"ltr\">order_id</code> فسيلحق كل مبلغ مسترد بكل بند في الطلب.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">أنواع الربط</h2>\n<p>تقرّر <code dir=\"ltr\">how</code> ما يحدث للمفاتيح الموجودة في طرف واحد فقط:</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 220\" role=\"img\" aria-labelledby=\"l-pda-4-2-t1\"><defs><marker id=\"arrow-l-pda-4-2-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-pda-4-2-t1\">مفاتيح الجدول الأيسر A و B و C، ومفاتيح الجدول الأيمن B و C و D. يحتفظ inner بـ B و C. ويحتفظ left بـ A و B و C مع أعمدة يمنى فارغة لـ A. ويحتفظ right بـ B و C و D. ويحتفظ outer بـ A و B و C و D.</title>\n  <text class=\"d-label-strong\" x=\"60\" y=\"28\" text-anchor=\"middle\">الأيسر</text>\n  <rect class=\"d-box-primary\" x=\"30\" y=\"40\" width=\"60\" height=\"30\" rx=\"5\"/><text class=\"d-code\" x=\"60\" y=\"60\" text-anchor=\"middle\">A</text>\n  <rect class=\"d-box-primary\" x=\"30\" y=\"76\" width=\"60\" height=\"30\" rx=\"5\"/><text class=\"d-code\" x=\"60\" y=\"96\" text-anchor=\"middle\">B</text>\n  <rect class=\"d-box-primary\" x=\"30\" y=\"112\" width=\"60\" height=\"30\" rx=\"5\"/><text class=\"d-code\" x=\"60\" y=\"132\" text-anchor=\"middle\">C</text>\n  <text class=\"d-label-strong\" x=\"150\" y=\"28\" text-anchor=\"middle\">الأيمن</text>\n  <rect class=\"d-box-accent\" x=\"120\" y=\"76\" width=\"60\" height=\"30\" rx=\"5\"/><text class=\"d-code\" x=\"150\" y=\"96\" text-anchor=\"middle\">B</text>\n  <rect class=\"d-box-accent\" x=\"120\" y=\"112\" width=\"60\" height=\"30\" rx=\"5\"/><text class=\"d-code\" x=\"150\" y=\"132\" text-anchor=\"middle\">C</text>\n  <rect class=\"d-box-accent\" x=\"120\" y=\"148\" width=\"60\" height=\"30\" rx=\"5\"/><text class=\"d-code\" x=\"150\" y=\"168\" text-anchor=\"middle\">D</text>\n  <path class=\"d-line\" d=\"M210 30 L210 200\"/>\n  <text class=\"d-label-strong\" x=\"270\" y=\"28\" text-anchor=\"middle\">inner</text>\n  <text class=\"d-code\" x=\"270\" y=\"96\" text-anchor=\"middle\">B</text><text class=\"d-code\" x=\"270\" y=\"132\" text-anchor=\"middle\">C</text>\n  <text class=\"d-label-strong\" x=\"380\" y=\"28\" text-anchor=\"middle\">left</text>\n  <text class=\"d-code\" x=\"380\" y=\"60\" text-anchor=\"middle\">A + NaN</text><text class=\"d-code\" x=\"380\" y=\"96\" text-anchor=\"middle\">B</text><text class=\"d-code\" x=\"380\" y=\"132\" text-anchor=\"middle\">C</text>\n  <text class=\"d-label-strong\" x=\"490\" y=\"28\" text-anchor=\"middle\">right</text>\n  <text class=\"d-code\" x=\"490\" y=\"96\" text-anchor=\"middle\">B</text><text class=\"d-code\" x=\"490\" y=\"132\" text-anchor=\"middle\">C</text><text class=\"d-code\" x=\"490\" y=\"168\" text-anchor=\"middle\">NaN + D</text>\n  <text class=\"d-label-strong\" x=\"610\" y=\"28\" text-anchor=\"middle\">outer</text>\n  <text class=\"d-code\" x=\"610\" y=\"60\" text-anchor=\"middle\">A + NaN</text><text class=\"d-code\" x=\"610\" y=\"96\" text-anchor=\"middle\">B</text><text class=\"d-code\" x=\"610\" y=\"132\" text-anchor=\"middle\">C</text><text class=\"d-code\" x=\"610\" y=\"168\" text-anchor=\"middle\">NaN + D</text>\n  <text class=\"d-label-muted\" x=\"440\" y=\"208\" text-anchor=\"middle\">تُملأ النظائر المفقودة بـ NaN</text>\n</svg><figcaption>أربعة أنواع من الربط، بحسب المفاتيح التي تبقى</figcaption></figure>\n<p>يحتفظ <code dir=\"ltr\">inner</code> (القيمة الافتراضية) فقط بالمفاتيح الموجودة في الطرفين. ويحتفظ <code dir=\"ltr\">left</code> بكل صف أيسر، ويملأ الأعمدة اليمنى بـ <code dir=\"ltr\">NaN</code> حيث لا يوجد نظير. و<code dir=\"ltr\">right</code> صورته المعكوسة، و<code dir=\"ltr\">outer</code> يحتفظ بكل شيء. ولإضافة معلومات إلى جدول وقائع، مثل الطلبات أو البنود أو الجلسات، استخدم <code dir=\"ltr\">how=&quot;left&quot;</code> مع جدول الوقائع على اليسار: لن تفقد أبدًا واقعة لأن جدول البحث ناقص، وقيم <code dir=\"ltr\">NaN</code> تبيّن لك بالضبط أين النقص.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">العثور على ما لم يتطابق</h2>\n<p>تضيف <code dir=\"ltr\">indicator=True</code> عمودًا اسمه <code dir=\"ltr\">_merge</code> يسجّل من أين جاء كل صف:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"2\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pandas </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">customers </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"customers.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">buyers </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders[[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"customer_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]].drop_duplicates()</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">check </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> customers.merge(buyers, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">on</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"customer_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">how</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"left\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">indicator</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">True</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(check[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"_merge\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].value_counts())</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">never_ordered </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> check[check[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"_merge\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">==</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"left_only\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">len</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(never_ordered), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"customers never ordered\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span></code></pre><div class=\"run-output\" data-run-output=\"2\" hidden></div></div>\n<p>85 عميلًا سجّلوا ولم يشتروا شيئًا، وهو العدد نفسه الذي وجدته بـ <code dir=\"ltr\">nunique</code> في الدرس 2.2، لكنه الآن قائمة أسماء تستطيع تسليمها لفريق التسويق. و<code dir=\"ltr\">indicator=True</code> هي أيضًا أسرع تدقيق لأيّ عملية دمج: صفوف <code dir=\"ltr\">right_only</code> مستحيلة في الربط الأيسر، والعدد المفاجئ من صفوف <code dir=\"ltr\">left_only</code> يعني أن مفاتيح يُفترض أن تتطابق لا تتطابق، وكثيرًا ما يكون السبب الأنواع (<code dir=\"ltr\">int</code> في طرف ونص في الآخر) أو مسافات لم تُحذف.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">اسم العمود نفسه في الطرفين</h2>\n<p>في <code dir=\"ltr\">order_items</code> و<code dir=\"ltr\">products</code> كليهما عمود <code dir=\"ltr\">unit_price</code>: السعر المدفوع وسعر القائمة. تضيف pandas لاحقتين (suffixes) للتمييز بينهما، وتستطيع اختيارهما:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"3\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pandas </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">items </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_items.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">parse_dates</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">products </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"products.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">lines </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    items.merge(orders[[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">on</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">validate</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"many_to_one\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    .merge(products[[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"product_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"name\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"unit_price\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">on</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"product_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">           suffixes</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"_charged\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"_list\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), </span><span style=\"color:#953800;--shiki-dark:#FFA657\">validate</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"many_to_one\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">lines[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"price_ratio\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (lines[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"unit_price_charged\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> lines[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"unit_price_list\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]).round(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(lines.groupby(lines[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">>=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"2025-03-01\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"price_ratio\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].agg([</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"min\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"max\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]))</span></span></code></pre><div class=\"run-output\" data-run-output=\"3\" hidden></div></div>\n<p>قبل مارس 2025، كان كل بند يُباع بسعر القائمة بالضبط؛ ومن 1 مارس، صار كل بند يُباع بزيادة 5%. لقد أكّد الدمج للتو زيادة الأسعار من البيانات وحدها، وهي أحد المشتبه بهم في قفزة إيراد الربع الرابع.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>الربط على مفتاح يتكرّر في الطرفين</span></p>\n<p>تعيد <code dir=\"ltr\">orders.merge(tickets, on=&quot;customer_id&quot;)</code> 6,624 صفًا من 2,066 طلبًا و873 تذكرة: كل طلب لعميل يقترن بكل تذكرة لذلك العميل. اجمع الإيراد على ذلك فتحسب الطلبات عدة مرات. تحوّل <code dir=\"ltr\">validate=&quot;many_to_one&quot;</code> هذا إلى خطأ <code dir=\"ltr\">MergeError</code> فوري. اكتبها في كل عملية دمج تتوقّع فيها أن يكون الطرف الأيمن فريدًا؛ فهي لا تكلّف شيئًا وتلتقط أغلى خطأ في هذا الدرس.</p>\n</aside>\n<p>في التمرين تبني الجدول الكامل على مستوى البنود الذي تحلّله بقية الدورة. وفي الدرس التالي تعيد تشكيل الجداول بين الصيغتين العريضة والطويلة باستخدام <code dir=\"ltr\">pivot_table</code> و<code dir=\"ltr\">melt</code>.</p>\n","toc":[{"id":"sec-1","text":"أول عملية دمج","level":2},{"id":"sec-2","text":"أنواع الربط","level":2},{"id":"sec-3","text":"العثور على ما لم يتطابق","level":2},{"id":"sec-4","text":"اسم العمود نفسه في الطرفين","level":2}],"takeaways":["<code dir=\"ltr\">left.merge(right, on=&quot;key&quot;, how=...)</code> تدمج الجداول بمطابقة قيم المفتاح؛ و<code dir=\"ltr\">how</code> تقرّر ما يحدث للصفوف التي لا مطابق لها.","<code dir=\"ltr\">how=&quot;left&quot;</code> تحتفظ بكل صف في الجدول الأيسر، وهي الخيار الافتراضي الآمن لإضافة أعمدة إلى جدول وقائع (fact table).","مرّر <code dir=\"ltr\">validate=&quot;many_to_one&quot;</code> (أو <code dir=\"ltr\">&quot;one_to_one&quot;</code>) كي تُطلق pandas الخطأ <code dir=\"ltr\">MergeError</code> إذا لم تكن مفاتيح الجدول الأيمن فريدة، بدلًا من مضاعفة الصفوف بصمت.","قارن <code dir=\"ltr\">len()</code> قبل كل عملية دمج وبعدها؛ فالدمج الأيسر على مفتاح فريد لا يغيّر عدد الصفوف أبدًا.","تضيف <code dir=\"ltr\">indicator=True</code> عمودًا اسمه <code dir=\"ltr\">_merge</code> (<code dir=\"ltr\">both</code> أو <code dir=\"ltr\">left_only</code> أو <code dir=\"ltr\">right_only</code>) يبيّن أيّ الصفوف وجدت نظيرًا."],"further":[{"title":"Merge, join, concatenate and compare","url":"https://pandas.pydata.org/docs/user_guide/merging.html"},{"title":"pandas.DataFrame.merge","url":"https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.merge.html"}],"quiz":[{"q":"<p>دمجت <code dir=\"ltr\">order_items</code> (3,991 صفًا) دمجًا أيسر مع <code dir=\"ltr\">orders</code> على <code dir=\"ltr\">order_id</code>. كم صفًا يجب أن يكون في النتيجة؟</p>","options":[{"text":"2,066، صف لكل طلب","why":"الدمج لا يجمّع؛ كل صف من البنود يبقى صفًا. التجميع هو ما يقلّل الصفوف."},{"text":"أكثر من 3,991، لأن للطلبات عدة عناصر","why":"كل بند يطابق طلبًا واحدًا بالضبط، فلا يتضاعف شيء. التكرار يحدث حين يتكرّر مفتاح الجدول الأيمن."},{"text":"أقل من 3,991، لأن الطلبات الملغاة تُحذف","why":"عمليات الدمج تطابق المفاتيح؛ ولا تصفّي بحسب الحالة. أنت تصفّي بعد ذلك."},{"text":"3,991 بالضبط، لأن لكل بند طلبًا واحدًا مطابقًا","why":"صحيح. بنود كثيرة لطلب واحد، مع <code dir=\"ltr\">how=&quot;left&quot;</code>، يحافظ على عدد صفوف الجدول الأيسر. و<code dir=\"ltr\">validate=&quot;many_to_one&quot;</code> تضمن ذلك."}],"answer":3},{"q":"<p>أيّ عملية دمج تجد العملاء الذين لم يقدّموا أي طلب؟</p>","options":[{"text":"<code dir=\"ltr\">customers.merge(orders, on=&quot;customer_id&quot;, how=&quot;inner&quot;)</code>","why":"الربط الداخلي (inner join) لا يحتفظ إلا بالعملاء الذين لديهم طلب واحد على الأقل، وهذا عكس ما تريده."},{"text":"<code dir=\"ltr\">customers.merge(orders, on=&quot;customer_id&quot;, how=&quot;left&quot;, indicator=True)</code>، ثم احتفظ بـ <code dir=\"ltr\">_merge == &quot;left_only&quot;</code>","why":"صحيح. الربط الأيسر يحتفظ بكل العملاء، و<code dir=\"ltr\">left_only</code> تحدّد من ليس لهم طلب مطابق. وهذا يجد 85 عميلًا."},{"text":"<code dir=\"ltr\">orders.merge(customers, on=&quot;customer_id&quot;, how=&quot;left&quot;)</code>","why":"حين تكون الطلبات على اليسار، يكون كل صف طلبًا، فلا يظهر العملاء الذين ليس لهم طلبات أبدًا."}],"answer":1},{"q":"<p>تعيد <code dir=\"ltr\">orders.merge(tickets, on=&quot;customer_id&quot;)</code> 6,624 صفًا من 2,066 طلبًا و873 تذكرة. ما الذي حدث؟</p>","options":[{"text":"<code dir=\"ltr\">customer_id</code> يتكرّر في الطرفين، فاقترن كل طلب لعميل بكل تذكرة لذلك العميل.","why":"صحيح. هذا ربط كثير إلى كثير (many-to-many). كانت <code dir=\"ltr\">validate=&quot;many_to_one&quot;</code> ستُطلق خطأً؛ اربط التذاكر بالطلبات على <code dir=\"ltr\">order_id</code> بدلًا من ذلك."},{"text":"أضافت pandas صفوفًا فارغة للتذاكر التي ليس لها طلبات.","why":"القيمة الافتراضية <code dir=\"ltr\">how=&quot;inner&quot;</code> لا تضيف أي صفوف غير مطابقة إطلاقًا."},{"text":"ألحق الدمج الجدولين أحدهما تحت الآخر.","why":"هذا ما تفعله <code dir=\"ltr\">pd.concat</code>. أما الدمج فيطابق الصفوف جنبًا إلى جنب."}],"answer":0}],"exercise":{"kind":"python","files":["order_items.csv","orders.csv","products.csv","customers.csv"],"starter":"import pandas as pd\n\nitems = pd.read_csv(\"order_items.csv\")\norders = pd.read_csv(\"orders.csv\", parse_dates=[\"order_date\"])\nproducts = pd.read_csv(\"products.csv\")\ncustomers = pd.read_csv(\"customers.csv\")\nitems[\"revenue\"] = items[\"quantity\"] * items[\"unit_price\"] * (1 - items[\"discount\"])\n\n# 1. sales: every item line with its order's columns, the product name (as \"product\"),\n#    and the customer's country. Use left merges with validate=\"many_to_one\".\n#    Then drop the lines of cancelled orders.\nsales = None\n\n# 2. Revenue by country, a Series sorted from highest to lowest\nrevenue_by_country = None\n","solution":"import pandas as pd\n\nitems = pd.read_csv(\"order_items.csv\")\norders = pd.read_csv(\"orders.csv\", parse_dates=[\"order_date\"])\nproducts = pd.read_csv(\"products.csv\")\ncustomers = pd.read_csv(\"customers.csv\")\nitems[\"revenue\"] = items[\"quantity\"] * items[\"unit_price\"] * (1 - items[\"discount\"])\n\nsales = (\n    items.merge(orders, on=\"order_id\", how=\"left\", validate=\"many_to_one\")\n    .merge(products[[\"product_id\", \"name\"]].rename(columns={\"name\": \"product\"}),\n           on=\"product_id\", how=\"left\", validate=\"many_to_one\")\n    .merge(customers[[\"customer_id\", \"country\"]], on=\"customer_id\", how=\"left\", validate=\"many_to_one\")\n)\nprint(len(sales))\nsales = sales[sales[\"status\"] != \"cancelled\"]\n\nrevenue_by_country = sales.groupby(\"country\")[\"revenue\"].sum().sort_values(ascending=False)\nprint(revenue_by_country.round(2))\n","checks":[{"id":"columns","test":"isinstance(sales, pd.DataFrame) and {'revenue', 'order_date', 'status', 'product', 'country'} <= set(sales.columns)"},{"id":"no-cancelled","test":"(sales['status'] != 'cancelled').all()"},{"id":"row-count","test":"len(sales) == int((items['order_id'].map(orders.set_index('order_id')['status']) != 'cancelled').sum())"},{"id":"revenue-total","test":"round(float(sales['revenue'].sum()), 2) == 446666.32"},{"id":"by-country","test":"isinstance(revenue_by_country, pd.Series) and revenue_by_country.index[0] == 'United Arab Emirates' and abs(float(revenue_by_country.iloc[0]) - 74531.8) < 0.01 and revenue_by_country.is_monotonic_decreasing"}],"title":"ابنِ جدول المبيعات","prompt":"<p>ابنِ <code dir=\"ltr\">sales</code>: كل صف من <code dir=\"ltr\">items</code>، مع أعمدة طلبه من <code dir=\"ltr\">orders</code>، و<code dir=\"ltr\">name</code> الخاص بالمنتج بعد إعادة تسميته إلى <code dir=\"ltr\">product</code>، و<code dir=\"ltr\">country</code> الخاص بالعميل. استخدم عمليات دمج يسرى مع <code dir=\"ltr\">validate=&quot;many_to_one&quot;</code>، ثم احذف بنود الطلبات الملغاة. بعد ذلك اجعل <code dir=\"ltr\">revenue_by_country</code> إجمالي الإيراد لكل دولة على هيئة Series، مرتّبة من الأعلى إلى الأدنى.</p>\n","hints":["سلسِل عمليات الدمج: <code dir=\"ltr\">items.merge(orders, on=&quot;order_id&quot;, how=&quot;left&quot;, validate=&quot;many_to_one&quot;)</code>، ثم المنتجات على <code dir=\"ltr\">product_id</code>، ثم العملاء على <code dir=\"ltr\">customer_id</code>.","اختر فقط الأعمدة التي تحتاجها من جداول البحث، مثل <code dir=\"ltr\">products[[&quot;product_id&quot;, &quot;name&quot;]].rename(columns={&quot;name&quot;: &quot;product&quot;})</code>، كي لا تستورد أسماء متعارضة مثل <code dir=\"ltr\">unit_price</code>.","صفِّ بـ <code dir=\"ltr\">sales[sales[&quot;status&quot;] != &quot;cancelled&quot;]</code>، ثم <code dir=\"ltr\">groupby(&quot;country&quot;)[&quot;revenue&quot;].sum().sort_values(ascending=False)</code>."],"explanation":"<p>كل عملية دمج تضيف أعمدة دون تغيير عدد الصفوف، لأن لكل بند طلبًا واحدًا ومنتجًا واحدًا وعميلًا واحدًا بالضبط، وهذا ما تضمنه <code dir=\"ltr\">validate=&quot;many_to_one&quot;</code>. اختيار أعمدة البحث التي تحتاجها فقط يُبقي الجدول مرتّبًا ويتجنّب اللاحقتين <code dir=\"ltr\">_x</code>/<code dir=\"ltr\">_y</code>. بعد حذف الطلبات الملغاة يصبح الإيراد 446,666.32 دولارًا، وهو تعريف الإيراد في الدورة. تتصدّر الإمارات بـ 74,531.80 دولارًا، متقدّمة قليلًا على المملكة المتحدة، مع أن لدى الولايات المتحدة عملاء أكثر: أول إشارة إلى أن الإيراد لكل عميل يختلف من دولة إلى أخرى.</p>\n","checkLabels":{"columns":"في <code dir=\"ltr\">sales</code> أعمدة الإيراد والطلب والمنتج والدولة","no-cancelled":"لم تبقَ أي طلبات ملغاة","row-count":"كل بند من الطلبات غير الملغاة موجود مرة واحدة بالضبط","revenue-total":"إجمالي الإيراد 446,666.32","by-country":"<code dir=\"ltr\">revenue_by_country</code> مرتّبة، والإمارات أولًا بـ 74,531.80"}},"words":588,"readMinutes":3}