{"id":"l-pda-4-3","courseId":"python-data-analysis","lang":"ar","kind":"lesson","summary":"أعِد تشكيل البيانات بين الصيغتين الطويلة والعريضة - pivot_table لملخّصات الجداول المتقاطعة، و crosstab للأعداد والمعدّلات، و melt لإعادة الجداول العريضة على طريقة جداول البيانات إلى جداول طويلة قابلة للتحليل.","html":"<p>المدير لا يريد 3,991 صفًا. يريد شبكة: القنوات على الجانب، والأرباع في الأعلى، والإيراد في الخلايا. وفريق المالية يرسل الأهداف بالطريقة نفسها، عمود لكل ربع. أما تحليلك فيعمل أفضل على الجداول الطويلة التي فيها صف لكل ملاحظة، لأن <code dir=\"ltr\">groupby</code> و<code dir=\"ltr\">merge</code> والتصفية كلها تتوقّع هذا الشكل. إعادة التشكيل تنقل البيانات بين الصيغتين، وهي آخر مهارة أساسية قبل أن تبدأ الإجابة عن الأسئلة بجدّية.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 220\" role=\"img\" aria-labelledby=\"l-pda-4-3-t1\"><defs><marker id=\"arrow-l-pda-4-3-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-3-t1\">على اليسار، جدول طويل بالأعمدة quarter و channel و revenue فيه ستة صفوف. سهم عنوانه pivot_table يشير إلى جدول عريض فيه الأرباع صفوفًا والقناتان web و app أعمدة. وسهم عائد عنوانه melt يشير إلى الخلف.</title>\n  <text class=\"d-label-strong\" x=\"130\" y=\"24\" text-anchor=\"middle\">طويل</text>\n  <rect class=\"d-box-primary\" x=\"30\" y=\"34\" width=\"200\" height=\"26\" rx=\"5\"/><text class=\"d-code\" x=\"130\" y=\"52\" text-anchor=\"middle\">quarter channel revenue</text>\n  <rect class=\"d-box\" x=\"30\" y=\"64\" width=\"200\" height=\"24\" rx=\"5\"/><text class=\"d-code\" x=\"130\" y=\"81\" text-anchor=\"middle\">Q1  web   27,868</text>\n  <rect class=\"d-box\" x=\"30\" y=\"92\" width=\"200\" height=\"24\" rx=\"5\"/><text class=\"d-code\" x=\"130\" y=\"109\" text-anchor=\"middle\">Q1  app   16,814</text>\n  <rect class=\"d-box\" x=\"30\" y=\"120\" width=\"200\" height=\"24\" rx=\"5\"/><text class=\"d-code\" x=\"130\" y=\"137\" text-anchor=\"middle\">Q2  web   30,110</text>\n  <rect class=\"d-box\" x=\"30\" y=\"148\" width=\"200\" height=\"24\" rx=\"5\"/><text class=\"d-code\" x=\"130\" y=\"165\" text-anchor=\"middle\">Q2  app   15,840</text>\n  <text class=\"d-label-muted\" x=\"130\" y=\"196\" text-anchor=\"middle\">صف لكل ملاحظة</text>\n  <path class=\"d-arrow\" d=\"M250 80 L440 80\" marker-end=\"url(#arrow-l-pda-4-3-1)\"/>\n  <text class=\"d-code\" x=\"345\" y=\"70\" text-anchor=\"middle\">pivot_table</text>\n  <path class=\"d-arrow\" d=\"M440 140 L250 140\" marker-end=\"url(#arrow-l-pda-4-3-1)\"/>\n  <text class=\"d-code\" x=\"345\" y=\"162\" text-anchor=\"middle\">melt</text>\n  <text class=\"d-label-strong\" x=\"560\" y=\"24\" text-anchor=\"middle\">عريض</text>\n  <rect class=\"d-box-primary\" x=\"460\" y=\"34\" width=\"200\" height=\"26\" rx=\"5\"/><text class=\"d-code\" x=\"560\" y=\"52\" text-anchor=\"middle\">quarter  web    app</text>\n  <rect class=\"d-box\" x=\"460\" y=\"64\" width=\"200\" height=\"24\" rx=\"5\"/><text class=\"d-code\" x=\"560\" y=\"81\" text-anchor=\"middle\">Q1   27,868 16,814</text>\n  <rect class=\"d-box\" x=\"460\" y=\"92\" width=\"200\" height=\"24\" rx=\"5\"/><text class=\"d-code\" x=\"560\" y=\"109\" text-anchor=\"middle\">Q2   30,110 15,840</text>\n  <text class=\"d-label-muted\" x=\"560\" y=\"196\" text-anchor=\"middle\">عمود لكل قناة</text>\n</svg><figcaption>الصيغتان الطويلة والعريضة تحملان البيانات نفسها بشكلين مختلفين</figcaption></figure>\n<h2 id=\"sec-1\" tabindex=\"-1\">الجدول المحوري pivot_table: لخّص وأعِد التشكيل معًا</h2>\n<p>ابدأ من جدول المبيعات الذي بنيته في الدرس 4.2 (أُعيد بناؤه هنا في بضعة أسطر)، واطلب الإيراد بحسب القناة والسنة:</p>\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 style=\"color:#1F2328;--shiki-dark:#E6EDF3\">sales </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:#1F2328;--shiki-dark:#E6EDF3\">sales </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sales[sales[</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:#1F2328;--shiki-dark:#E6EDF3\">sales </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sales.assign(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">year</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">sales[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].dt.year, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">quarter</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">sales[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].dt.quarter)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">table </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sales.pivot_table(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">index</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"channel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">columns</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"year\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">values</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">                          aggfunc</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"sum\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">margins</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 style=\"color:#953800;--shiki-dark:#FFA657\">margins_name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Total\"</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\">(table.round(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</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>كل وسيط يقابل جزءًا من الجدول المحوري (pivot table) في جداول البيانات: <code dir=\"ltr\">index</code> هو الصفوف، و<code dir=\"ltr\">columns</code> الأعمدة، و<code dir=\"ltr\">values</code> الحقل، و<code dir=\"ltr\">aggfunc</code> دالة التلخيص. تضيف <code dir=\"ltr\">margins=True</code> صف المجاميع وعمودها، وهما يؤكّدان النتيجة: 446,666 دولارًا إجمالًا، وهو تعريف الإيراد من الدرس السابق. نما الإيراد في كل قناة من 2024 إلى 2025، وتضاعف الويب نحو ثلاث مرات.</p>\n<p><code dir=\"ltr\">pivot_table</code> هي الحساب نفسه الذي تجريه <code dir=\"ltr\">groupby([&quot;channel&quot;, &quot;year&quot;])[&quot;revenue&quot;].sum().unstack()</code>. استخدم <code dir=\"ltr\">groupby</code> حين ستواصل الحساب، و<code dir=\"ltr\">pivot_table</code> حين تكون الشبكة نفسها هي المُخرج. ولعدّ الطلبات المختلفة، غيّر دالة التجميع: <code dir=\"ltr\">values=&quot;order_id&quot;, aggfunc=&quot;nunique&quot;</code>. وتستبدل <code dir=\"ltr\">fill_value=0</code> قيم <code dir=\"ltr\">NaN</code> في الخلايا الفارغة حين يكون الصفر هو الإجابة الصادقة، كما في “لا مبيعات في هذه الخلية”.</p>\n<h3 id=\"sec-2\" tabindex=\"-1\">من المبالغ إلى الحصص</h3>\n<p>شبكة المبالغ تستدعي السؤال التالي: كيف تغيّر <strong>المزيج</strong>؟ اقسم كل عمود على مجموعه، فتعرض الشبكة الحصص بدلًا من المبالغ. تعيد <code dir=\"ltr\">by_year.sum()</code> مجموعًا لكل عمود، وقسمة DataFrame على تلك الـ Series تحاذي كل مجموع مع عموده بحسب التسمية، وهي قاعدة المحاذاة نفسها التي قابلتها مع Series وأخرى في الدرس 2.1. ولقسمة كل صف على مجموع صفّه بدلًا من ذلك، استخدم <code dir=\"ltr\">by_year.div(by_year.sum(axis=1), axis=0)</code>.</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\">sales </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:#1F2328;--shiki-dark:#E6EDF3\">sales </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sales[sales[</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">by_year </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sales.pivot_table(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">index</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"channel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">columns</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">sales[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].dt.year,</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">                            values</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"revenue\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">aggfunc</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"sum\"</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\">((by_year </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> by_year.sum()).round(</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=\"1\" hidden></div></div>\n<p>ارتفعت حصة الويب من الإيراد من 53% في 2024 إلى 59% في 2025، بينما انخفضت حصة التطبيق من 35% إلى 31%. المبالغ تخبرك أن كل شيء نما؛ والحصص تخبرك أين تركّز النمو. أبلغ عن الاثنين، لأن كلًّا منهما يخفي ما يُظهره الآخر.</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>الفرق بين pivot و pivot_table</span></p>\n<p>تفشل <code dir=\"ltr\">sales.pivot(index=&quot;quarter&quot;, columns=&quot;channel&quot;, values=&quot;revenue&quot;)</code> بالخطأ <code dir=\"ltr\">ValueError: Index contains duplicate entries, cannot reshape</code>. <code dir=\"ltr\">pivot</code> إعادة تشكيل خالصة: تتوقّع قيمة واحدة بالضبط في كل خلية، والبيانات الخام فيها المئات. استخدم <code dir=\"ltr\">pivot_table</code> على البيانات الخام، واحتفظ بـ <code dir=\"ltr\">pivot</code> للجداول التي فيها أصلًا صف لكل خلية.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">crosstab: الأعداد والمعدّلات</h2>\n<p>لعمودين فئويين، تعدّ <code dir=\"ltr\">pd.crosstab</code> التركيبات، وتحوّل <code dir=\"ltr\">normalize</code> الأعداد إلى حصص. هذه زيارات موقع Cartwheel:</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\">sessions </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\">\"web_sessions.csv\"</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\">(pd.crosstab(sessions[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"source\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], sessions[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"device\"</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\">(pd.crosstab(sessions[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"device\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], sessions[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"purchased\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">normalize</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"index\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).round(</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=\"2\" hidden></div></div>\n<p>الجدول الثاني يجيب عن سؤال حقيقي. تجعل <code dir=\"ltr\">normalize=&quot;index&quot;</code> مجموع كل صف 1، فيكون العمود <code dir=\"ltr\">1</code> معدّل الشراء لكل جهاز: 12.4% من جلسات الحاسوب المكتبي تنتهي بشراء، لكن 6.7% فقط من جلسات الهاتف، مع أن الهاتف يجلب أكبر قدر من الزيارات. هذه الفجوة تستحق حديثًا مع المسؤول عن صفحة الدفع على الهاتف. اختر التطبيع بحسب السؤال: <code dir=\"ltr\">&quot;index&quot;</code> للمعدّلات داخل كل مجموعة صفوف، و<code dir=\"ltr\">&quot;columns&quot;</code> لتركيبة كل عمود، و<code dir=\"ltr\">&quot;all&quot;</code> للحصص من المجموع الكلي.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">melt: من العريض إلى الطويل من جديد</h2>\n<p>تصل أهداف فريق المالية لعام 2025 كجدول بيانات، عمود لكل ربع. لمقارنتها بالإيراد الفعلي تحتاجها طويلة، صف لكل قناة وربع، كي يمكن دمجها:</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\">targets </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.DataFrame({</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"channel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"marketplace\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"mobile_app\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"web\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">],</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"Q1\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">5000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">15000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">28000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">],</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"Q2\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">5000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">16000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">30000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">],</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"Q3\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">6000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">20000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">40000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">],</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"Q4\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">9000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">30000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">60000</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:#953800;--shiki-dark:#FFA657\">long</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> targets.melt(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">id_vars</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"channel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">var_name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quarter\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">value_name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"target\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">long</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quarter\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> long</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quarter\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].str[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].astype(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">int</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:#953800;--shiki-dark:#FFA657\">long</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.head(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">6</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:#953800;--shiki-dark:#FFA657\">long</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.shape)</span></span></code></pre><div class=\"run-output\" data-run-output=\"3\" hidden></div></div>\n<p><code dir=\"ltr\">id_vars</code> هي الأعمدة التي تحدّد الصف وتبقى كما هي؛ وكل عمود آخر يصبح زوجًا من القيم في عمودين جديدين، اسم العمود القديم في <code dir=\"ltr\">var_name</code> وقيمته في <code dir=\"ltr\">value_name</code>. ثلاثة صفوف في أربعة أرباع تعطي اثني عشر. تأخذ <code dir=\"ltr\">str[1]</code> الحرف الثاني من <code dir=\"ltr\">&quot;Q1&quot;</code>، فيصبح الربع رقمًا يطابق <code dir=\"ltr\">sales[&quot;quarter&quot;]</code>، جاهزًا لعملية دمج مثل التي في الدرس 4.2.</p>\n<aside class=\"callout callout-tip\" 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=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>أبقِ البيانات طويلة حتى الخطوة الأخيرة</span></p>\n<p>نفّذ التصفية والتجميع والدمج على الجداول الطويلة، ولا تُعِد التشكيل إلى عريض إلا للعرض. الجداول العريضة التي فيها السنوات أو الأشهر أعمدة صعبة التصفية (“أيّ عمود هو 2025-Q4؟”)، وتنكسر لحظة وصول فترة جديدة.</p>\n</aside>\n<p>في التمرين تبني شبكة الأرباع لعام 2025 وتفحص الأداء مقابل تلك الأهداف. وبعدها يأتي الزمن نفسه: إعادة التجميع الزمني، والفترات، ومعدّلات النمو.</p>\n","toc":[{"id":"sec-1","text":"الجدول المحوري pivot_table: لخّص وأعِد التشكيل معًا","level":2},{"id":"sec-2","text":"من المبالغ إلى الحصص","level":3},{"id":"sec-3","text":"crosstab: الأعداد والمعدّلات","level":2},{"id":"sec-4","text":"melt: من العريض إلى الطويل من جديد","level":2}],"takeaways":["البيانات الطويلة فيها صف لكل ملاحظة؛ والبيانات العريضة تنشر متغيّرًا واحدًا عبر الأعمدة. تحلّل pandas البيانات الطويلة أفضل، ويقرأ الناس الجداول العريضة أفضل.","<code dir=\"ltr\">pivot_table(index=..., columns=..., values=..., aggfunc=&quot;sum&quot;)</code> تجمّع وتعيد التشكيل في خطوة واحدة، مثل الجدول المحوري في جداول البيانات.","<code dir=\"ltr\">pivot</code> تعيد التشكيل فقط؛ وتُطلق <code dir=\"ltr\">ValueError: Index contains duplicate entries</code> حين تتشارك عدة صفوف خلية واحدة، فاستخدم <code dir=\"ltr\">pivot_table</code> للبيانات الخام.","<code dir=\"ltr\">pd.crosstab(a, b, normalize=&quot;index&quot;)</code> تحوّل عمودين إلى نِسَب مئوية لكل صف، وهي أسرع طريقة لمقارنة المعدّلات بين المجموعات.","<code dir=\"ltr\">melt(id_vars=..., var_name=..., value_name=...)</code> تعيد الأعمدة العريضة مثل <code dir=\"ltr\">Q1</code>…<code dir=\"ltr\">Q4</code> إلى صفوف."],"further":[{"title":"Reshaping and pivot tables","url":"https://pandas.pydata.org/docs/user_guide/reshaping.html"},{"title":"pandas.DataFrame.pivot_table","url":"https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.pivot_table.html"},{"title":"pandas.melt","url":"https://pandas.pydata.org/docs/reference/api/pandas.melt.html"}],"quiz":[{"q":"<p>تُطلق <code dir=\"ltr\">sales.pivot(index=&quot;quarter&quot;, columns=&quot;channel&quot;, values=&quot;revenue&quot;)</code> الخطأ <code dir=\"ltr\">ValueError: Index contains duplicate entries, cannot reshape</code>. ما الإصلاح؟</p>","options":[{"text":"رتّب <code dir=\"ltr\">sales</code> بحسب الربع أولًا.","why":"الترتيب ليس المشكلة؛ بنود كثيرة تتشارك الربع والقناة نفسيهما، وليس لدى <code dir=\"ltr\">pivot</code> قاعدة لدمجها."},{"text":"احذف الصفوف المكرّرة بـ <code dir=\"ltr\">drop_duplicates()</code>.","why":"الصفوف بنود طلبات مختلفة، لا مكرّرات. حذفها سيمحو إيرادًا حقيقيًا."},{"text":"حوّل <code dir=\"ltr\">quarter</code> إلى نص.","why":"نوع المفتاح لا علاقة له؛ المشكلة هي وجود عدة قيم في الخلية الواحدة."},{"text":"استخدم <code dir=\"ltr\">pivot_table</code> مع <code dir=\"ltr\">aggfunc=&quot;sum&quot;</code>، التي تدمج القيم التي تقع في الخلية نفسها.","why":"صحيح. تجمّع <code dir=\"ltr\">pivot_table</code> أولًا ثم تعيد التشكيل. أما <code dir=\"ltr\">pivot</code> فلا تعمل إلا حين يظهر كل زوج من الفهرس والعمود مرة واحدة."}],"answer":3},{"q":"<p>في جدول المالية الأعمدة <code dir=\"ltr\">channel, Q1, Q2, Q3, Q4</code>. أيّ استدعاء يجعل صفًا لكل قناة وربع؟</p>","options":[{"text":"<code dir=\"ltr\">targets.melt(id_vars=&quot;channel&quot;, var_name=&quot;quarter&quot;, value_name=&quot;target&quot;)</code>","why":"صحيح. يبقى <code dir=\"ltr\">channel</code> معرّفًا، وتصبح أعمدة الأرباع الأربعة صفوفًا، أسماؤها في <code dir=\"ltr\">quarter</code> وقيمها في <code dir=\"ltr\">target</code>."},{"text":"<code dir=\"ltr\">targets.pivot_table(index=&quot;channel&quot;)</code>","why":"الجدول عريض أصلًا بحسب الربع؛ وإعادة التشكيل المحوري لن تنقل أعمدة الأرباع إلى صفوف."},{"text":"<code dir=\"ltr\">targets.T</code>","why":"المنقول (transpose) يبدّل الصفوف والأعمدة، فتصبح القنوات أعمدة، وهذا ما زال عريضًا."}],"answer":0},{"q":"<p>تعرض <code dir=\"ltr\">pd.crosstab(sessions[&quot;device&quot;], sessions[&quot;purchased&quot;], normalize=&quot;index&quot;)</code> القيمة 0.067 في صف mobile والعمود 1. ماذا تعني؟</p>","options":[{"text":"6.7% من كل عمليات الشراء حدثت على الهاتف.","why":"هذا ما تعطيه <code dir=\"ltr\">normalize=&quot;columns&quot;</code>. أما مع <code dir=\"ltr\">&quot;index&quot;</code> فمجموع كل صف يساوي 1."},{"text":"6.7% من جلسات الهاتف انتهت بعملية شراء.","why":"صحيح. التطبيع بحسب الفهرس يقسم كل عدد على مجموع صفّه، فتكون القيمة معدّل التحويل داخل جلسات الهاتف."},{"text":"للهاتف 6.7% من كل الجلسات.","why":"حصة الجلسات بحسب الجهاز تأتي من <code dir=\"ltr\">value_counts(normalize=True)</code> على <code dir=\"ltr\">device</code>."}],"answer":1}],"exercise":{"kind":"python","files":["order_items.csv","orders.csv"],"starter":"import pandas as pd\n\nitems = pd.read_csv(\"order_items.csv\")\norders = pd.read_csv(\"orders.csv\", parse_dates=[\"order_date\"])\nitems[\"revenue\"] = items[\"quantity\"] * items[\"unit_price\"] * (1 - items[\"discount\"])\nsales = items.merge(orders, on=\"order_id\", how=\"left\", validate=\"many_to_one\")\nsales = sales[sales[\"status\"] != \"cancelled\"]\nsales = sales.assign(year=sales[\"order_date\"].dt.year, quarter=sales[\"order_date\"].dt.quarter)\nsales_2025 = sales[sales[\"year\"] == 2025]\n\ntargets = pd.DataFrame({\n    \"channel\": [\"marketplace\", \"mobile_app\", \"web\"],\n    \"Q1\": [5000, 15000, 28000],\n    \"Q2\": [5000, 16000, 30000],\n    \"Q3\": [6000, 20000, 40000],\n    \"Q4\": [9000, 30000, 60000],\n})\n\n# 1. grid: 2025 revenue with quarters (1-4) as rows and channels as columns\ngrid = None\n\n# 2. targets_long: columns channel, quarter (int 1-4), target - 12 rows\ntargets_long = None\n\n# 3. vs_target: targets_long with an added \"revenue\" column (actual 2025 revenue\n#    for that channel and quarter)\nvs_target = None\n\n# 4. Number of channel-quarters where revenue reached the target\nbeat = 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\"])\nitems[\"revenue\"] = items[\"quantity\"] * items[\"unit_price\"] * (1 - items[\"discount\"])\nsales = items.merge(orders, on=\"order_id\", how=\"left\", validate=\"many_to_one\")\nsales = sales[sales[\"status\"] != \"cancelled\"]\nsales = sales.assign(year=sales[\"order_date\"].dt.year, quarter=sales[\"order_date\"].dt.quarter)\nsales_2025 = sales[sales[\"year\"] == 2025]\n\ntargets = pd.DataFrame({\n    \"channel\": [\"marketplace\", \"mobile_app\", \"web\"],\n    \"Q1\": [5000, 15000, 28000],\n    \"Q2\": [5000, 16000, 30000],\n    \"Q3\": [6000, 20000, 40000],\n    \"Q4\": [9000, 30000, 60000],\n})\n\ngrid = sales_2025.pivot_table(index=\"quarter\", columns=\"channel\", values=\"revenue\", aggfunc=\"sum\")\n\ntargets_long = targets.melt(id_vars=\"channel\", var_name=\"quarter\", value_name=\"target\")\ntargets_long[\"quarter\"] = targets_long[\"quarter\"].str[1].astype(int)\n\nactual = sales_2025.groupby([\"channel\", \"quarter\"], as_index=False)[\"revenue\"].sum()\nvs_target = targets_long.merge(actual, on=[\"channel\", \"quarter\"], how=\"left\", validate=\"one_to_one\")\n\nbeat = int((vs_target[\"revenue\"] >= vs_target[\"target\"]).sum())\nprint(grid.round(0))\nprint(vs_target.round(0))\n","checks":[{"id":"grid","test":"isinstance(grid, pd.DataFrame) and grid.shape == (4, 3) and abs(float(grid.loc[4, 'web']) - 87530) < 1"},{"id":"long","test":"isinstance(targets_long, pd.DataFrame) and len(targets_long) == 12 and {'channel', 'quarter', 'target'} <= set(targets_long.columns) and sorted(set(targets_long['quarter'].tolist())) == [1, 2, 3, 4]"},{"id":"vs-target","test":"isinstance(vs_target, pd.DataFrame) and len(vs_target) == 12 and abs(float(vs_target['revenue'].sum()) - 326808) < 1"},{"id":"beat","test":"int(beat) == 8"}],"title":"عام 2025 مقابل الهدف","prompt":"<p>يحتوي <code dir=\"ltr\">sales_2025</code> على بنود 2025 غير الملغاة، مع العمودين <code dir=\"ltr\">quarter</code> و<code dir=\"ltr\">channel</code>، و<code dir=\"ltr\">targets</code> هو جدول فريق المالية العريض. ابنِ <code dir=\"ltr\">grid</code>، جدولًا محوريًا للإيراد فيه الأرباع من 1 إلى 4 صفوف والقنوات أعمدة. حوّل <code dir=\"ltr\">targets</code> بـ melt إلى <code dir=\"ltr\">targets_long</code> بالأعمدة <code dir=\"ltr\">channel</code> و<code dir=\"ltr\">quarter</code> (عدد صحيح من 1 إلى 4) و<code dir=\"ltr\">target</code>. ثم أنشئ <code dir=\"ltr\">vs_target</code>: هو <code dir=\"ltr\">targets_long</code> مع عمود <code dir=\"ltr\">revenue</code> فيه الإيراد الفعلي لكل قناة وربع. وأخيرًا اجعل <code dir=\"ltr\">beat</code> عدد الصفوف التي بلغ فيها الإيراد الهدف.</p>\n","hints":["<code dir=\"ltr\">sales_2025.pivot_table(index=&quot;quarter&quot;, columns=&quot;channel&quot;, values=&quot;revenue&quot;, aggfunc=&quot;sum&quot;)</code>.","<code dir=\"ltr\">targets.melt(id_vars=&quot;channel&quot;, var_name=&quot;quarter&quot;, value_name=&quot;target&quot;)</code>، ثم حوّل <code dir=\"ltr\">&quot;Q1&quot;</code> إلى <code dir=\"ltr\">1</code> بـ <code dir=\"ltr\">.str[1].astype(int)</code>.","جمّع القيم الفعلية إلى مستوى التفصيل نفسه بـ <code dir=\"ltr\">groupby([&quot;channel&quot;, &quot;quarter&quot;], as_index=False)[&quot;revenue&quot;].sum()</code>، وادمج على المفتاحين، وعُدّ <code dir=\"ltr\">(vs_target[&quot;revenue&quot;] &gt;= vs_target[&quot;target&quot;]).sum()</code>."],"explanation":"<p>الشبكة للناس؛ والجدول الطويل للحساب. تحويل الأهداف بـ melt وتجميع القيم الفعلية إلى مستوى التفصيل نفسه (صف لكل قناة وربع) يجعل المقارنة عملية دمج بسيطة على مفتاحين، وتؤكّد <code dir=\"ltr\">validate=&quot;one_to_one&quot;</code> أن كل هدف قابل قيمة فعلية واحدة بالضبط. حقّقت Cartwheel ثمانية أهداف من 12، وجاءت الإخفاقات الأربعة كلها في النصف الأول من السنة، ثم تجاوزت أهداف الربع الرابع في كل مكان بفارق كبير: إيراد الويب في الربع الرابع كان 87,530 دولارًا مقابل هدف قدره 60,000. دليل آخر على أن شيئًا غير معتاد حدث في الربع الرابع.</p>\n","checkLabels":{"grid":"<code dir=\"ltr\">grid</code> بحجم 4 أرباع × 3 قنوات، والويب في الربع الرابع نحو 87,530","long":"في <code dir=\"ltr\">targets_long</code> 12 صفًا والأرباع أعداد صحيحة","vs-target":"في <code dir=\"ltr\">vs_target</code> 12 صفًا وإيراد فعلي مجموعه يساوي إجمالي 2025","beat":"<code dir=\"ltr\">beat</code> يساوي 8"}},"words":616,"readMinutes":4}