{"id":"l-ml-5-1","courseId":"ml-crash-course","lang":"ar","kind":"lesson","summary":"جمّع العملاء في مجموعات باستخدام k-means، وارسم ملامح كل عنقود لترى إن كان يعني شيئًا، واضغط أعمدة كثيرة مترابطة في أعمدة قليلة باستخدام PCA.","html":"<p>ليس كل سؤال يأتي مع labels. يسأل فريق التسويق في Cartwheel: “ما أنواع العملاء لدينا؟” لم يصنّف أحد العملاء بأنواع، فلا هدف للتنبؤ به. يبحث <strong>التعلّم غير الموجَّه (unsupervised learning)</strong> عن بنية في الـ features وحدها. والأداتان اللتان ستستخدمهما أكثر من غيرهما هما العنقدة (clustering)، التي تجمّع الصفوف المتشابهة، وتقليل الأبعاد، الذي يلخّص أعمدة كثيرة في أعمدة قليلة.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">k-means في حركتين</h2>\n<p>تحتاج k-means إلى قرار واحد منك: عدد العناقيد، k. ثم تكرّر خطوتين:</p>\n<ol>\n<li><strong>الإسناد</strong>: ضع كل عميل في العنقود الذي مركزه الأقرب.</li>\n<li><strong>التحديث</strong>: انقل كل مركز إلى متوسط العملاء المسندين إليه.</li>\n</ol>\n<p>حين تتوقف الإسنادات عن التغيّر، ينتهي الأمر. وقد تعطي مراكز البداية المختلفة نتائج مختلفة، لذا يشغّل scikit-learn العملية كلها <code dir=\"ltr\">n_init</code> مرة ويحتفظ بالحل الأكثر إحكامًا.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 240\" role=\"img\" aria-labelledby=\"l-ml-5-1-t1\"><defs><marker id=\"arrow-l-ml-5-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-ml-5-1-t1\">اللوحة اليسرى: نقاط ملوّنة بحسب أقرب مركز من مركزين، يظهران كحلقتين كبيرتين. اللوحة اليمنى: انتقل كل مركز إلى وسط النقاط المسندة إليه. تتكرّر الخطوتان حتى تتوقف الإسنادات عن التغيّر.</title>\n  <rect class=\"d-box\" x=\"20\" y=\"20\" width=\"300\" height=\"180\" rx=\"10\"/>\n  <rect class=\"d-box\" x=\"360\" y=\"20\" width=\"300\" height=\"180\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"170\" y=\"225\" text-anchor=\"middle\">1. الإسناد إلى أقرب مركز</text>\n  <text class=\"d-label-strong\" x=\"510\" y=\"225\" text-anchor=\"middle\">2. نقل المركز إلى المتوسط</text>\n  <circle class=\"d-dot\" cx=\"60\" cy=\"60\" r=\"5\"/><circle class=\"d-dot\" cx=\"85\" cy=\"80\" r=\"5\"/><circle class=\"d-dot\" cx=\"70\" cy=\"110\" r=\"5\"/><circle class=\"d-dot\" cx=\"105\" cy=\"65\" r=\"5\"/>\n  <rect class=\"d-box-accent\" x=\"215\" y=\"120\" width=\"10\" height=\"10\"/><rect class=\"d-box-accent\" x=\"245\" y=\"150\" width=\"10\" height=\"10\"/><rect class=\"d-box-accent\" x=\"270\" y=\"125\" width=\"10\" height=\"10\"/><rect class=\"d-box-accent\" x=\"230\" y=\"170\" width=\"10\" height=\"10\"/>\n  <circle class=\"d-box-primary\" cx=\"140\" cy=\"110\" r=\"14\"/>\n  <circle class=\"d-box-accent\" cx=\"200\" cy=\"110\" r=\"14\"/>\n  <circle class=\"d-dot\" cx=\"400\" cy=\"60\" r=\"5\"/><circle class=\"d-dot\" cx=\"425\" cy=\"80\" r=\"5\"/><circle class=\"d-dot\" cx=\"410\" cy=\"110\" r=\"5\"/><circle class=\"d-dot\" cx=\"445\" cy=\"65\" r=\"5\"/>\n  <rect class=\"d-box-accent\" x=\"555\" y=\"120\" width=\"10\" height=\"10\"/><rect class=\"d-box-accent\" x=\"585\" y=\"150\" width=\"10\" height=\"10\"/><rect class=\"d-box-accent\" x=\"610\" y=\"125\" width=\"10\" height=\"10\"/><rect class=\"d-box-accent\" x=\"570\" y=\"170\" width=\"10\" height=\"10\"/>\n  <circle class=\"d-box-primary\" cx=\"420\" cy=\"79\" r=\"14\"/>\n  <circle class=\"d-box-accent\" cx=\"585\" cy=\"146\" r=\"14\"/>\n  <path class=\"d-arrow\" d=\"M330 110 L350 110\" marker-end=\"url(#arrow-l-ml-5-1-1)\"/>\n</svg><figcaption>جولة واحدة من k-means</figcaption></figure>\n<p>لأن “الأقرب” مسافة، فالدرس نفسه من أقرب k جيران ينطبق هنا: وحّد المقاييس أولًا، وإلا حسم <code dir=\"ltr\">total_spent</code> بالآلاف كل شيء.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">تقسيم عملاء Cartwheel إلى شرائح</h2>\n<p>استخدم أعمدة السلوك فقط واترك <code dir=\"ltr\">churned</code> خارجًا: فالتقسيم إلى شرائح يجب أن يصف العملاء، لا نتيجتهم. ستنظر إلى الانقطاع بعد ذلك لترى إن كانت الشرائح تعني شيئًا.</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 style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.cluster </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> KMeans</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.metrics </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> silhouette_score</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.pipeline </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> make_pipeline</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.preprocessing </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> StandardScaler</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">churn </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\">\"churn.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">behaviour </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"total_spent\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"avg_order_value\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"days_since_last_order\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">             \"tenure_days\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"mobile_share\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">X </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> StandardScaler().fit_transform(churn[behaviour])</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> k </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> range</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">7</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">):</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    labels </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> KMeans(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">n_clusters</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">k, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">n_init</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">10</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">random_state</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).fit_predict(X)</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:#CF222E;--shiki-dark:#FF7B72\">f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"k=</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">k</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  silhouette </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">silhouette_score(X, labels)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:.3f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</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\">segments </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> make_pipeline(StandardScaler(), KMeans(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">n_clusters</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">n_init</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">10</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">random_state</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">churn[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"segment_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> segments.fit_predict(churn[behaviour])</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">profile </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> churn.groupby(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"segment_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).agg(</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">    customers</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"size\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), </span><span style=\"color:#953800;--shiki-dark:#FFA657\">orders</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"mean\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">    avg_order</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"avg_order_value\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"mean\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), </span><span style=\"color:#953800;--shiki-dark:#FFA657\">days_since</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"days_since_last_order\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"mean\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">    tenure</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"tenure_days\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"mean\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), </span><span style=\"color:#953800;--shiki-dark:#FFA657\">churn_rate</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churned\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"mean\"</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\">(profile.round(</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=\"0\" hidden></div></div>\n<p>أولًا، الجزء الصادق. تقيس <strong>قيمة silhouette</strong> كم يكون كل عميل أقرب إلى عنقوده منه إلى العنقود التالي، من -1 إلى 1. والقيم في حدود 0.25 لكل k تعني أن العملاء لا ينقسمون إلى مجموعات طبيعية واضحة؛ بل يشكّلون طيفًا متصلًا. وهذا معتاد في البيانات السلوكية. لا يجعل ذلك العنقدة عديمة الفائدة، لكنه يعني أن k اختيارك أنت، تتخذه من أجل الفائدة، لا حقيقة اكتشفتها.</p>\n<p>ثم الجزء المفيد. رسم ملامح العناقيد الأربعة يعطي شرائح يستطيع المسوِّق تسميتها:</p>\n<ul>\n<li><strong>العابرون الغائبون</strong> (129): طلبان، آخرهما قبل نحو 310 أيام. انقطاع 79%.</li>\n<li><strong>مشترو القطع الغالية</strong> (41): طلبات قليلة بمتوسط 600 دولار. انقطاع 66%.</li>\n<li><strong>الزبائن الدائمون</strong> (79): ثمانية طلبات، آخرها قبل نحو 100 يوم. انقطاع 33%.</li>\n<li><strong>الجدد والحديثون</strong> (149): عضوية قصيرة، وطلبوا قبل نحو شهرين. انقطاع 44%.</li>\n</ul>\n<p>تختلف نِسب الانقطاع بحدّة بين الشرائح مع أن k-means لم ترَ <code dir=\"ltr\">churned</code> قط. هذا دليل على أن الشرائح تلتقط شيئًا حقيقيًا، ويعطي فريق التسويق أربع محادثات مختلفة بدل محادثة واحدة.</p>\n<p>ليست k-means الخيار الوحيد. فهي تفترض عناقيد شبه مستديرة ومتشابهة الحجم، وهو افتراض يكفي عادةً للشرائح السلوكية. أما العنقدة الهرمية (<code dir=\"ltr\">AgglomerativeClustering</code>) فتُظهر كيف تندمج المجموعات على مستويات مختلفة، و<code dir=\"ltr\">DBSCAN</code> يجد المناطق الكثيفة بأي شكل ويصنّف القيم الشاذة كضجيج. ابدأ بـ k-means للتقسيم إلى شرائح؛ وبدّل حين لا تناسب افتراضاتها بياناتك بوضوح.</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>تُعيد k-means دائمًا k مجموعة، حتى من الضجيج العشوائي. أرقام العناقيد لا معنى لها، وقد تتغيّر بين تشغيل وآخر. قبل أن تسمّي عنقودًا “كبار العملاء”، اطبع متوسطاته وأحجامه، وتحقّق من ثباته عبر بضع قيم لـ <code dir=\"ltr\">random_state</code>، وتأكّد أنه يهمّ في شيء تكترث به الشركة.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">PCA: أعمدة أقل، ومعظم المعلومات</h2>\n<p>في كثير من مجموعات البيانات عشرات الأعمدة المترابطة. بيانات سرطان الثدي فيها 30 قياسًا لأنوية الخلايا، كثير منها تنويعات على الحجم. يجد <strong>تحليل المكوّنات الرئيسية (PCA)</strong> محاور جديدة، كلٌّ منها مزيج موزون من الأعمدة الأصلية، مرتّبة بحسب مقدار ما تلتقطه من تباين البيانات. احتفظ بالأولى منها ويصبح لديك ملخّص مضغوط.</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\"> numpy </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> np</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.datasets </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> load_breast_cancer</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.decomposition </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> PCA</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.pipeline </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> make_pipeline</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.preprocessing </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> StandardScaler</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">X, y </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> load_breast_cancer(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">return_X_y</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\">pca </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> make_pipeline(StandardScaler(), PCA()).fit(X)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">ratios </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pca[</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\">].explained_variance_ratio_</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\">\"first two components keep:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, ratios[:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</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 style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"=\"</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\">(ratios[:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].sum(), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</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:#0A3069;--shiki-dark:#A5D6FF\">\"components needed for 90%:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">int</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(np.argmax(np.cumsum(ratios) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">>=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.90</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)) </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\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">coords </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> make_pipeline(StandardScaler(), PCA(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">n_components</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)).fit_transform(X)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> label, name </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"malignant\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), (</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"benign\"</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\">(name, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"average position:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, coords[y </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">==</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> label].mean(</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\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).round(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>يحتفظ مكوّنان بـ 63% من التباين في 30 عمودًا موحّد المقاييس، وتحتفظ سبعة مكوّنات بـ 90%. ارسم الإحداثيين وستجد الأورام الخبيثة والحميدة على جانبين متقابلين من المحور الأول، مع أن PCA لم يرَ الـ labels قط. ولهذا يُعدّ رسم PCA ثنائي الأبعاد نظرة أولى معيارية على أي مجموعة بيانات عريضة.</p>\n<aside class=\"callout callout-note\" 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\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4\"/><path d=\"M12 8h.01\"/></svg><span><span class=\"callout-kind\">ملاحظة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>متى يساعد PCA النموذج</span></p>\n<p>يمكن أن يكون PCA أيضًا خطوة في pipeline قبل النموذج، لتقليل الضجيج أو تسريع التدريب على بيانات عريضة جدًا. أما في الجداول التي فيها دزينة من الأعمدة ذات المعنى، مثل بيانات الانقطاع، فعادةً ما يكلّف قابلية التفسير ولا يكسب شيئًا. استخدمه للاستكشاف أولًا؛ ولا تضفه إلى نموذج إلا حين يقول التحقق المتقاطع إنه يساعد.</p>\n</aside>\n<p>كلتا التقنيتين هنا كلاسيكية ورخيصة. أما الدرس القادم فعن نماذج تتعلّم features خاصة بها من المدخلات الخام: الشبكات العصبية.</p>\n","toc":[{"id":"sec-1","text":"k-means في حركتين","level":2},{"id":"sec-2","text":"تقسيم عملاء Cartwheel إلى شرائح","level":2},{"id":"sec-3","text":"PCA: أعمدة أقل، ومعظم المعلومات","level":2}],"takeaways":["تتناوب k-means بين خطوتين، إسناد كل نقطة إلى أقرب مركز ونقل كل مركز إلى متوسط نقاطه، حتى لا يتغيّر شيء.","تستخدم العنقدة المسافات، فوحّد مقاييس الـ features أولًا، واضبط <code dir=\"ltr\">n_init</code> و<code dir=\"ltr\">random_state</code> لنتائج قابلة للتكرار.","العناقيد لا تكون مفيدة إلا إذا استطعت وصفها؛ فارسم ملامح كلٍّ منها بمتوسطات المجموعة قبل أن تعطيه اسمًا.","قيمة silhouette المنخفضة تعني أن البيانات ليست فيها مجموعات طبيعية واضحة؛ وقد تظل الشرائح مفيدة، لكنها اختيار لا اكتشاف.","يجد PCA محاور جديدة تلتقط أكبر قدر من التباين؛ ويخبرك <code dir=\"ltr\">explained_variance_ratio_</code> كم يحتفظ به كل مكوّن."],"further":[{"title":"Clustering: K-means","url":"https://scikit-learn.org/stable/modules/clustering.html#k-means"},{"title":"KMeans","url":"https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html"},{"title":"Principal component analysis (PCA)","url":"https://scikit-learn.org/stable/modules/decomposition.html#pca"}],"quiz":[{"q":"<p>شغّلت k-means على أعمدة الانقطاع الخام دون توحيد المقاييس. أيّ عمود سيهيمن على العناقيد؟</p>","options":[{"text":"<code dir=\"ltr\">used_coupon</code>، لأن الأعمدة الثنائية تفصل المجموعات بوضوح.","why":"عمود 0/1 لا يساهم بأكثر من 1 في المسافة المربّعة، وهذا لا شيء بجانب المبالغ بالدولار."},{"text":"<code dir=\"ltr\">days_since_last_order</code>، لأنه أفضل متنبئ بالانقطاع.","why":"لا ترى k-means الهدف أبدًا؛ إنها لا ترى إلا المسافات، والمسافات يحرّكها المقياس."},{"text":"كل الأعمدة تساهم بالتساوي، لأن k-means تطبّع داخليًا.","why":"لا يوحّد KMeans المقاييس بنفسه؛ عليك أن تضيف <code dir=\"ltr\">StandardScaler</code>."},{"text":"<code dir=\"ltr\">total_spent</code>، لأن قيمه بالآلاف تطغى على كل مسافة أخرى.","why":"صحيح. دون توحيد المقاييس، تجمّع k-means العملاء في الغالب بحسب الإنفاق."}],"answer":3},{"q":"<p>قيم silhouette من k = 2 إلى 7 كلها بين 0.25 و0.27. ماذا يخبرك ذلك؟</p>","options":[{"text":"البيانات ليس فيها تجمّع طبيعي قوي، فاختر k بحسب الفائدة وقابلية الشرح.","why":"صحيح. القيم المنخفضة المتقاربة تعني أن لا قيمة لـ k تبرز؛ والتقسيم إلى شرائح اختيار نمذجي."},{"text":"k = 7 هي الأفضل لأن نتيجتها الأعلى.","why":"فروق بهذا الصغر ضجيج، وسبع شرائح أصعب على الفريق في الاستخدام من أربع."},{"text":"فشلت k-means ويجب استبدالها بنموذج موجَّه.","why":"ضعف بنية العناقيد لا يجعل العنقدة عديمة الفائدة، والنموذج الموجَّه يحتاج إلى labels ليست لديك للشرائح."}],"answer":0},{"q":"<p>يعطي PCA على features سرطان الثدي الثلاثين قيم <code dir=\"ltr\">explained_variance_ratio_</code> تساوي 0.44 و0.19 لأول مكوّنين. ماذا يُظهر رسم ثنائي الأبعاد لهما؟</p>","options":[{"text":"أهم اثنين من الـ features الأصلية.","why":"المكوّنات تركيبات موزونة من الـ features الثلاثين كلها، لا اثنان من الأصلية."},{"text":"منظرًا يحتفظ بنحو 63% من التباين في البيانات الموحّدة المقاييس.","why":"صحيح. إنها خريطة مفيدة، لكن أكثر من ثلث التباين لا يظهر فيها."},{"text":"الفئتين، مفصولتين تمامًا، لأن PCA يستخدم الـ labels.","why":"PCA غير موجَّه؛ ولا يرى الـ labels أبدًا. وأي فصل تراه يأتي من بنية البيانات."}],"answer":1}],"exercise":{"kind":"python","files":["churn.csv"],"starter":"import numpy as np\nimport pandas as pd\nfrom sklearn.cluster import KMeans\nfrom sklearn.datasets import load_wine\nfrom sklearn.decomposition import PCA\nfrom sklearn.pipeline import make_pipeline\nfrom sklearn.preprocessing import StandardScaler\n\nchurn = pd.read_csv(\"churn.csv\")\nbehaviour = [\"orders\", \"total_spent\", \"avg_order_value\", \"days_since_last_order\",\n             \"tenure_days\", \"mobile_share\"]\n\n# 1. Segment customers into 4 clusters: StandardScaler, then KMeans(n_clusters=4, n_init=10, random_state=42)\nsegments = make_pipeline(KMeans(n_clusters=4, n_init=10, random_state=42))\nlabels = segments.fit_predict(churn[behaviour])\n\n# 2. Churn rate per cluster (a Series indexed by cluster label), and the highest rate\nchurn_by_cluster = None\nhighest_rate = None\n\n# 3. PCA on the wine data (13 scaled features): share of variance kept by the first 2 components\nX_wine, y_wine = load_wine(return_X_y=True)\nwine_var_2 = None\n","solution":"import numpy as np\nimport pandas as pd\nfrom sklearn.cluster import KMeans\nfrom sklearn.datasets import load_wine\nfrom sklearn.decomposition import PCA\nfrom sklearn.pipeline import make_pipeline\nfrom sklearn.preprocessing import StandardScaler\n\nchurn = pd.read_csv(\"churn.csv\")\nbehaviour = [\"orders\", \"total_spent\", \"avg_order_value\", \"days_since_last_order\",\n             \"tenure_days\", \"mobile_share\"]\n\nsegments = make_pipeline(StandardScaler(), KMeans(n_clusters=4, n_init=10, random_state=42))\nlabels = segments.fit_predict(churn[behaviour])\n\nchurn_by_cluster = churn.groupby(labels)[\"churned\"].mean()\nhighest_rate = churn_by_cluster.max()\n\nX_wine, y_wine = load_wine(return_X_y=True)\npca = make_pipeline(StandardScaler(), PCA(n_components=2)).fit(X_wine)\nwine_var_2 = pca[-1].explained_variance_ratio_.sum()\nprint(churn_by_cluster.round(2).to_dict(), round(wine_var_2, 3))\n","checks":[{"id":"scaled","test":"[type(s).__name__ for s in segments] == ['StandardScaler', 'KMeans']"},{"id":"clusters","test":"labels is not None and len(labels) == 398 and len(set(labels)) == 4 and sorted(__import__('collections').Counter(labels).values()) == sorted(__import__('collections').Counter(__import__('sklearn.pipeline', fromlist=['x']).make_pipeline(__import__('sklearn.preprocessing', fromlist=['x']).StandardScaler(), __import__('sklearn.cluster', fromlist=['x']).KMeans(n_clusters=4, n_init=10, random_state=42)).fit_predict(churn[behaviour])).values()) and min(__import__('collections').Counter(labels).values()) > 20"},{"id":"churn-rates","test":"churn_by_cluster is not None and len(churn_by_cluster) == 4 and sorted(float(v) for v in churn_by_cluster) == sorted(float(v) for v in churn.groupby(labels)['churned'].mean()) and abs(float(highest_rate) - float(max(churn_by_cluster))) < 1e-9 and 0.7 < float(highest_rate) < 0.9"},{"id":"pca","test":"wine_var_2 is not None and abs(float(wine_var_2) - 0.554) < 0.005"}],"title":"شرائح ومنظر مضغوط","prompt":"<ol>\n<li>أصلح <code dir=\"ltr\">segments</code> بحيث تُوحَّد مقاييس العملاء قبل k-means (4 عناقيد، <code dir=\"ltr\">n_init=10</code>، <code dir=\"ltr\">random_state=42</code>)، واحصل على label واحد لكل عميل في <code dir=\"ltr\">labels</code>.</li>\n<li><code dir=\"ltr\">churn_by_cluster</code>: نسبة الانقطاع في كل عنقود، كـ Series فهرسها label العنقود. و<code dir=\"ltr\">highest_rate</code>: أكبر هذه النِّسب.</li>\n<li><code dir=\"ltr\">wine_var_2</code>: حصة التباين التي يحتفظ بها أول مكوّنين رئيسيين للـ features الثلاثة عشر في بيانات النبيذ، بعد توحيد المقاييس.</li>\n</ol>\n","hints":["ضع <code dir=\"ltr\">StandardScaler()</code> قبل <code dir=\"ltr\">KMeans(...)</code> في <code dir=\"ltr\">make_pipeline</code>.","يجمّع <code dir=\"ltr\">churn.groupby(labels)[&quot;churned&quot;].mean()</code> بحسب مصفوفة الـ labels مباشرة.","درّب <code dir=\"ltr\">make_pipeline(StandardScaler(), PCA(n_components=2))</code> على <code dir=\"ltr\">X_wine</code> واجمع <code dir=\"ltr\">pipe[-1].explained_variance_ratio_</code>."],"explanation":"<p>تجد k-means بعد توحيد المقاييس الشرائح الأربع من الدرس (129 و41 و79 و149 عميلًا)، وينقطع العابرون الغائبون بنسبة 79% مع أن الهدف لم يُستخدم في العنقدة قط. ودون توحيد المقاييس، كان <code dir=\"ltr\">total_spent</code> سيهيمن، وكانت الشرائح ستفرز العملاء بحسب الإنفاق في الغالب. وفي بيانات النبيذ، يحتفظ مكوّنان بنحو 55% من التباين في 13 قياسًا: ما يكفي لخريطة مفيدة، لا ما يكفي لاستبدال الأعمدة الأصلية.</p>\n","checkLabels":{"scaled":"يوحّد segments المقاييس قبل العنقدة","clusters":"تحمل labels الشرائح الأربع المتوقعة","churn-rates":"churn_by_cluster وhighest_rate صحيحان","pca":"wine_var_2 هو التباين الذي يحتفظ به مكوّنان"}},"words":615,"readMinutes":4}