{"id":"l-pda-3-2","courseId":"python-data-analysis","lang":"ar","kind":"lesson","summary":"نظّف الأعمدة النصية باستخدام الـ accessor المسمّى ‎.str - احذف المسافات، ووحّد حالة الأحرف، واستبدل الحروف، وقسّم الأسماء، وابحث عن الأنماط - ثم تحقّق من النتيجة مقابل القيم التي تتوقّعها.","html":"<p>اسأل الملف الفوضوي: إلى كم دولة تبيع Cartwheel؟ فيقول 22. الإجابة الحقيقية ثماني دول. والأربع عشرة الأخرى هي الدول نفسها مكتوبة بالشكل <code dir=\"ltr\">UNITED KINGDOM</code> و<code dir=\"ltr\">united kingdom</code> وغيرهما. جمّع الإيراد بحسب الدولة الآن، فتتوزّع مبيعات المملكة المتحدة على ثلاثة صفوف، كلٌّ منها أصغر من حقيقته. تنظيف النصوص عمل غير لامع، لكنه الفرق بين رسم بياني صحيح وآخر خاطئ.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الـ accessor المسمّى ‎.str</h2>\n<p>للـ Series النصية خاصية <code dir=\"ltr\">.str</code> تكشف methods النصوص في Python، وتطبّقها على كل القيم دفعة واحدة:</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\">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[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"country\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].nunique(), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"spellings\"</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\">\"country\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].value_counts().tail(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">6</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\">clean </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\">\"country\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].str.strip().str.title()</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(clean.nunique(), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"countries\"</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\">sorted</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(clean.unique()))</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>كل استدعاء لـ <code dir=\"ltr\">.str</code> يعيد Series جديدة، فتسلسلها: احذف المسافات، ثم وحّد حالة الأحرف. تصبح الكتابات الـ 22 ثماني، وهذا يطابق الدول الثماني التي تبيع لها Cartwheel فعلًا. والقيم المفقودة تمرّ مفقودة بدلًا من أن تسبّب خطأً، وهذه هي الميزة الأساسية مقارنةً بكتابة حلقة تستدعي <code dir=\"ltr\">.strip()</code> على كل قيمة بنفسك. وعدّ القيم المختلفة قبل التنظيف وبعده، كما يفعل أمرا الطباعة الأول والثالث، هو أسرع دليل على أن التنظيف فعل ما قصدته.</p>\n<p>تعمل <code dir=\"ltr\">title()</code> هنا لأن كل دول Cartwheel أسماء مكوّنة من كلمات عادية. لكنها ستُفسد قيمًا مثل <code dir=\"ltr\">USA</code> (فتصبح <code dir=\"ltr\">Usa</code>) أو <code dir=\"ltr\">McDonald</code> (فتصبح <code dir=\"ltr\">Mcdonald</code>). حين يكون في العمود قيم كهذه، استخدم <code dir=\"ltr\">str.lower()</code> للمقارنة، أو قاموس تحويل مع <code dir=\"ltr\">map()</code> كما في الدرس 2.5.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">المسافات غير المرئية</h2>\n<p>ثلاثة عشر اسم عميل في الملف المُصدَّر فيها مسافات قبلها أو بعدها. تبدو متطابقة في الجدول، فالطريقة الوحيدة لرؤيتها هي القياس:</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\">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\">padded </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\">\"customer\"</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\">\"customer\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].str.strip()</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(padded.sum(), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"padded names\"</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.loc[padded, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"customer\"</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\">).map(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">repr</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).tolist())</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">messy[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"customer\"</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\">\"customer\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].str.strip()</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>تعرض <code dir=\"ltr\">repr()</code> النص مع علامات تنصيصه، فتجعل المسافات مرئية: <code dir=\"ltr\">'  Hassan Hughes '</code>. الأسماء التي لم تُحذف مسافاتها تكسر بالضبط العمليات التي ستعتمد عليها لاحقًا: التجميع، والمطابقة مع جدول آخر، وحذف المكرّرات. احذف المسافات من كل عمود نصي ستجمّع أو تربط عليه، حتى لو بدا نظيفًا. وتحذف <code dir=\"ltr\">str.strip()</code> أكثر من المسافات العادية: علامات الجدولة (tabs)، وفواصل الأسطر، والمسافات غير القابلة للكسر التي يحملها كثيرًا النص المنسوخ من صفحات الويب وملفات PDF، كلها مسافات بيضاء بالنسبة إلى Python، فيتعامل معها استدعاء واحد.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">الاستبدال والتقسيم والبحث</h2>\n<p>يكتب الملف المُصدَّر القناة <code dir=\"ltr\">mobile app</code>، بينما تستخدم جداول Cartwheel النظيفة <code dir=\"ltr\">mobile_app</code>. مطابقة الأعراف الآن توفّر عليك ربطًا فاشلًا في القسم 4:</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\">].str.replace(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\" \"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"_\"</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 style=\"color:#953800;--shiki-dark:#FFA657\">dropna</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">names </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\">\"customer\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].str.strip().str.split(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\" \"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">expand</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:#1F2328;--shiki-dark:#E6EDF3\">names.columns </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"first_name\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"last_name\"</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\">(names.head(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</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\">coffee </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\">\"product\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].str.contains(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"coffee\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">case</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\">(coffee.sum(), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"coffee lines\"</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\">str.split(&quot; &quot;, expand=True)</code> جدولًا (DataFrame) فيه عمود لكل جزء؛ وبدون <code dir=\"ltr\">expand</code> تحصل على Series من القوائم، و<code dir=\"ltr\">.str[0]</code> تختار الجزء الأول من كل منها. وتعيد <code dir=\"ltr\">str.contains()</code> قناعًا منطقيًا جاهزًا للتصفية كما في الدرس 2.4، و<code dir=\"ltr\">case=False</code> تتجاهل حالة الأحرف.</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>الوصول بالنقطة إلى عمود اسمه product</span></p>\n<p>يفشل <code dir=\"ltr\">messy.product.str.contains(&quot;Coffee&quot;)</code> بالخطأ <code dir=\"ltr\">AttributeError: 'function' object has no attribute 'str'</code>، لأن <code dir=\"ltr\">product</code> هو أيضًا اسم method في الـ DataFrame تضرب القيم. هذه مشكلة الحجب من الدرس 2.3 في الواقع العملي. الأقواس المربعة، <code dir=\"ltr\">messy[&quot;product&quot;]</code>، تصل إلى العمود دائمًا.</p>\n</aside>\n<h3 id=\"sec-4\" tabindex=\"-1\">نص عادي أم نمط؟</h3>\n<p>تستطيع <code dir=\"ltr\">str.replace</code> و<code dir=\"ltr\">str.contains</code> أيضًا أن تأخذا <strong>تعابير نمطية (regular expressions)</strong>، وهي لغة مصغّرة لأنماط النصوص. في pandas 2 تعامل <code dir=\"ltr\">str.replace</code> الوسيط الأول كنص عادي ما لم تمرّر <code dir=\"ltr\">regex=True</code>، وتعامله <code dir=\"ltr\">str.contains</code> كنمط ما لم تمرّر <code dir=\"ltr\">regex=False</code>. ويوقعك هذا الفرق في المشكلات مع الرموز التي لها معنى خاص في الأنماط، مثل <code dir=\"ltr\">$</code> و<code dir=\"ltr\">.</code> و<code dir=\"ltr\">(</code>. ويعرض الدرس 3.3 حالة <code dir=\"ltr\">$</code>، التي تمسّ كل عمود مبالغ مالية ستنظّفه في حياتك.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">أثبت أن العمود نظيف</h2>\n<p>كود التنظيف الذي نجح مع ملف هذا الشهر قد يقابل كتابة جديدة الشهر القادم. اختم بفحص يحدّد معنى “نظيف”:</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:#0550AE;--shiki-dark:#79C0FF\">EXPECTED</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Egypt\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"United Arab Emirates\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Saudi Arabia\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Jordan\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">            \"United Kingdom\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Germany\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"United States\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Canada\"</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\">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\">\"country\"</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\">\"country\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].str.strip().str.title()</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">unexpected </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> set</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(messy[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"country\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> EXPECTED</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">assert</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> not</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> unexpected, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Unknown countries: </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">unexpected</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</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:#0A3069;--shiki-dark:#A5D6FF\">\"country column OK:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, messy[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"country\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].nunique(), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"values\"</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\">assert condition, message</code> شيئًا حين يكون الشرط صحيحًا، وتوقف الـ script برسالتك حين يكون خاطئًا. طرح المجموعات يسرد أيّ قيمة منظّفة ليست في القائمة المتوقّعة، وتضعها الـ f-string في رسالة الخطأ لتعرف بالضبط ما الذي تصلحه.</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>النوع category</span></p>\n<p>العمود النصي الذي فيه قيم قليلة متكرّرة، مثل الدولة أو القناة، يمكن تخزينه بـ <code dir=\"ltr\">astype(&quot;category&quot;)</code>. يستهلك ذاكرة أقل ويسجّل القيم المسموح بها. وهو اختياري لجدول من 168 صفًا؛ استخدمه حين يكون في الملف ملايين الصفوف، ومرّر <code dir=\"ltr\">observed=True</code> حين تجمّع بحسبه.</p>\n</aside>\n<p>في التمرين تنظّف الأعمدة النصية الثلاثة في الملف المُصدَّر. وبعدها تأتي المجاميع: أرقام محبوسة داخل نصوص مثل <code dir=\"ltr\">$1,234.50</code>.</p>\n","toc":[{"id":"sec-1","text":"الـ accessor المسمّى ‎.str","level":2},{"id":"sec-2","text":"المسافات غير المرئية","level":2},{"id":"sec-3","text":"الاستبدال والتقسيم والبحث","level":2},{"id":"sec-4","text":"نص عادي أم نمط؟","level":3},{"id":"sec-5","text":"أثبت أن العمود نظيف","level":2}],"takeaways":["الـ accessor المسمّى <code dir=\"ltr\">.str</code> يطبّق method نصية على كل قيمة في العمود، وتبقى القيم المفقودة مفقودة بدلًا من أن تسبّب أخطاء.","<code dir=\"ltr\">str.strip()</code> وحالة أحرف موحّدة (<code dir=\"ltr\">str.title()</code> أو <code dir=\"ltr\">str.lower()</code>) تصلح معظم الكتابات المختلفة؛ تحقّق منها بـ <code dir=\"ltr\">nunique()</code> قبل وبعد.","<code dir=\"ltr\">str.replace(old, new)</code> تعامل <code dir=\"ltr\">old</code> كنص عادي افتراضيًا في pandas 2؛ مرّر <code dir=\"ltr\">regex=True</code> فقط حين تقصد نمطًا.","<code dir=\"ltr\">str.split(&quot; &quot;, expand=True)</code> تحوّل عمودًا نصيًا واحدًا إلى عدة أعمدة، و<code dir=\"ltr\">str.contains()</code> تبني قناعًا منطقيًا من نمط.","اختم كل تنظيف نصي بفحص مقابل قائمة القيم التي تتوقّعها، كي تفشل كتابة جديدة الشهر القادم بصوت عالٍ."],"further":[{"title":"Working with text data","url":"https://pandas.pydata.org/docs/user_guide/text.html"},{"title":"pandas.Series.str.replace","url":"https://pandas.pydata.org/docs/reference/api/pandas.Series.str.replace.html"},{"title":"pandas.Series.str.split","url":"https://pandas.pydata.org/docs/reference/api/pandas.Series.str.split.html"}],"quiz":[{"q":"<p>في <code dir=\"ltr\">messy[&quot;country&quot;]</code> 22 قيمة مختلفة لثماني دول حقيقية. أيّ سطر هو الإصلاح الذي يجب أن تكتبه؟</p>","options":[{"text":"<code dir=\"ltr\">messy[&quot;country&quot;].str.title()</code>","why":"يعطي 8 في هذا الملف مصادفةً، لأن كتابات الدول فيه لا تختلف إلا في حالة الأحرف. لكن قيمة فيها مسافة زائدة في ملف الشهر القادم ستبقى مختلفة عن نظيرتها النظيفة، فاحذف المسافات أولًا كما تفعل مع أي عمود تجمّع بحسبه."},{"text":"<code dir=\"ltr\">messy[&quot;country&quot;].str.strip().str.title()</code>","why":"صحيح. حذف المسافات يزيل الفروق غير المرئية، وحالة العنوان (title case) تجعل <code dir=\"ltr\">EGYPT</code> و<code dir=\"ltr\">egypt</code> و<code dir=\"ltr\">Egypt</code> قيمة واحدة."},{"text":"<code dir=\"ltr\">messy[&quot;country&quot;].unique()</code>","why":"هذا يسرد الكتابات الـ 22؛ ولا يغيّر أيًّا منها."},{"text":"<code dir=\"ltr\">messy[&quot;country&quot;].str.replace(&quot; &quot;, &quot;&quot;)</code>","why":"هذا يحذف المسافات داخل الأسماء، فتصبح <code dir=\"ltr\">United Kingdom</code> <code dir=\"ltr\">UnitedKingdom</code>، ويترك اختلافات حالة الأحرف كما هي."}],"answer":1},{"q":"<p>لماذا يسبّب <code dir=\"ltr\">messy.product.str.contains(&quot;Coffee&quot;)</code> الخطأ <code dir=\"ltr\">AttributeError: 'function' object has no attribute 'str'</code>؟</p>","options":[{"text":"<code dir=\"ltr\">product</code> اسم method في الـ DataFrame، فيعيد الوصول بالنقطة الـ method بدلًا من العمود.","why":"صحيح. الـ method <code dir=\"ltr\">DataFrame.product()</code> تضرب القيم، وتحجب العمود. أما <code dir=\"ltr\">messy[&quot;product&quot;]</code> فتصل إلى العمود دائمًا."},{"text":"العمود فيه قيم مفقودة.","why":"القيم المفقودة تجعل methods الـ <code dir=\"ltr\">.str</code> تعيد <code dir=\"ltr\">NaN</code> لتلك الصفوف؛ ولا تسبّب <code dir=\"ltr\">AttributeError</code>."},{"text":"<code dir=\"ltr\">contains</code> لا تعمل إلا على الأعمدة المحمّلة بـ <code dir=\"ltr\">dtype=&quot;string&quot;</code>.","why":"تعمل <code dir=\"ltr\">.str.contains</code> على الأعمدة النصية العادية (object) أيضًا."}],"answer":0},{"q":"<p>نظّفت عمود الدولة ثم شغّلت <code dir=\"ltr\">assert set(clean) &lt;= set(EXPECTED)</code>. ما فائدة هذا الـ assertion؟</p>","options":[{"text":"يسرّع عمليات groupby اللاحقة.","why":"الـ assertion يفحص شرطًا فقط؛ ولا يغيّر البيانات أو الأداء."},{"text":"يرتّب الدول أبجديًا.","why":"المجموعات (sets) ليس لها ترتيب، والفحص لا يغيّر شيئًا."},{"text":"يوقف الـ script إذا كانت أيّ قيمة منظّفة ليست من الدول المعروفة، مثل خطأ كتابي جديد الشهر القادم.","why":"صحيح. assertion فاشل أفضل بكثير من تقرير فيه “دولة” تاسعة تحمل بهدوء جزءًا من الإيراد."}],"answer":2}],"exercise":{"kind":"python","files":["orders_messy.csv"],"starter":"import pandas as pd\n\nmessy = pd.read_csv(\"orders_messy.csv\")\n\n# 1. customer: remove leading/trailing spaces\n\n# 2. country: remove spaces and use title case (\"UNITED KINGDOM\" -> \"United Kingdom\")\n\n# 3. channel: \"mobile app\" -> \"mobile_app\", and missing -> \"unknown\"\n\n# 4. New column first_name: the first word of the cleaned customer name\n\n# 5. Number of rows whose product name mentions \"desk\" (any capitalisation)\ndesk_lines = None\n","solution":"import pandas as pd\n\nmessy = pd.read_csv(\"orders_messy.csv\")\n\nmessy[\"customer\"] = messy[\"customer\"].str.strip()\nmessy[\"country\"] = messy[\"country\"].str.strip().str.title()\nmessy[\"channel\"] = messy[\"channel\"].str.replace(\" \", \"_\").fillna(\"unknown\")\nmessy[\"first_name\"] = messy[\"customer\"].str.split(\" \").str[0]\n\ndesk_lines = messy[\"product\"].str.contains(\"desk\", case=False).sum()\nprint(messy[\"country\"].value_counts())\n","checks":[{"id":"customer","test":"(messy['customer'] == messy['customer'].str.strip()).all() and messy['customer'].nunique() == 122"},{"id":"country","test":"set(messy['country']) == {'Egypt', 'United Arab Emirates', 'Saudi Arabia', 'Jordan', 'United Kingdom', 'Germany', 'United States', 'Canada'}"},{"id":"channel","test":"set(messy['channel']) == {'web', 'mobile_app', 'marketplace', 'unknown'} and (messy['channel'] == 'unknown').sum() == 7"},{"id":"first-name","test":"'first_name' in messy.columns and messy['first_name'].iloc[0] == 'Daniel' and not messy['first_name'].str.contains(' ').any()"},{"id":"desk","test":"int(desk_lines) == 15"}],"title":"نظّف الأعمدة النصية في الملف المُصدَّر","prompt":"<p>في <code dir=\"ltr\">messy</code>: احذف المسافات من <code dir=\"ltr\">customer</code>؛ واحذف المسافات من <code dir=\"ltr\">country</code> وحوّله إلى حالة العنوان (title case)؛ وفي <code dir=\"ltr\">channel</code>، استبدل المسافة بشرطة سفلية (<code dir=\"ltr\">mobile app</code> ← <code dir=\"ltr\">mobile_app</code>) واملأ القيم المفقودة بـ <code dir=\"ltr\">&quot;unknown&quot;</code>. أضِف عمودًا <code dir=\"ltr\">first_name</code> يحمل الكلمة الأولى من اسم العميل المنظّف. وأخيرًا اجعل <code dir=\"ltr\">desk_lines</code> عدد الصفوف التي يحتوي فيها <code dir=\"ltr\">product</code> على “desk” بأيّ حالة أحرف.</p>\n","hints":["سلسِل methods الـ <code dir=\"ltr\">.str</code> وأسنِد النتيجة إلى العمود نفسه: <code dir=\"ltr\">messy[&quot;country&quot;] = messy[&quot;country&quot;].str.strip().str.title()</code>.","تترك <code dir=\"ltr\">.str.replace(&quot; &quot;, &quot;_&quot;)</code> القيم المفقودة مفقودة، فتستطيع تسلسل <code dir=\"ltr\">.fillna(&quot;unknown&quot;)</code> بعدها مباشرة.","<code dir=\"ltr\">messy[&quot;customer&quot;].str.split(&quot; &quot;).str[0]</code> تأخذ الكلمة الأولى. وللعدّ، <code dir=\"ltr\">str.contains(&quot;desk&quot;, case=False).sum()</code>؛ واستخدم <code dir=\"ltr\">messy[&quot;product&quot;]</code> لا <code dir=\"ltr\">messy.product</code>."],"explanation":"<p>بعد حذف المسافات وتحويل حالة الأحرف، تتقلّص كتابات الدول الـ 22 إلى الدول الثماني التي تبيع لها Cartwheel بالضبط، ويبقى 122 اسم عميل مختلفًا بعد أن تتطابق المكرّرات المحاطة بالمسافات مع نظيراتها المنظّفة. والقناة الآن تستخدم الأكواد نفسها التي في جدول <code dir=\"ltr\">orders</code> النظيف، فيمكن مقارنة الاثنين أو ربطهما لاحقًا. تأخذ <code dir=\"ltr\">str.split(&quot; &quot;).str[0]</code> الجزء الأول من كل تقسيم؛ أما <code dir=\"ltr\">expand=True</code> فتعطيك كل الأجزاء كأعمدة. وتجد <code dir=\"ltr\">case=False</code> كلًّا من <code dir=\"ltr\">Standing Desk 140cm</code> و<code dir=\"ltr\">Compact Writing Desk</code> و<code dir=\"ltr\">Desk Mat XL</code>: 15 بندًا.</p>\n","checkLabels":{"customer":"أسماء العملاء ليست فيها مسافات في البداية أو النهاية","country":"<code dir=\"ltr\">country</code> يحمل الدول الحقيقية الثماني بالضبط","channel":"<code dir=\"ltr\">channel</code> يستخدم <code dir=\"ltr\">web</code> و<code dir=\"ltr\">mobile_app</code> و<code dir=\"ltr\">marketplace</code> و<code dir=\"ltr\">unknown</code>","first-name":"<code dir=\"ltr\">first_name</code> يحمل كلمة واحدة لكل صف، أولها Daniel","desk":"<code dir=\"ltr\">desk_lines</code> يساوي 15"}},"words":608,"readMinutes":4}