{"id":"l-pda-3-1","courseId":"python-data-analysis","lang":"ar","kind":"lesson","summary":"اعثر على القيم المفقودة بـ isna، وافهم أيّ النصوص تحوّلها read_csv بصمت إلى NaN، واختر عن قصد بين استرجاعها أو وضع تسمية لها أو حذفها أو إبقائها بأنواع تقبل القيم المفقودة.","html":"<p>افتح <code dir=\"ltr\">orders_messy.csv</code> في جدول بيانات، فيسهل أن تفوتك الفجوات: خلية كمية فارغة هنا، و<code dir=\"ltr\">N/A</code> في عمود القناة هناك. حمّله في pandas فتصبح تلك الفجوات <code dir=\"ltr\">NaN</code>، تتدفّق بعدها بصمت عبر حساباتك: المجاميع تتخطّاها، والمتوسطات تتجاهلها، و<code dir=\"ltr\">fillna(0)</code> مكتوبة على عجل تحوّل “لا نعرف” إلى “لم يُبَع شيء”. القيم المفقودة (missing values) ليست إزعاجًا تقنيًا تزيله من طريقك؛ كل واحدة منها سؤال عن العمل.</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\">messy </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_messy.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\">(messy.shape)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(messy.isna().sum())</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(messy[messy.isna().any(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">axis</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)].head().to_string())</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>تعيد <code dir=\"ltr\">isna()</code> جدولًا (DataFrame) من القيم المنطقية، وجمعه يعدّ الفجوات في كل عمود: 9 كميات و7 قنوات. وتسأل <code dir=\"ltr\">isna().any(axis=1)</code> لكل صف هل أيّ عمود فيه مفقود (<code dir=\"ltr\">axis=1</code> تعني “عبر الأعمدة”)، وهذا يتيح لك النظر إلى الصفوف المتضرّرة فعلًا. والنظر إليها مهم: الكميات التسع المفقودة تخصّ طلبات عادية عبر السنتين، لا يومًا واحدًا معطوبًا.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">ما قرّرته read_csv نيابةً عنك</h2>\n<p>لا توجد في الملف الخام خلايا قناة فارغة. فيه النص <code dir=\"ltr\">N/A</code>، وحوّلته pandas إلى <code dir=\"ltr\">NaN</code> لأن <code dir=\"ltr\">N/A</code> موجود في قائمتها الافتراضية لعلامات الفقد، مع الخلية الفارغة و<code dir=\"ltr\">NA</code> و<code dir=\"ltr\">NaN</code> و<code dir=\"ltr\">null</code> و<code dir=\"ltr\">None</code> وغيرها. هذا مفيد عادةً. وأحيانًا يكون خاطئًا: عمود رموز الدول سيفقد ناميبيا، التي رمزها في ISO هو <code dir=\"ltr\">NA</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\">raw </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_messy.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">keep_default_na</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">False</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\">(raw[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"channel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].value_counts())</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((raw[</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:#0A3069;--shiki-dark:#A5D6FF\"> \"\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).sum(), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"empty quantity cells\"</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>الآن يظهر <code dir=\"ltr\">N/A</code> كقيمة حقيقية بعدد 7، وعمود الكمية كله نصوص، لأن الخلايا الفارغة بقيت نصوصًا فارغة. وللتحكّم في القائمة بدقة، مرّر <code dir=\"ltr\">na_values=[&quot;N/A&quot;, &quot;&quot;]</code> مع <code dir=\"ltr\">keep_default_na=False</code>.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">تقرير ما تفعله</h2>\n<p>لا يوجد إصلاح يصلح لكل حالة. اطرح ثلاثة أسئلة بهذا الترتيب، وإذا كانت الإجابة عنها كلها “لا” فأبقِ القيمة مفقودة:</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 250\" role=\"img\" aria-labelledby=\"l-pda-3-1-t1\"><defs><marker id=\"arrow-l-pda-3-1-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-3-1-t1\">مسار قرار. أولًا: هل يمكن استرجاع القيمة من مصدر آخر؟ إذا نعم، استرجعها. ثانيًا: هل للفقد معنى، مثل عدم وجود كوبون؟ إذا نعم، ضع له تسمية. ثالثًا: هل الصف بلا فائدة بدونها في هذا التحليل؟ إذا نعم، احذفه لذلك التحليل. وإلا فأبقِها مفقودة.</title>\n  <rect class=\"d-box\" x=\"10\" y=\"20\" width=\"150\" height=\"60\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"85\" y=\"45\" text-anchor=\"middle\">يمكن استرجاعها</text>\n  <text class=\"d-label\" x=\"85\" y=\"65\" text-anchor=\"middle\">من مصدر آخر؟</text>\n  <rect class=\"d-box\" x=\"185\" y=\"20\" width=\"150\" height=\"60\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"260\" y=\"45\" text-anchor=\"middle\">هل لـ \"مفقود\"</text>\n  <text class=\"d-label\" x=\"260\" y=\"65\" text-anchor=\"middle\">معنى ما؟</text>\n  <rect class=\"d-box\" x=\"360\" y=\"20\" width=\"150\" height=\"60\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"435\" y=\"45\" text-anchor=\"middle\">الصف بلا فائدة</text>\n  <text class=\"d-label\" x=\"435\" y=\"65\" text-anchor=\"middle\">بدونها؟</text>\n  <rect class=\"d-box-primary\" x=\"535\" y=\"20\" width=\"150\" height=\"60\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"610\" y=\"55\" text-anchor=\"middle\">أبقِها NA</text>\n  <path class=\"d-arrow\" d=\"M160 50 L183 50\" marker-end=\"url(#arrow-l-pda-3-1-1)\"/>\n  <path class=\"d-arrow\" d=\"M335 50 L358 50\" marker-end=\"url(#arrow-l-pda-3-1-1)\"/>\n  <path class=\"d-arrow\" d=\"M510 50 L533 50\" marker-end=\"url(#arrow-l-pda-3-1-1)\"/>\n  <text class=\"d-label-muted\" x=\"171\" y=\"40\" text-anchor=\"middle\">لا</text>\n  <text class=\"d-label-muted\" x=\"346\" y=\"40\" text-anchor=\"middle\">لا</text>\n  <text class=\"d-label-muted\" x=\"521\" y=\"40\" text-anchor=\"middle\">لا</text>\n  <path class=\"d-arrow\" d=\"M85 80 L85 140\" marker-end=\"url(#arrow-l-pda-3-1-1)\"/>\n  <path class=\"d-arrow\" d=\"M260 80 L260 140\" marker-end=\"url(#arrow-l-pda-3-1-1)\"/>\n  <path class=\"d-arrow\" d=\"M435 80 L435 140\" marker-end=\"url(#arrow-l-pda-3-1-1)\"/>\n  <text class=\"d-label-muted\" x=\"100\" y=\"115\">نعم</text>\n  <text class=\"d-label-muted\" x=\"275\" y=\"115\">نعم</text>\n  <text class=\"d-label-muted\" x=\"450\" y=\"115\">نعم</text>\n  <rect class=\"d-box-success\" x=\"10\" y=\"144\" width=\"150\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"85\" y=\"174\" text-anchor=\"middle\">استرجعها</text>\n  <rect class=\"d-box-accent\" x=\"185\" y=\"144\" width=\"150\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"260\" y=\"174\" text-anchor=\"middle\">ضع لها تسمية</text>\n  <rect class=\"d-box-warn\" x=\"360\" y=\"144\" width=\"150\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"435\" y=\"174\" text-anchor=\"middle\">احذف الصف (مؤقتًا)</text>\n  <text class=\"d-label-muted\" x=\"85\" y=\"220\" text-anchor=\"middle\">اربط بجدول نظيف</text>\n  <text class=\"d-label-muted\" x=\"260\" y=\"220\" text-anchor=\"middle\">fillna(\"unknown\")</text>\n  <text class=\"d-label-muted\" x=\"435\" y=\"220\" text-anchor=\"middle\">dropna(subset=...)</text>\n</svg><figcaption>ثلاثة أسئلة لكل قيمة مفقودة</figcaption></figure>\n<p>الاسترجاع يأتي أولًا لأنه الخيار الوحيد الذي يضيف معلومة. معرّفات الطلبات في الملف المُصدَّر موجودة أيضًا في جدولي <code dir=\"ltr\">orders</code> و<code dir=\"ltr\">order_items</code> النظيفين لدى Cartwheel، فيمكن البحث عن الكميات المفقودة هناك؛ وستتعلّم عملية الدمج (merge) التي تفعل ذلك في الدرس 4.2. وإلى أن تستطيع استرجاع قيمة، لا تخترع واحدة.</p>\n<p>بالتطبيق على الملف المُصدَّر: <strong>القناة</strong> المفقودة لا يمكن تخمينها من أي شيء آخر في الصف، لكن “لا نعرف القناة” حقيقة تستحق أن تبقى ظاهرة، فتحصل على تسمية صريحة. أما <strong>كود الكوبون</strong> الفارغ في جدول <code dir=\"ltr\">orders</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\">messy </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_messy.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">messy[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"channel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> messy[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"channel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].fillna(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"unknown\"</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\">(messy[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"channel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].value_counts())</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\">(messy.dropna(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">subset</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quantity\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"rows with a known quantity\"</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\">dropna(subset=[&quot;quantity&quot;])</code> الصفوف التي تنقصها الكمية فقط، وفي النتيجة التي تخزّنها فقط؛ أما <code dir=\"ltr\">dropna()</code> العادية دون subset فتحذف الصف إذا كان <strong>أيّ</strong> عمود فيه مفقودًا، وهذا على الملف الخام سيرمي 16 صفًا، منها مجاميع سليمة تمامًا. احذف لتحليل محدّد، لا من نسختك الرئيسية.</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\">fillna(0)</code> متوسط الكمية من 1.43 إلى 1.36، وتجعل تسعة طلبات حقيقية تبدو فارغة. لا تملأ إلا بقيمة تستطيع الدفاع عنها في جملة واحدة أمام مديرك.</p>\n</aside>\n<p>وفخ آخر: لا تستطيع العثور على الفجوات بـ <code dir=\"ltr\">==</code>. القيمة <code dir=\"ltr\">NaN</code> لا تساوي أي شيء، ولا حتى نفسها، فتكون <code dir=\"ltr\">messy[&quot;channel&quot;] == np.nan</code> مساوية لـ <code dir=\"ltr\">False</code> في كل صف. استخدم دائمًا <code dir=\"ltr\">isna()</code> و<code dir=\"ltr\">notna()</code>.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">الأنواع القابلة للقيم المفقودة: أعداد صحيحة يمكن أن تُفقد</h2>\n<p>حُمّل عمود الكمية على هيئة <code dir=\"ltr\">float64</code>، فتُطبع الكميات <code dir=\"ltr\">1.0</code> و<code dir=\"ltr\">2.0</code>. هذا هو الأثر نفسه الذي رأيته مع <code dir=\"ltr\">referred_by</code> في الدرس 2.2: <code dir=\"ltr\">NaN</code> الكلاسيكية عدد عشري. ولدى pandas أيضًا أنواع <strong>قابلة للقيم المفقودة (nullable)</strong>، تُكتب بحرف كبير، وتخزّن علامة فقد مستقلة هي <code dir=\"ltr\">pd.NA</code>:</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\">messy </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_messy.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">qty </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> messy[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"quantity\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].astype(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Int64\"</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\">(qty.dtype, qty.iloc[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">32</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">35</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].tolist())</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(qty.sum(), qty.count(), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">len</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(qty))</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\">(qty.mean(), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</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>يحتفظ <code dir=\"ltr\">Int64</code> بالأعداد الصحيحة كأعداد صحيحة، ويعرض الفجوات كـ <code dir=\"ltr\">&lt;NA&gt;</code>. والحسابات التجميعية تتخطّى القيم المفقودة افتراضيًا، فتجمع <code dir=\"ltr\">sum()</code> الكميات المعروفة الـ 159، ويخبرك الفرق بين <code dir=\"ltr\">count()</code> و<code dir=\"ltr\">len()</code> بعدد المفقود بالضبط. وتضم العائلة نفسها <code dir=\"ltr\">Float64</code> و<code dir=\"ltr\">boolean</code> و<code dir=\"ltr\">string</code>، وتحمّل <code dir=\"ltr\">pd.read_csv(..., dtype_backend=&quot;numpy_nullable&quot;)</code> كل الأعمدة بهذه الطريقة. وسلوك واحد يجب أن تعرفه: المقارنات مع <code dir=\"ltr\">pd.NA</code> تعيد <code dir=\"ltr\">&lt;NA&gt;</code> بدلًا من <code dir=\"ltr\">False</code>، لأن سؤال “هل الكمية المجهولة تساوي 1؟” لا إجابة صادقة عنه.</p>\n<p>في التمرين تعدّ الفجوات وتضع لها تسميات وتغيّر نوعها. وفي الدرس التالي تنظّف الأعمدة النصية، حيث تأتي أسماء الدول بأربع كتابات مختلفة.</p>\n","toc":[{"id":"sec-1","text":"العثور على الفجوات","level":2},{"id":"sec-2","text":"ما قرّرته read_csv نيابةً عنك","level":2},{"id":"sec-3","text":"تقرير ما تفعله","level":2},{"id":"sec-4","text":"الأنواع القابلة للقيم المفقودة: أعداد صحيحة يمكن أن تُفقد","level":2}],"takeaways":["تحوّل <code dir=\"ltr\">read_csv</code> افتراضيًا الخلايا الفارغة والنصوص مثل <code dir=\"ltr\">N/A</code> و<code dir=\"ltr\">NA</code> و<code dir=\"ltr\">null</code> إلى قيم مفقودة، فافحص الملف الخام حين تبدو الأعداد مفاجئة.","<code dir=\"ltr\">df.isna().sum()</code> تعدّ الخانات الفارغة في كل عمود؛ و<code dir=\"ltr\">df[df.isna().any(axis=1)]</code> تعرض الصفوف التي فيها أيّ منها.","كل قيمة مفقودة تحتاج قرارًا - استرجعها، أو ضع لها تسمية، أو احذف الصف، أو أبقِها مفقودة - والملء بـ 0 نادرًا ما يكون صحيحًا.","النوع <code dir=\"ltr\">Int64</code> القابل للقيم المفقودة (nullable) يحتفظ بالأعداد الصحيحة كأعداد صحيحة ويعرض الفجوات كـ <code dir=\"ltr\">&lt;NA&gt;</code>، بدلًا من تحويل العمود إلى أعداد عشرية.","المجاميع والمتوسطات والأعداد تتخطّى القيم المفقودة افتراضيًا، فيكون الفرق بين <code dir=\"ltr\">count()</code> و<code dir=\"ltr\">len()</code> هو عدد الفجوات بالضبط."],"further":[{"title":"Working with missing data","url":"https://pandas.pydata.org/docs/user_guide/missing_data.html"},{"title":"Nullable integer data type","url":"https://pandas.pydata.org/docs/user_guide/integer_na.html"},{"title":"pandas.DataFrame.fillna","url":"https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.fillna.html"}],"quiz":[{"q":"<p>في الملف الفوضوي 9 كميات فارغة. شغّل زميلك <code dir=\"ltr\">fillna(0)</code> على العمود قبل حساب متوسط الكمية لكل طلب. ماذا يحدث؟</p>","options":[{"text":"لا يتغيّر المتوسط، لأن الأصفار لا تؤثّر في المتوسط.","why":"الأصفار تُحسب قيمًا حقيقية، فتكبّر المقام وتسحب المتوسط إلى الأسفل (هنا من نحو 1.43 إلى 1.36)."},{"text":"يبقى المتوسط صحيحًا، لأن pandas تتجاهل الأصفار.","why":"تتجاهل pandas القيم المفقودة في <code dir=\"ltr\">mean()</code>، لا الأصفار. الملء حوّل “غير معروف” إلى “صفر”، وهذا ادّعاء عن البيانات."},{"text":"تُطلق pandas خطأً لأن العمود أصبح مختلطًا.","why":"الملء بـ 0 يُبقي العمود رقميًا؛ لا خطأ يظهر، ولهذا بالضبط يمرّ الخطأ دون أن يلاحظه أحد."},{"text":"ينخفض المتوسط، لأن 9 كميات مجهولة أصبحت تُحسب طلبات من صفر عناصر.","why":"صحيح. كل طلب فيه عنصر واحد على الأقل، فالصفر ليس قيمة معقولة. ترك الفجوات مفقودة يعطي المتوسط الصادق للكميات المعروفة."}],"answer":3},{"q":"<p>في ملف CSV الخام النص <code dir=\"ltr\">N/A</code> في عمود القناة في 7 صفوف. بعد <code dir=\"ltr\">pd.read_csv</code>، ماذا تعرض <code dir=\"ltr\">value_counts()</code> له؟</p>","options":[{"text":"صفًا <code dir=\"ltr\">N/A</code> بعدد 7","why":"النص <code dir=\"ltr\">N/A</code> موجود في قائمة pandas الافتراضية لعلامات القيم المفقودة، فيُحوَّل إلى <code dir=\"ltr\">NaN</code> ويُستبعد من <code dir=\"ltr\">value_counts()</code>."},{"text":"لا شيء؛ الصفوف السبعة قيم مفقودة وتُستبعد ما لم تمرّر <code dir=\"ltr\">dropna=False</code>","why":"صحيح. مرّر <code dir=\"ltr\">dropna=False</code> لتراها، أو <code dir=\"ltr\">keep_default_na=False</code> عند التحميل إذا كان يجب أن يبقى <code dir=\"ltr\">N/A</code> فئة حقيقية."},{"text":"خطأ، لأن <code dir=\"ltr\">N/A</code> ليست قناة صالحة","why":"لا تتحقّق <code dir=\"ltr\">read_csv</code> من الفئات؛ فهي إما تحتفظ بالنص وإما تحوّل علامات الفقد المعروفة."}],"answer":1},{"q":"<p><code dir=\"ltr\">q = messy[&quot;quantity&quot;].astype(&quot;Int64&quot;)</code>. ماذا تعيد <code dir=\"ltr\">q.count()</code> و<code dir=\"ltr\">len(q)</code> لـ 168 صفًا فيها 9 فجوات؟</p>","options":[{"text":"<code dir=\"ltr\">159</code> و <code dir=\"ltr\">168</code>","why":"صحيح. تعدّ <code dir=\"ltr\">count()</code> القيم غير المفقودة؛ وتعدّ <code dir=\"ltr\">len()</code> الصفوف، والفجوات منها."},{"text":"<code dir=\"ltr\">168</code> و <code dir=\"ltr\">168</code>","why":"لا تعدّ <code dir=\"ltr\">count()</code> القيم المفقودة أبدًا، أيًّا كان النوع."},{"text":"<code dir=\"ltr\">159</code> و <code dir=\"ltr\">159</code>","why":"القيم المفقودة تبقى صفوفًا؛ و<code dir=\"ltr\">len()</code> تشملها."}],"answer":0}],"exercise":{"kind":"python","files":["orders_messy.csv"],"starter":"import pandas as pd\n\nmessy = pd.read_csv(\"orders_messy.csv\")\n\n# 1. A Series: number of missing values in each column\nmissing_by_col = None\n\n# 2. Replace missing channels with the label \"unknown\" (in messy itself)\n\n# 3. Convert quantity to the nullable integer dtype \"Int64\" (in messy itself)\n\n# 4. Average quantity of the rows where it is known, rounded to 2 decimals\navg_quantity = None\n","solution":"import pandas as pd\n\nmessy = pd.read_csv(\"orders_messy.csv\")\n\nmissing_by_col = messy.isna().sum()\nprint(missing_by_col)\n\nmessy[\"channel\"] = messy[\"channel\"].fillna(\"unknown\")\nmessy[\"quantity\"] = messy[\"quantity\"].astype(\"Int64\")\n\navg_quantity = round(float(messy[\"quantity\"].mean()), 2)\n","checks":[{"id":"missing-counts","test":"isinstance(missing_by_col, pd.Series) and int(missing_by_col['quantity']) == 9 and int(missing_by_col['channel']) == 7 and int(missing_by_col.sum()) == 16"},{"id":"channel-labelled","test":"messy['channel'].isna().sum() == 0 and (messy['channel'] == 'unknown').sum() == 7"},{"id":"quantity-int64","test":"str(messy['quantity'].dtype) == 'Int64' and int(messy['quantity'].isna().sum()) == 9"},{"id":"avg","test":"round(float(avg_quantity), 2) == 1.43"},{"id":"rows-kept","test":"len(messy) == 168"}],"title":"عُدّ الفجوات وسمِّها وغيّر نوعها","prompt":"<p>حمّل الملف الفوضوي (تم ذلك نيابةً عنك). اجعل <code dir=\"ltr\">missing_by_col</code> كائن Series يعدّ القيم المفقودة في كل عمود. وفي <code dir=\"ltr\">messy</code> نفسه، استبدل القنوات المفقودة بـ <code dir=\"ltr\">&quot;unknown&quot;</code>، وحوّل <code dir=\"ltr\">quantity</code> إلى النوع <code dir=\"ltr\">&quot;Int64&quot;</code> القابل للقيم المفقودة، مع إبقاء فجواته مفقودة. ثم اجعل <code dir=\"ltr\">avg_quantity</code> متوسط الكميات المعروفة، مقرّبًا إلى منزلتين عشريتين. احتفظ بكل الصفوف الـ 168: لا حذف، ولا ملء للكميات.</p>\n","hints":["<code dir=\"ltr\">messy.isna().sum()</code> تعيد عددًا لكل عمود.","أسنِد النتائج مرة أخرى: <code dir=\"ltr\">messy[&quot;channel&quot;] = messy[&quot;channel&quot;].fillna(&quot;unknown&quot;)</code> و<code dir=\"ltr\">messy[&quot;quantity&quot;] = messy[&quot;quantity&quot;].astype(&quot;Int64&quot;)</code>.","تتخطّى <code dir=\"ltr\">mean()</code> القيم المفقودة من تلقاء نفسها: <code dir=\"ltr\">round(float(messy[&quot;quantity&quot;].mean()), 2)</code>."],"explanation":"<p>تحصل فجوات القناة على تسمية صريحة لأن “قناة غير معروفة” معلومة مفيدة في تقرير، بينما تبقى فجوات الكمية مفقودة لأنه لا توجد قيمة يمكن الدفاع عنها إلى أن تسترجعها بعملية الدمج (merge) في الدرس 4.2. يحتفظ <code dir=\"ltr\">Int64</code> (بحرف I كبير) بالعمود كأعداد صحيحة مع <code dir=\"ltr\">&lt;NA&gt;</code> للفجوات التسع، بدلًا من الأعداد العشرية <code dir=\"ltr\">1.0</code> و<code dir=\"ltr\">2.0</code> التي بدأت بها. وتتخطّى <code dir=\"ltr\">mean()</code> القيم المفقودة افتراضيًا، فيصف المتوسط 1.43 الكميات المعروفة الـ 159؛ أما الملء بـ 0 أولًا فكان سيعطي 1.36، وهو رقم لا يصف أي طلب حقيقي.</p>\n","checkLabels":{"missing-counts":"<code dir=\"ltr\">missing_by_col</code> تجد 9 كميات مفقودة و7 قنوات مفقودة","channel-labelled":"القنوات المفقودة أصبحت <code dir=\"ltr\">&quot;unknown&quot;</code>","quantity-int64":"<code dir=\"ltr\">quantity</code> من النوع <code dir=\"ltr\">Int64</code> وما زالت فيه فجواته التسع","avg":"<code dir=\"ltr\">avg_quantity</code> يساوي 1.43","rows-kept":"الصفوف الـ 168 كلها ما زالت موجودة"}},"words":611,"readMinutes":4}