النص الكامل للمحاضرة 25: الرسم البياني
النص الكامل للمحاضرة 25: الرسم البياني
المصدر الأصلي: النص الرسمي الإنجليزي، PDF، صفحة المحاضرة الرسمية، فيديو المحاضرة الرسمي.
النسبة والترخيص: آنا بيل (Ana Bell)، مقرر 6.100L: مقدمة في علوم الحاسوب والبرمجة باستخدام Python، خريف 2022، معهد ماساتشوستس للتكنولوجيا، MIT OpenCourseWare. هذه ترجمة عربية تعليمية غير رسمية، وليست معتمدة من MIT ولا تعني تأييده لها. تخضع المواد الأصلية المشمولة والترجمة لرخصة المشاع الإبداعي: النسبة–غير تجاري–المشاركة بالمثل 4.0 (CC BY-NC-SA 4.0): يجب حفظ النسبة، والاستخدام غير التجاري، ومشاركة التكييفات بالرخصة نفسها. لا تشمل الرخصة الافتراضية مواد الأطراف الثالثة المستثناة؛ انظر شروط MIT OCW.
منهج الترجمة والتغطية: تُترجم هنا صفحات التفريغ الرسمي الثماني عشرة غير الفارغة، وفق فواصل الصفحات الفعلية في ملف PDF المستخرج، دون تلخيص للفقرات أو حذف للتكرار والتصحيحات الشفهية ومداخلات الحضور والإشارات الصوتية. عناوين «صفحة المصدر» فواصل مادية لا موضوعية. تُحفظ أسماء Python والمعرّفات والتعبيرات الرياضية، وتُقرن المصطلحات المهمة بالإنجليزية. الملاحظات الموسومة «ملاحظة المترجم» إضافات توضيحية منفصلة عن كلام المصدر، وليست تصحيحات صامتة له. لا تُستنسخ هنا رسوم الشرائح أو ملفات البيانات الخارجية؛ النص يترجم وصفها المنطوق كما ورد في التفريغ.
صفحة المصدر 1
MITOCW | المحاضرة 25 من 6.100L، متعدد.mp4
[صرير]
[حفيف]
[نقرات]
آنا بيل (ANA BELL):
حسنًا، فلنبدأ محاضرة اليوم. لم يتبقَّ لدينا إلا محاضرتان، هذه ومحاضرة الاثنين القادم. أدركت أنه لم تعد هناك مهام ينبغي تسليمها في هذا المقرر. لا اختبارات قصيرة أخرى بعد الليلة، ولا مجموعات مسائل أخرى. لذا أقدّر فعلًا حضوركم هذه المحاضرات. المقصود أن تكون أكثر متعة قليلًا. لا حاجة إلى تدوين الملاحظات. استرخوا نوعًا ما واستمتعوا بالمحتوى. سنتحدث اليوم عن مكتبة (Library) في Python يمكن أن تساعدكم على الرسم البياني (Plotting).
والسبب الذي يجعلنا نتحدث عن مكتبة الرسم هذه، بدل شيء آخر ربما يتعلق أكثر بتعلّم الآلة (Machine Learning) أو شيء آخر من هذا القبيل، هو أنكم في وقت أو آخر، إذا قررتم أخذ أي مقرر آخر يبني نوعًا ما على هذا المقرر التمهيدي، فستريدون على الأرجح إنشاء بعض الرسوم البيانية أو تصوير شيء بصريًّا. وحتى إذا شاركتم في UROP، فستضطرون على الأرجح إلى تصوير نوع ما من البيانات. وهذه خطوة تالية جميلة جدًّا، أن نريكم كيف تستخدمون مكتبة موجودة بالفعل. فقد بذل أحدهم العمل بالفعل لإنشاء هذه المكتبة التي تستطيع رسم الأشياء لنا. فلنحاول استخدامها فحسب.
ملاحظة المترجم: UROP هو برنامج فرص البحث لطلاب المرحلة الجامعية (Undergraduate Research Opportunities Program) في MIT.
وهذه طريقة جميلة جدًّا جدًّا لنختتم المقرر، بأن نريكم مكتبة التصوير البصري (Visualization) هذه. إذًا سنـ — المكتبة التي سنفعل — سنستخدمها تسمى matplotlib. وهي أبسط مكتبة للرسم والتصوير البصري يمكن أن تكون لدينا. والطريقة التي نجلبها بها إلى كودنا، كما فعلنا في المحاضرات القليلة الماضية، هي عبارة الاستيراد (Import Statement) هذه.
والملف الفعلي الذي يأتي إلى — الذي سنجلبه إلى برنامجنا يسمى matplotlib.pyplot. هذه عبارة طويلة نوعًا ما. وفي كثير من الأحيان عندما نريد استخدام هذه — أو عندما نريد استخدام هذه المكتبة، ستضطرون في الأساس إلى قول matplotlib.pyplot نقطة، ثم اسم الدالة من ذلك الملف. وهذا كثير من الكتابة وكثير من الطباعة. لذا عندما نجلبها إلى — عندما نجلب هذه المكتبة إلى ملفنا نحن، يمكننا فعلًا إعادة تسميتها.
إذًا as plt تخبر Python أنني أود الآن الإشارة إلى هذا الملف وهذه المكتبة بالاسم plt. فإذا أردنا في أي وقت استدعاء دوال، أو ربما كائنات وأشياء كهذه من هذا الملف، فسنفعل ذلك باستخدام plt نقطة، ثم اسم أيّ شيء نريد استخدامه. إنها فقط طريقة ألطف كثيرًا للحصول على محتويات الملف، بدل كتابة matplotlib.pyplot نقطة، ثم شيء آخر دائمًا. نعم، سؤال.
أحد الحضور (AUDIENCE):
هل plt اسم متغير؟
آنا بيل (ANA BELL):
يمكنك التفكير فيه على أنه اسم متغير. يمكن أن يكون أيّ شيء تريده. يمكنك استيراد matplotlib.pyplot as ana. وبعد ذلك يمكنك قول ana نقطة، اسم العملية، أو plot، أو أيّ شيء كان. فهو مجرد أيّ اسم تريد إعطاءه لها.
حسنًا، توجد مكتبات تصوير بصري أخرى. كثير منها — أو كلها — يبني على هذه. فهذه أبسط مكتبة يمكنك الحصول عليها. والمكتبات الأخرى الموجودة تبني عليها، بأن تفعل بعض الأشياء وراء الكواليس لتجعل حياتكم أسهل ربما، أو لتنفيذ بعض التصويرات الرائعة جدًّا، أو ربما أشياء يمكنك فيها تمرير الفأرة فوق إحداثية، وأشياء من هذا القبيل.
ملاحظة المترجم: القول إن جميع مكتبات التصوير البصري تبني على
matplotlibمنقول كما ورد؛ ليس وصفًا عامًّا دقيقًا لكل المكتبات الموجودة.
صفحة المصدر 2
لكننا لا نحتاج إلى فعل أيّ من ذلك الآن. من الجيد فقط إلقاء نظرة على مكتبة التصوير البصري الأساسية جدًّا هذه. خلال المحاضرة، سننظر قليلًا إلى بعض الكود. سنشغّله على Python — من ملف Python فقط. ثم سنتحدث عنه على الشرائح. عندما نرسم الأشياء، نحتاج إلى إخبار Python بمجموعة قيم x ومجموعة قيم y. وهذا شائع جدًّا. إذا استخدمتم Matlab، فستعرفون أن تلك هي تقريبًا الطريقة التي يجري بها ذلك — والأمر نفسه في Python.
عندما ننشئ الإحداثيات (Coordinates) التي نود رسمها في مستوى ثنائي الأبعاد (2D Plane)، فإننا في الأساس ننشئ قائمتين فقط، حيث يكون لدينا، فهرسًا بفهرس، قائمة تحتوي على كل القيم التي نريدها لإحداثي x، وقائمة — وفي قائمة منفصلة كل القيم التي نودها لإحداثي y. إذًا، عند الفهرس 0 في كل من هاتين القائمتين، تنشئون في الأساس قيم x عند الفهرس 0، وقيم y عند الفهرس 0، لتصبح الإحداثية — نقطة إحداثية واحدة.
إذًا، من أبسط الأشياء التي نستطيع فعلها أن ننشئ قائمة جميلة من القيم تكون قيم x لدينا. سيكون محور x لدينا في الأساس الأعداد من 0 حتى 29. ثم هنا في الأسفل يمكننا إنشاء أربع قوائم مختلفة تحتوي على أربع مجموعات مختلفة من إحداثيات قيم y. عندما نرسم، سنرسم قائمة قيم x هذه مقابل كل هذه النقاط الخطية (Linear)، وقائمة قيم x هذه مقابل كل هذه النقاط التربيعية (Quadratic)، وقائمة قيم x هذه مقابل النقاط التكعيبية (Cubic)، وهكذا.
والطريقة التي ننشئ بها هذه القوائم هي صياغة Python مألوفة إلى حد كبير. تمرّ n لدينا من 0 إلى 29. ثم نلحق بنهاية كل واحدة من هذه القوائم، linear وquadratic وcubic وexponential، دالة ما لتلك n. ستكون قائمة linear فيها كل القيم نفسها مرة أخرى فقط. فنحن نرسم (0, 0)، و(1, 1)، و(2, 2)، وهكذا. وفي قائمة quadratic سنرسم (0, 0)، و(1, 1)، و(2, 4)، و(3, 9)، وهكذا. والأمر نفسه مع التكعيبية، ثم هذه الأسّية (Exponential).
اخترت اعتباطًا فقط 1.5**n لأنها بدت جميلة نوعًا ما في الرسم، لكن يمكنكم تصور عدد مختلف للدالة الأسّية هناك. والطريقة التي نرسم بها بعض القيم هي، كما قد تتوقعون، أمر plot. إذًا plt هو الاسم الذي قررنا استيراد تلك المكتبة به، الاسم الذي أعطيناه لها؛ ونقطة plot تخبر Python بأننا نود رسم قائمة ما من قيم x وy.
إذًا، معاملات (Parameters) أمر plot ستكون تسلسلين (Sequences) من القيم. يمكن أن يكونا قائمتين عادةً، لكن يمكن أيضًا أن يكونا صفّين (Tuples). وقد يكونان أيضًا المفاتيح التي تحصلون عليها من قاموس (Dictionary)، وهي أيضًا قابلة للتكرار (Iterable)، وأشياء كهذه. فعلينا تمرير قائمة من أشياء عددية. سيكون هذا عادةً ما يوجد على محور x لديكم. والمعامل الثاني سيكون دالة تلك القيم على محور x.
يجب أن تكون القائمتان بالطول نفسه، طبعًا، كي تعرف Python أيّ الإحداثيات نرسم. وإذا لم تكونا بالطول نفسه عن طريق الخطأ، فستصدر Python خطأ، وعندئذ لا — لن ترسم شيئًا فحسب. عندما نشغّل الكود، سترسم Python القيم عمومًا إما في نافذة جديدة، أو مباشرة ضمن وحدة التحكم (Console)، أي هنا. هنا في الداخل تمامًا، يمكنها وضع الرسم مباشرة ضمن مجموعة من الأشياء التي قد تطبعونها.
للتبديل بين هذين، من باب الفضول فقط، تذهبون إلى Tools، ثم Preferences، ثم iPython console، ثم Graphics. وهنا يمكنكم اختيار إما automatic، الذي سينشئ لنا نافذة جديدة تفاعلية، يمكنك فيها التكبير والتصغير، وأشياء كهذه، أو inline، الذي سيضع الرسم الذي تخبرون Python بتوليده مباشرة هنا في وحدة التحكم. وأنا أفضّل النافذة الجديدة لأن التفاعل معها أسهل لي. فسنفعل ذلك.
صفحة المصدر 3
فلنشغّل فعلًا أحد أوامر الرسم. إذًا plt.plot، نرسم هنا محور x على أنه الأعداد من 0 حتى 29 فقط. ومحور y سيكون أيضًا القيم من 0 حتى 29 فقط. لقد صنعنا رسمًا خطيًّا صغيرًا جميلًا. وتلاحظون أنه فتح لي نافذة صغيرة هنا في الأسفل. وهذا هو الرسم الذي ولّده. رائع، لا مفاجأة. هذا بالضبط ما توقعناه منه.
حسنًا، ماذا نلاحظ عن ذلك الرسم؟ نلاحظ كيف احتوت Python الخط داخل هذا الإطار على نحو جميل. أضافت قليلًا من المساحة إلى يسار خطي ويمينه، وأسفل خطي وأعلاه، كي يناسب الإطار بشكل جميل. لم تُصغّر العرض إلى مجال قياسي ما من 0 إلى 100. بل قرّبت العرض إلى هذا المجال من 0 إلى نحو 30، ومن 0 إلى نحو 30 على محور y. فمن الجميل جدًّا جدًّا أنها فعلت كل ذلك لنا.
ترتيب النقاط في القائمة مهم. ستلاحظون إذًا أن من الأشياء الأخرى في هذا الرسم هنا أننا أعطيناها نقاطًا فعلية ينبغي أن ترسمها. لكن أمر plot لا يرسم النقاط افتراضيًّا. بدلًا من ذلك، يصل كل النقاط بخط فقط. فهو يصل نقاط الفهارس المتتالية بخط. إذًا وصل (0, 0)، و(1, 1)، و(2, 2)، وهكذا.
لذلك فإن ترتيب النقاط مهم فعلًا. إذا كانت لدينا دالة، مثلًا، في هذه الحالة، n وn² — حيث n من 0 حتى 29، وn² هي 0، 1، 2، 4 — 0، 1، 4، 9، وهكذا — لكنها في — لكنها ليست بالترتيب، فستأخذ Python فقط أزواجًا متتالية من تلك القوائم وتصلها بخط. هذا مثال إذًا. لدي قائمة قيم x وهي testSamples هذه. إنها كل الأعداد من 0 حتى 29، لكنها ليست بالترتيب.
وقيم الاختبار المرتبطة بكل واحدة منها، مجددًا، صحيحة. هذا مربع 0. و25 هذه مربع 5. و9 هذه مربع 3. لكنها ليست بالترتيب. فإذا شغّلنا ذلك بأمر plot فقط، فسنحصل على رسم مشوّه. لا يبدو جميلًا جدًّا، ونعرف بالفعل ما المشكلة، أليس كذلك؟ وصلت Python فقط (0, 0)، و(5, 25)، ثم (3, 9) بخط — ليس جميلًا جدًّا حقًّا.
بدلًا من ذلك، ما نود فعله هو إخبار Python برسم النقاط فحسب. فلا أهتم بوصلها بخط. في هذه الحالة سأخبر Python، بدل رسمها فقط، أن تنشئ لي مخططًا مبعثرًا (Scatter Plot). إذًا plt.scatter، مع قائمة قيم x وy نفسها، ستنشئ لي هذا الرسم الجميل، حيث ترسم الإحداثيات. لا يهم أنها ليست بالترتيب، لأنها تُرسم فقط دون أيّ شيء يصلها. وهذا جميل جدًّا.
هذا إذًا هو المثال الذي نفّذناه هنا للتو. وهذا تنفيذنا لمخطط مبعثر يعطينا هذا الرسم الجميل. حسنًا، من الأشياء الأخرى التي قد ترغبون في فعلها رسم مجموعة كبيرة من الخطوط في نافذة واحدة. لفعل ذلك، كل ما عليكم إخبار Python به هو تسلسل كل الأوامر، كل أوامر الرسم أو كل ما تودون رسمه على شكل (Figure) واحد. إذًا، دون أن تخبروا Python بأنكم تودون إنشاء شكل جديد، كلما رأت أمر plot، ستستمر فقط في إضافة أيّ نقاط تُولَّد، أو أيّ خطوط تُولَّد، إلى الشكل الحالي المفتوح.
لدينا الآن شيء واحد فقط مفتوح. فستستمر فقط في إضافة الأشياء إلى شكلنا. هنا لدي أربعة أوامر رسم متتالية. لم أخبر Python صراحةً في أيّ وقت بأن تنشئ لي شكلًا جديدًا. لذا ستضيف هذه الخطوط الأربعة كلها إلى الرسم نفسه. فهي لا تنشئ شكلًا جديدًا فحسب. تستمر فقط في إضافة الأشياء إلى رسمي. يمكنكم إذًا تصور أنني إذا أضفت مخططًا مبعثرًا أيضًا، فسيضيف فقط النقاط المنفردة إلى هذا الرسم.
صفحة المصدر 4
حسنًا، مجددًا، ماذا نلاحظ؟ حسنًا، وضعت Python كل شيء في إطار جميل لي، لتضمن أن كل خط لدي يناسب هذا الرسم. فمحور x لدي يقع بأريحية بين 0 و30. ومحور y لدي يقع أيضًا بأريحية بين 0 و120,000. إذًا، يوجد قليل من الفراغ عند أعلى ذلك الخط الأسّي. لكن هذا يقودنا، إذا عُرض علينا هذا الرسم، إلى ألا نعرف نوعًا ما، على نحو مضلل، ما يحدث مع هذه الخطوط السفلية هنا.
هذا إذًا هو الخطي لدينا، الخط الأزرق. والبرتقالي هو التربيعي. لسنا متأكدين تمامًا مما يحدث هناك في الأسفل، لأن المقاييس (Scales) هي فقط — مقياس y عالٍ أكثر من اللازم. في هذه الحالة المحددة، سيكون الأفضل تصوير البيانات في نوافذ مختلفة منفصلة.
إذًا، بدل رسم كل شيء في نافذة واحدة، سنخبر Python بأن تنشئ نافذة جديدة وترسم بعض الأشياء فيها. والطريقة التي نخبر بها Python بأن تنشئ لنا نافذة جديدة هي أمر plt.figure. بمجرد أن ترى Python plt.figure، ستنشئ نافذة جديدة وتجلبها إلى المقدمة. وأي أوامر رسم لاحقة ستُضاف إلى هذا الشكل الجديد.
يوجد معامل يمكن أن يأخذه هذا الشكل. وهو اسم الشكل. تعرفون عندما تكون لديكم نافذة، في أعلاها اسم البرنامج أو أيّ شيء يجري تشغيله؟ حسنًا، السلسلة النصية (String) التي تضعونها هناك ستكون الاسم الذي تعطونه لذلك الشكل. إذا لم يكن لدى Python شكل بذلك الاسم بالفعل، فإنها تنشئ شكلًا جديدًا وتجلبه إلى المقدمة. لكن إذا كان هناك شكل بذلك الاسم بالفعل، وصادف أن استدعيت plt.figure بالاسم نفسه، فسيعيد فقط جلب تلك النافذة إلى المقدمة مرة أخرى لإضافة مزيد من الأشياء إليها.
سننظر إذًا إلى هذا المثال هنا. لدينا مجموعة كاملة من الأشياء تُرسم. في سطري الكود الأولين هنا، لدينا أولًا شكل جديد يُنشأ. وسميناه expo. ثم أمر plot هذا هنا، الذي يأتي مباشرة بعد figure، سيضيف الدالة الأسّية التي أنشأناها إلى ذلك الشكل expo. ثم لدينا plt.figure بعدها مباشرة. فستجلب Python هذا الشكل الجديد إلى المقدمة.
وأمر plot الذي يأتي بعدها مباشرة سيضيف الخطي، هذا الخط، إلى هذا الشكل الجديد — الذي سميناه lin. بضع مرات أخرى سننشئ ونفعل الشيء نفسه لإنشاء quad وcube هذين. وسيُضاف إلى كل واحد منهما خط واحد. ثم هنا في الأسفل سنقول: حسنًا، دعوني أعد إلى ذلك الشكل الأسّي وأضف إليه منحنى أسّيًّا مختلفًا آخر. سننشئ المنحنى الأسّي إذًا، هذه المرة 1.6**i بدل 1.5**i.
ثم سنخبر Python بأن تجلب الشكل الذي اسمه expo إلى المقدمة مجددًا. فتذكروا أننا أنشأناه هنا في الأعلى. لذلك لا تنشئ شكلًا جديدًا. ستجلب ذلك الشكل مجددًا فحسب. ويوجد فيه بالفعل منحنى. ثم سنخبر Python بأن ترسم فيه منحنى ثانيًا. فلنرَ ذلك. هذا كل هذا الكود هنا. شغّله.
حسنًا، لم يُنشأ شكل واحد فقط — نافذة واحدة فقط — بل أربعة. هذا هو cube لدي. ويمكنكم أن تروا هنا في الأعلى، يصعب رؤيته قليلًا، لكن هذا هو الاسم الذي أعطيناه له. وهذا quad لدي. وهذا lin لدي، الخطي. وهذا الأسّي لدي. نرى إذًا أن الشكل الأسّي يحتوي على خطين، لأننا أضفنا واحدًا في البداية تمامًا، ثم أعدناه للمعالجة لإضافة خط آخر إليه.
صفحة المصدر 5
إذًا مجددًا، هذه الرسوم موجودة فعلًا على الشريحة. هذا هو التربيعي. وهذا التكعيبي. وهذا الخطي. وهذا الأسّي. الخط الأزرق كان منحنى البداية لدينا، 1.5**x. والبرتقالي هو 1.6**x. فأُضيف كلاهما إلى الرسم نفسه. جميل. إذًا، مجددًا، شيء ينبغي ملاحظته: سترون كيف وضعت Python خطوطنا داخل إطار جميل، بحيث استطاعت احتواء كل ما تحتاج إلى رسمه داخل هذا الرسم.
ما سنفعله بعد ذلك — أعرف أن ذلك كان مملًّا قليلًا. ما سنفعله بعد ذلك هو أن نبدأ النظر إلى بعض الأمثلة الحقيقية، بعض البيانات من الواقع. سنبدأ أولًا ببعض البيانات الواقعية المصغّرة للتجريب. ثم سنبدأ قريبًا التعامل مع مجموعات بيانات فعلية سنقرؤها، وسنرسمها، وسنفحصها، ونحاول الإجابة عن بعض الأسئلة عنها، وأشياء كهذه. أولًا، فلننظر إلى هذا المثال من الحياة الواقعية، حيث لدينا الأشهر ودرجات الحرارة لكل شهر منها.
لاحظوا أن الأشهر هنا هي فعلًا القيمة التي يعيدها range هذا، وهي مثل كائن قابل للتكرار، مثل صفّ. لذا فهي ما زالت تسلسلًا من القيم. ليست قائمة، لكن تمريرها وسيطًا (Argument) إلى plot مناسب تمامًا. وtemps، طبعًا، ستكون درجات الحرارة المقابلة لكل واحد من تلك الأشهر على هيئة قائمة.
سيبدو الرسم على نحو كهذا إذا شغّلنا ذلك الكود فعلًا. ماذا نلاحظ؟ حسنًا، كما من قبل، وضعت matplotlib بياناتنا داخل إطار جميل. فيه قليل من المساحة يسارًا ويمينًا، وأعلى وأسفل. واختارت تلقائيًّا المقاييس: إلى أيّ حد تنخفض، وإلى أيّ حد ترتفع، وعلامات التدريج (Tick Marks) لهذا.
لكن لنفترض أنك مشرف طالب، وجاءك برسم يبدو هكذا. هل ستستطيع معرفة أيّ شيء عن هذا الرسم من دون معرفة الكود الذي ولّده بالضبط؟ ليس حقًّا، أليس كذلك؟ يبدو مجرد حدبة. قد يكون أيّ نوع من البيانات. لذا ما نود فعله قبل أن نقوم فعلًا بـ — في تطبيقاتكم وأشياء كهذه مستقبلًا، قبل عرض رسم على شخص آخر، ستريدون إضافة عنوان، وستريدون تسمية محاوركم.
إذًا، ما نريد فعله، إضافةً إلى رسم البيانات فعلًا، هو إخبار Python بأن تضيف لنا عنوانًا وتسميات لمحاورنا. نفعل ذلك باستخدام أسطر الكود الثلاثة هذه هنا. وبما أننا لم نخبر Python بأن تنشئ شكلًا جديدًا أو أيّ شيء كهذا، فإن أيّ أوامر ننفّذها تتعلق بالرسم ستُضاف إلى هذا الشكل فقط. هنا لدي Python تضيف هذا العنوان، وهاتين التسميتين إلى محورنا — إلى محورينا.
هنا لدي هذا وهذا الرسم. أشغّله، وها نحن ذا، لدينا شيء يبدو أجمل كثيرًا. يمكننا الآن تسليم هذا الرسم إلى شخص ما، وسيعرف ما موضوعه. هذا جيد، لكن بما أنها درجات حرارة، فما أود فعله حقًّا هو أن أقول: حسنًا، درجة الحرارة — أدنى درجة حرارة لدي ينبغي حقًّا أن تبدأ عند محور y هنا، هذا التقاطع مع محور y. وأعلى درجة حرارة لدي، لا أريد حقًّا تلك المساحة الإضافية، لأن هذه آخر قيمة درجة حرارة لدي. فلنجعل الإطار ينتهي هناك فحسب.
يمكننا إجراء ذلك التغيير الصغير بتعيين حدود (Limits) على محور x لدينا. هنا سأحدّ محور x ليبدأ من 1 وينتهي عند 12. وإذا فعلت ذلك، مجددًا، فهذا مجرد أمر صغير نضعه، استمرارًا لبقية الأوامر. ويُطبّق على هذا الرسم. وبمجرد أن أفعل ذلك، تنشئ Python لي الآن الرسم نفسه، إلا أن محور x يبدأ عند 1 وينتهي عند 12، داخل إطار جميل هنا، فلا مزيد من المساحة الإضافية.
ما زالت هناك تحسينات يمكن إجراؤها على هذا الرسم، فالأشهر هنا تقفز. قررت Python أن الأفضل إظهار 2، 4، 6، 8، 10، 12 فقط بوصفها تدريجات على محور x. لكنني أقرر أنني أود — بما أنني أعرض كل أشهر السنة ودرجات حرارتها، فأنا أود حقًّا أن تكون هناك علامات تدريج لكل شهر على حدة. مجددًا، سيفعل ذلك لنا أمر صغير.
صفحة المصدر 6
إذًا plt.xticks يأخذ صفًّا بكل الأماكن التي تودون إنشاء علامة من تلك العلامات الصغيرة عندها. فإذا فعلنا ذلك — مجددًا، أمر صغير آخر بالتتابع هنا — إذا فعلنا ذلك، تملأ Python الآن التدريجات في كل موضع طلبنا منها ملأه. أصبح يبدو أفضل كثيرًا بالفعل.
ما الذي ما زال غير صحيح تمامًا — أعدكم، سيكون هذا آخر تحسين نجريه. أجد شخصيًّا صعوبة في ربط الأعداد بالأشهر. ما زلت أعدّ على أصابعي. لذا سيكون من الجميل القول: حسنًا، بدل الأعداد على محور x لدي، أود وجود الأسماء الفعلية لأشهري، Jan وFeb وMar، وهكذا. لفعل ذلك، سنجري تغييرًا صغيرًا واحدًا على أمر xticks هنا.
سنعطيه معاملًا ثانيًا. الأول، طبعًا، هو ما كان لدينا من قبل، وهو يقول: هذه كل علامات التدريج التي أود وجودها. والمعامل الثاني هو التسميات لكل واحدة من تلك العلامات. ستُطابق إذًا واحدة بواحدة. أي 1 سيُطابق Jan، و2 سيُطابق فبراير، وهكذا. فبدل استخدام القيم العددية، ستنشئ Python لنا القيم النصية التي طلبت منها إنشاءها.
ها هو. التشغيل ينشئ لي هذا الرسم الجميل جدًّا. سأكون سعيدة باستقبال هذا بوصفِي مشرفة، مقارنة بأول رسم تمامًا كان لدينا. حسنًا، هل هناك أسئلة حتى الآن؟ هل يبدو أننا بخير؟ حسنًا. الشيء الآخر الذي يمكنكم فعله هو إضافة خطوط شبكة (Grid Lines) إذا أردتم. إذًا plt.grid يبدّل خطوط الشبكة بين التشغيل والإيقاف. إذا كانت هناك خطوط شبكة بالفعل، فسيوقفها عندما يرى ذلك الأمر. وإذا لم تكن هناك خطوط شبكة، فسيشغّلها.
يمكن إذًا أن تكون لديكم مجموعة من أوامر plt.grid تستمر في تشغيل الأشياء وإيقافها، وهكذا. حسنًا، هذا كان رسمنا لقيم درجات حرارة مدينة واحدة خلال سنة، لنقل متوسطًا. لنفترض أننا أردنا رسم مدينتين مختلفتين. الكود الذي يفعل ذلك كما يلي.
مجددًا، لدينا الأشهر في هذا النطاق، من 1 حتى 12، شاملًا. لدي هنا قائمة بكل درجات حرارة بوسطن، أرسمها، وقائمة بكل درجات حرارة فينيكس هنا، وأرسمها. وطبعًا سأضيف بعض التسميات إلى رسمي. هكذا إذًا. وإذا شغّلت ذلك، نحصل على شيء يبدو هكذا.
طبعًا، يمكنني إزالة تلك المساحات الصغيرة يسارًا ويمينًا، لكن لا بأس الآن. ما الذي ينقص هذا الرسم؟ لنفترض أنكم لم تروا الكود، وأُعطيتم هذا الرسم فقط.
أحد الحضور (AUDIENCE):
لن تعرفي أيّ مدينة هي أيّ خط.
آنا بيل (ANA BELL):
نعم، بالضبط. لا أعرف ماذا — نعم، هذه درجات حرارة مختلفة من العنوان والتسميات، لكنكم لا تعرفون أيّ مدينة هي أيّ خط، بالضبط. لذا ما نود فعله هو إخبار Python بكيفية تسمية هذين الخطين. لفعل ذلك، لا نحتاج إلا إلى معامل إضافي هنا في أمر plot. فعندما تخبرونها أيّ البيانات ترسم، يمكنكم أيضًا إخبارها بالتسمية التي ينبغي أن تحصل عليها تلك البيانات.
هنا لدي تسمية Boston للأول، وتسمية Phoenix للثاني. ثم تخبرون Python بأن تضيف مفتاحًا للرسم (Legend) إلى رسمكم. المعامل هنا هو موقع المفتاح. وbest تعني فقط أن على Python أن تقرر أين تضع المفتاح، أعلى اليسار، أعلى اليمين، الوسط، أينما كان، بحيث لا يتداخل كثيرًا مع البيانات. أو يمكنكم إخبارها أين تضع ذلك المفتاح فحسب. يمكنكم إذًا إجبار المفتاح على موضع محدد.
هنا لدي بيانات سُمّيت بالفعل. ثم نضيف المفتاح. والآن يمكنكم رؤية أنها، في هذه الحالة المحددة، قررت وضعه أعلى اليمين. لكن مجددًا، يمكنكم إجباره على الانتقال إلى مكان آخر. بدا منتصف الأسفل اختيارًا جيدًا أيضًا. حسنًا، جميل جدًّا. لدينا الآن Python — لقد أنشأت تلقائيًّا محوري x وy لنا، كما أخبرناها أن تفعل، لكن الألوان التي اختارتها كانت عشوائية.
ملاحظة المترجم: الوصف «عشوائية» منقول من الكلام؛ تستخدم
matplotlibافتراضيًّا دورة ألوان محددة، لا اختيارًا عشوائيًّا بالمعنى البرمجي.
صفحة المصدر 7
يمكننا الآن تحديد مجموعة من التفاصيل المختلفة للرسم. سنفعل ذلك بعد قليل لنبين لكم فقط أنه يمكنكم فعله. سنختار ألوانًا مختلفة وأنماطًا مختلفة لرسومنا. وسنختار سماكات مختلفة لخطوطنا. ثم ربما يمكننا — وسنضيف أيضًا بعض العلامات (Markers)، أي حيث توجد بالضبط — كل نقطة بيانات لدينا، سنضع علامة عليها. ثم سأريكم كيف يمكنكم إنشاء رسوم فرعية (Subplots). فبدل إنشاء نوافذ جديدة، يمكنكم فعلًا امتلاك نافذة واحدة تحتوي على رسوم فرعية صغيرة مختلفة.
حسنًا، أول شيء سنراه هو كيفية تخصيص البيانات لتكون بنمط خط معين ولون معين. توجد صياغة مختصرة (Shorthand Notation) لفعل ذلك. بدل تمرير اسم المعامل فعلًا في أمر plot، يمكننا استخدام صياغة مختصرة. ربما لاحظتم بالفعل هذا الجزء الإضافي الصغير هنا. فكلما استخدمتموه أكثر، اعتدتم عليه أكثر.
لكن هذا يخبر Python في الأساس أنني أود أن يكون هذا الرسم، هذا الخط المقابل لهذه البيانات، أزرق، وهذا ما يرمز إليه b، وأن يكون خطًّا متصلًا (Solid Line). هذا ما تعنيه تلك الشرطة الصغيرة. أما خط فينيكس، فربما خمنتم أنه يخبر Python بأنني أود أن يكون أحمر، r للأحمر، وأن يكون خطًّا متقطعًا (Dashed Line). ثم الأخير، سأضيف درجة حرارة أخرى هنا، بيانات درجة حرارة مينيابوليس — أود أن يكون أخضر وخطًّا بشرطة–نقطة–شرطة (Dash-Dot).
يمكننا تشغيل ذلك. ويبدو على نحو كهذا. حسنًا، لدي خطي الأزرق المتصل لبوسطن، وخطي المتقطع لفينيكس، وخطي بشرطة–نقطة–شرطة لمينيابوليس. جميل جدًّا.
الآن، بدل استخدام تلك الصياغة المختصرة، حيث لدينا هذا المعامل الواحد الذي يعرف نوعًا ما، بطريقة سحرية، اللون والنمط لمجرد تمريره، يمكننا فعلًا إخبار Python بقيم المعاملات المقابلة للون؛ فهنا لدي color='b' للأزرق، ثم المقابلة لنمط الخط. هنا linestyle= — ثم تمررون صراحةً نمط الخط الذي تودونه.
قد يكون هذا أوضح وفق ما تعلمناه. لكن Python تتيح لكم خيار فعل كل ذلك في واحد. فإذا فعلنا — إذا شغّلناه بهذه المعاملات المحددة الصريحة، فسنحصل على الرسم نفسه تمامًا كما من قبل — لا مفاجأة.
توجد إذًا خيارات كثيرة يمكن أن تكون لدينا هنا. هذه كل خيارات أنماط الخطوط. فيمكنكم أيضًا إضافة خط منقّط (Dotted Line)، لم أعرضه. وهذه كل خيارات الألوان، إضافة إلى كثير غيرها. يمكنكم أيضًا تمرير قيم RGB، أو ربما القيم الست عشرية (Hex Values)، إذا أردتم درجة محددة جدًّا من الأرجواني أو شيء ما. ثم يمكننا أيضًا إضافة علامات إلى خطوطنا. لم نرَ هذا بعد، لكن فلنفعله تاليًا.
لنقل إنني أود أن تظهر نقاط البيانات الفعلية التي رسمتها في خطوطي. الآن، تُوصل الخطوط فقط — أو تُوصل نقاط البيانات فقط بخطوطنا، المتقطعة أو المنقّطة أو أيًّا كان ما اخترناه، لكن العلامة — نقاط البيانات نفسها لا تظهر بعلامات. مجددًا، بالصياغة المختصرة يمكننا إخبار Python: مهلًا، فلنضف هذه العلامات.
هنا أخبر Python بأن تضع نقطة فقط لهذا الخط الأزرق المتصل. وهنا أخبر Python بأن تضع نقطة أكبر لهذا الخط الأحمر المتقطع. وهنا أخبرها بأن تضع نجمة للخط الأخضر بشرطة–نقطة–شرطة–نقطة. حسنًا، هذا هنا في الأسفل. شغّله.
صفحة المصدر 8
والآن نرى علامات صغيرة جميلة لكل واحدة من نقاط بياناتنا. يمكننا أيضًا استخدام مثلثات، ويمكننا استخدام مربعات. توجد خيارات أخرى كثيرة للعلامات، وكلها موجودة في توثيق matplotlib. هذا إذًا ما حصلنا عليه، ممتاز. آخر شيء يمكننا فعله هو إضافة سماكة إلى خطوطنا. ففي كثير من الأحيان، من الجيد أولًا تمييز الخطوط باستخدام الشرطات أو النقاط وأشياء كهذه، لكن أيضًا باستخدام السماكة.
هنا يُمرَّر معامل آخر، linewidth، سماكة الخط. سيكون هذا خطًّا رفيعًا، وهذا ربما خطًّا أسمك، وهذا سيكون سميكًا على نحو غير معقول. فلنرَ بالضبط كيف سيبدو ذلك. سيبدو غريبًا جدًّا. كما قلت، خط سميك على نحو غير معقول. لكنه موجود. ثم يمكنكم رؤية أن مفتاح الرسم نفسه تكيف أيضًا مع ما اخترتموه لأنماط خطوطكم.
نعم، هذا بالضبط ما قلته. جميل. آخر شيء أريد الحديث عنه هو الرسوم الفرعية. الآن، الأشياء الوحيدة التي تعلمناها نوعًا ما عن الرسم هي أنكم إما ترسمون كل خط لديكم على شكل واحد، أو تنشئون شكلًا جديدًا، فيصبح نافذة جديدة ينبغي أن تنتقلوا بينها من أجل أيّ شيء تودون رسمه.
في كثير من الأحيان، ما يكون جميلًا حقًّا هو إنشاء شكل واحد فقط. فلا يكون لديكم إلا شيء واحد يظهر، نافذة واحدة مثلًا. وداخل تلك النافذة ذات اسم ما هنا — وداخل تلك النافذة يمكنكم إنشاء مجموعة من الرسوم الفرعية المختلفة. هنا أنشأت ستة رسوم فرعية مختلفة. يمكننا إخبار Python بذلك. ونفعل ذلك باستخدام أمر subplot.
في هذه الحالة المحددة، أخبرت Python بأن تنشئ لي رسمًا فرعيًّا بصفّين، وهذا ما يقوله المعامل الأول، وعمود واحد. وهذا ما يقوله المعامل الثاني. هذه إذًا نافذة واحدة فيها موضعان. المعامل الثالث يخبر Python أيّ واحد من هذين الموضعين تفتح لإضافة خطوط أو بيانات إليه. أي 1 يعني أن هذا هو الذي تفتحونه، و2 يعني أن هذا هو الثاني الذي تفتحونه.
يمكنكم أن تروا هنا أن أمر subplot في الأعلى تمامًا يخبر Python بأن تفتح هذا للتعديل، في الأساس. سنضيف إليه درجة حرارة بوسطن. فهذه كل أوامر الرسم وكل التسميات وكل ما بعدها ينتمي إلى هذا الرسم الفرعي العلوي هنا. ثم أمر subplot هنا في الأسفل يخبر Python أنني، على هذا الشكل ذي الصفّين والعمود الواحد، أود الآن فتح الموضع رقم 2 للتعديل. ثم كل ما لدي بعد ذلك يُضاف إلى الرسم الفرعي عند هذا الموضع.
سيبدو ذلك كما يلي. لدي إذًا — هذه نافذة واحدة فقط تُنشأ. ويمكنكم رؤية أن العلوي فيه درجة حرارة بوسطن، والسفلي فيه درجة حرارة فينيكس.
للوهلة الأولى، هل يبدو هذا صحيحًا من حيث درجات الحرارة، إذا نظرتم إلى الصور نفسها فقط؟ لا أعرف عنكم، لكن للوهلة الأولى ظننت أن درجات حرارة بوسطن وفينيكس متساوية، لأنني لم أنظر عن قرب إلى محوري y. بدا نوعًا ما: مهلًا، كلاهما يبلغ الحد الأدنى بالطريقة نفسها، وكلاهما يبلغ الحد الأعلى بالطريقة نفسها. لذلك بدوا لي متشابهين جدًّا. لكن إذا فحصنا محوري y عن قرب أكثر، نرى أن درجة حرارة بوسطن تبدأ عند 30 وتصل إلى 70. أما فينيكس فتبدأ عند، ما هذا، 50 وتصل إلى 90.
إذًا، إذا كنا نعرض رسومًا في شكل واحد، فسيكون من الجميل حقًّا ضمان أن المحورين محدودان بطريقة متشابهة، خاصة إذا كنا نرسم بيانات متشابهة، درجات الحرارة في هذه الحالة المحددة. لذا ما نود فعله أيضًا في كودنا هو تعيين حدود لمحاورنا، ولمحور y فقط لأن محور x هو نفسه. يمكنني هنا تحديد محور y من 0 إلى 100، وهي مجموعة معقولة من درجات الحرارة بالفهرنهايت.
صفحة المصدر 9
إذا ثبّتُّ حدود درجات الحرارة هذه من 0 إلى 100، ثم رسمت الآن، فسأحصل على شيء يبدو هكذا. والآن للوهلة الأولى يبدو هذا أكثر منطقية لي كثيرًا. لدي — تبدو درجات حرارة فينيكس خلال هذه السنة، في المتوسط، أعلى من درجات حرارة بوسطن.
يمكننا الآن رسم عدة — يمكننا إنشاء عدة رسوم فرعية. في المثال السابق هنا، كان لدي اثنان فقط، علوي وسفلي. لكن يمكنني إنشاء أيّ عدد أريده من الرسوم الفرعية داخل نافذتي. عندما أنشئها وأخبر Python بعدد الصفوف والأعمدة لدي — في هذا المثال المحدد الذي رسمته هنا للتو، لدي ثلاثة صفوف وعمودان. فالمعامل الثالث الذي أمرره سيخبر Python في الأساس أيّ واحد من هذه الرسوم الفرعية تفتحه للمعالجة.
سيكون هذا الأول، وهذا الثاني — نوعًا ما بالطريقة التي نقرأ بها — الثالث، والرابع، والخامس، والسادس. إذًا، ذلك المعامل الثالث لأوامر subplot هذه سيكون إما 1 أو 2 أو 3 أو 4 أو 5 أو 6، ليخبر Python إلى أيّ واحد من هذه الأقسام سأضيف الرسوم.
في هذه الحالة المحددة، كان لدي درجة حرارة بوسطن وفينيكس ومينيابوليس. لذلك أنشئ مصفوفة اثنين في اثنين (2×2) فحسب. لدي هنا هذا الشيء الذي يبدو هكذا، شكل بهذه الرسوم الفرعية الأربعة. وسأضيف بوسطن هنا، وفينيكس هنا، ثم مينيابوليس هنا في الأسفل، أي 1 و2 و3 هي الرسوم الفرعية التي أفتحها. لا شيء في الرابع، فسيكون ذلك الموضع الرابع فارغًا فقط.
ستبدو الرسوم إذًا على نحو كهذا. ولم أغير سماكات الخطوط في هذه الحالة المحددة. لم أحتج إلى ذلك. ويمكنكم رؤية أن كل شيء مرسوم مع تقييد الارتفاعات، مجددًا، من 0 إلى 100، كي يكون كل شيء قابلًا للمقارنة. ولاحظوا الموضع الفارغ هنا، لأنه لم يكن لدي شيء أملؤه به. هل هناك أسئلة عن هذا؟ هل هذا مثير للاهتمام؟ حسنًا.
حسنًا، بذلك ننتهي من بعض الأشياء الأساسية جدًّا التي يمكننا فعلها بالرسم، تخصيصات أساسية. ما أود فعله الآن هو فتح بضع مجموعات بيانات مختلفة للمعالجة. يمكننا البدء برسم القيم وحدها على رسم عادي. ثم يمكننا البدء في فحص الأشياء التي نصوّرها، وطرح مزيد من الأسئلة، ورؤية إلى أين نذهب من هناك.
أول شيء أود فعله هو فتح ملف عن سكان الولايات المتحدة. يحتوي هذا الملف المحدد على 40 عددًا مختلفًا. فيه قيمة للسكان على مدى نحو 400 سنة، كل 10 سنوات. أي 40 قيمة مختلفة لدرجات الحرارة، بدءًا من، لا أدري، زمن بعيد جدًّا حتى نحو 2010 أو شيء كهذا.
ملاحظة المترجم: قول «درجات الحرارة» هنا زلة في المصدر؛ الملف المذكور يتعلق بعدد السكان. كذلك فإن المدى من 1610 إلى 2010، إذا شمل الطرفين بقيمة كل عشر سنوات، يضم 41 قيمة، لا 40؛ أُبقيت الأعداد المنطوقة كما وردت.
يبدو الملف على نحو كهذا. يبدأ عند 1610 ويصل نزولًا إلى 2010. هذه إذًا 40 سطرًا لـ40 سنة — 400 سنة، كل 10 سنوات. ثم يوجد فراغ في الملف. ثم لدي قيمة السكان. تبدأ عند 350، وتزداد، وتصل نزولًا إلى 300,000 في 2010.
هذا إذًا ما يبدو عليه الملف. من المهم معرفة شكل الملف، لأنكم ستضطرون إلى قراءة هذه البيانات وحفظها في بنية بيانات (Data Structure) من نوع ما يسهل التعامل معها. في حالتنا، بنية بيانات يسهل التعامل معها جدًّا، عندما يكون لديكم تسلسل كامل من القيم، هي القائمة.
ما يمكننا فعله إذًا هو فتح هذا الملف للمعالجة، وقراءة السنوات على هيئة قائمة، ثم قراءة قيم السكان على هيئة قائمة أيضًا. يمكننا استخدام قاموس أيضًا إذا أردنا. لكنني في هذه الحالة استخدمت قائمتين فقط. فلننظر إلى الكود الذي يفعل ذلك. يبدو كثيرًا، لكنني سأمرّ عليه. هنا الدالة التي ستقرأ الملف.
صفحة المصدر 10
تفتح الملف فقط، وتنشئ قائمتين فارغتين. ستحتوي إحداهما على التواريخ. والأخرى ستحتوي على أعداد السكان. تمرّ عبر كل سطر في الملف. لقد وضعت هنا في الأعلى ما يبدو عليه سطر في الملف تقريبًا. فيه عدد ما، ثم فراغ، ثم عدد آخر. لكن عندما نقرأ وملف — عندما نقرأ سطرًا من ملف، فإن Python تقرؤه فعلًا على أنه سلسلة نصية.
ما يعنيه ذلك لنا هو أننا سنضطر إلى أخذ هذه السلسلة النصية، وكل سطر هو السلسلة النصية «1640 فراغ 26,634»، شيء من هذا القبيل، ونفصلها بطريقة ما، بحيث يكون لدينا التاريخ ثم عدد السكان، ثم نحفظ هذين الجزأين بطريقة ما في قوائم.
أول شيء نلاحظه إذًا هو أن لدينا فاصلة مزعجة في قيم السكان. تلك القيم مقروءة للبشر (Human-Readable)، فتسهل قراءتها علينا، لكن الحاسوب ليس سعيدًا جدًّا بها.
إذا كان لدي عدد مثل 11,345، أيًّا كان، فهو يُقرأ على أنه سلسلة نصية، أليس كذلك؟ وإذا حاولت فقط تحويل نوعه (Cast) إلى عدد صحيح، مباشرة دون إجراء أيّ معالجة عليه، فإن Python غير سعيدة جدًّا. لذا ما نحتاج إلى فعله هو إزالة تلك الفاصلة. لأنني ما دمت لا أملك فاصلة هناك، تستطيع Python تحويل ذلك العدد النصي إلى عدد صحيح عادي لنرسمه بعد ذلك.
هذا إذًا ما يفعله هذا الجزء من الكود. يفعله بطريقة غريبة. يقول: مهلًا، خذ سطر المحارف كله، واحتفظ فقط بالمحارف التي تكون إما رقمًا أو فراغًا. بذلك يزيل الفاصلة عمليًّا، لأنه ينشئ نسخة جديدة من ذلك السطر تحتوي فقط على الأرقام والفراغات. سيأخذ 2 و6، ثم 6 و3 و4 بعدها مباشرة. فينشئ فقط هذا السطر الجديد الذي يبدو هكذا.
ثم بعد أن يحصل على هذا السطر الجديد، سنقسم عند الفراغ، لأننا نلاحظ أن كل — بعد كل تاريخ، كل سنة، لدينا فراغ يفصل قيمة السكان عن التاريخ. فإذا قسمنا عند الفراغ باستخدام أمر split، فإن الشيء قبل الفراغ، أي السطر عند الفهرس 0، يعطينا التاريخ. سنحوّله فقط إلى int ونلحقه بـdates.
ثم السطر عند الفهرس 1 هو الشيء بعد الفراغ، مجددًا دون الفاصلة لأننا نفّذنا تلك الحيلة. ثم نحوّل ذلك إلى عدد صحيح ونلحقه بقيم populations لدينا. هذا ما نفعله هناك. وهذا ما نفعله هناك. وبعد ذلك، نعيد التواريخ وأعداد السكان فحسب. تصبح التواريخ قيم x لرسمِي، وتصبح أعداد السكان قيم y لرسمِي.
ثم نرسمه، ويبدو على نحو كهذا. أسهل كثيرًا في القراءة أو معرفة ما يحدث من مجرد النظر إلى أعداد وحدها. تصوير الأشياء أجمل دائمًا من مجرد قراءة مجموعة كاملة من الأعداد، حتى لو كانت 40 فقط. وفي الواقع، يمكننا معرفة شيئين لم نكن نستطيع معرفتهما — بالتأكيد لم نكن لنستطيع معرفتهما بمجرد النظر إلى الأعداد وحدها.
الأول هو أننا نلاحظ حدبة صغيرة هنا في عدد السكان. هذا أثر الحرب العالمية الثانية على عدد السكان. والثاني، الأصعب قليلًا في ملاحظته، حدبة صغيرة أخرى هنا في الأسفل. وهذا أثر الحرب الأهلية على عدد السكان. لذا فإن التصوير أجمل. يكشف بعض الأشياء المثيرة للاهتمام.
الشيء الآخر الذي ينبغي ملاحظته هو: حسنًا، ماذا يحدث هنا في الأسفل؟ يبدو نوعًا ما أن عدد السكان لا ينمو كثيرًا حقًّا. ثم ربما من 1750 فصاعدًا يبدأ النمو أسّيًّا. يصعب معرفة ما يحدث بالضبط في ذلك الجزء السفلي. فلنفكر في طريقة مختلفة لعرض هذه البيانات. بدل وجود مقياس خطي (Linear Scale) على محور y لدينا، فلنرَ إمكان استخدام مقياس لوغاريتمي (Logarithmic Scale).
صفحة المصدر 11
سنضيف أمرًا يخبر Python بأن تجعل محور y لدينا بمقياس لوغاريتمي بدل الخطي. إذا فعلنا ذلك، فهذا يعني أن كل زيادة منتظمة في y — على محور y لدينا — ستعني زيادة أسّية في عدد السكان.
حسنًا، فلنرسم ذلك. وإذا رسمناه، نحصل على شيء يبدو هكذا. يبقى محور x دون تغيير. ما زلنا نزيد السنوات خطيًّا. لكن محور y الآن لوغاريتمي. ماذا نلاحظ إذًا؟ حسنًا، أرى خطًّا هنا، وأرى خطًّا آخر هنا.
مجددًا، النمو الخطي على مقياس لوغاريتمي يعني نموًّا أسّيًّا على مقياس خطي. لذا ما نلاحظه هو أن هناك هاتين الفترتين الزمنيتين من النمو الأسّي. وفي الواقع، يبدو أن تلك السنوات المبكرة تنمو — يبدو أن عدد السكان ينمو بمعدل أسرع من السنوات اللاحقة. ولم يكن ذلك ظاهرًا بسهولة على الرسم السابق الذي كان لدينا.
إذًا السؤال — لدي سؤال لكم. أيّهما وجدتم أكثر إفادة؟ حسنًا، هذا يعتمد نوعًا ما على ما نهتم باكتشافه. إذا كنا نهتم باتجاهات كبيرة في البيانات، حيث اكتشفنا في الرسم العلوي الأيسر هنا آثار الحروب على عدد السكان، فالرسم العلوي الأيسر هو الذي ينبغي النظر إليه. لكن إذا صوّرنا البيانات بطريقة مختلفة قليلًا، فإنها تعطينا رؤى مختلفة عما حدث لعدد السكان.
لم يكن ذلك ظاهرًا بالقدر نفسه في الرسم السابق. لذا يعتمد الأمر نوعًا ما على ما تهتمون باكتشافه، لتقرروا أيّ هذه الرسوم تجدونه أكثر إفادة. وأحيانًا يكون كلاهما ضروريًّا على الأرجح لمعرفة ما حدث بالضبط. حسنًا، بذلك ننهي مثالنا عن سكان الولايات المتحدة.
فلننظر الآن إلى ملف مختلف قليلًا. في هذا الملف المحدد سننظر إلى سكان البلدان. هذه أعداد السكان في مجموعة كاملة من البلدان المختلفة — أو عذرًا، كل البلدان — مرتّبة من البلدان ذات أعلى عدد سكان في أعلى الملف، نزولًا إلى البلدان ذات أدنى عدد سكان في أسفل الملف. فهي مصنّفة في الأساس بهذا الترتيب. لذا أعرف أن هذا الترتيب صحيح.
يوجد إذًا 237 سطرًا في هذا الملف. ماذا نلاحظ عن البيانات؟ نحتاج إلى معرفة شكل البيانات كي نقرأ الملف. ومجددًا، سنهتم باستخراج أجزاء معينة منها. في التحليل المحدد الذي سأجريه تاليًا، أنا مهتمة فعلًا بعدد السكان نفسه فقط. فلا أهتم بالرتبة (Rank). ولا أهتم فعلًا بالبلد أيضًا.
لذلك كل ما سيهتم — الكود الذي سيقرأ الملف لن يهتم إلا باستخراج قيمة السكان. ولاحظوا، مرة أخرى، أن لدينا فواصلنا هنا في قيم السكان، صحيح. لذا سنستخدم الحيلة نفسها للتخلص منها. مجددًا، تنسيق جميل مقروء للبشر هنا، لكنه ليس جيدًا جدًّا لقراءة الملف والتعامل مع البيانات نفسها. لذلك سنضطر إلى معالجة ذلك عندما نقرأ.
ها هي الدالة التي تقرأ الملف. ستبدو مشابهة جدًّا للدالة السابقة. مجددًا، لدي عينة صغيرة من ملفنا هنا في الأعلى لتذكير أنفسنا بشكله. في هذا الملف المحدد، لا أهتم إلا بالحصول على قيمة السكان. وهو فعلًا ملف مفصول بعلامات الجدولة (Tab-Separated). لدي إذًا الرتبة، ثم جدولة، ثم البلد، ثم جدولة، ثم السكان، ثم جدولة، ثم التاريخ.
عندما آخذ سطرًا من الكود، فأول ما سأفعله هو تقسيمه عند محرف الجدولة. ومحرف الجدولة هو هذا الشيء، شرطة مائلة عكسية ثم t، أي \t. بعد أن أقسمه عند محرف الجدولة، يكون الشيء عند الفهرس 0 هو الرتبة، والشيء عند الفهرس 1 هو البلد، والشيء عند الفهرس 2 هو عدد السكان. والشيء عند الفهرس 3 هو التاريخ.
صفحة المصدر 12
إذا كنت مهتمة بالحصول على عدد السكان فقط، فسأنظر إلى الشيء عند الفهرس 2، وهذا يعطيني عدد السكان على هيئة سلسلة نصية هنا. ثم ننفّذ الحيلة نفسها تمامًا كما من قبل لإزالة الفواصل. لا يوجد فراغ في هذه الحالة المحددة، لأنني حفظت العدد وحده بفضل التقسيم عند الجدولة. فكل ما أحتاج إلى فعله هو الاحتفاظ بالأرقام. وإذا احتفظت بالأرقام، فسأحتفظ بذلك العدد على أنه عدد السكان فقط.
مجددًا، أحوّله إلى عدد صحيح، لأنني أود العمل بالأعداد في قوائمي بدل السلاسل النصية. سيكون ذلك غريبًا جدًّا. وفي نهاية هذه الدالة، يكون لدي كل أعداد السكان بالترتيب نفسه الذي كانت فيه في ذلك الملف، مقروءة على هيئة قائمة، أعداد لا سلاسل نصية. فإذا رسمنا أعداد السكان، أعداد السكان وحدها، فسيكون لدي شيء كهذا.
إذا رسمت أعداد السكان وحدها، أرى شيئًا يبدو هكذا. يصعب معرفة ذلك نوعًا ما — أعني، إنه انخفاض أسّي كبير، لكن هل هذا ما هو عليه حقًّا؟ مجددًا، سنجري رسمًا شبه لوغاريتمي (Semi-Log Plot) على محور y لنرى بالضبط هل يوجد أيّ نوع من السلوك الخطي على ذلك الرسم اللوغاريتمي. ولا مفاجأة في أنه يطابق حدسنا نوعًا ما. توجد بلدان قليلة جدًّا ذات عدد سكان عالٍ جدًّا. وتوجد بلدان قليلة جدًّا ذات عدد سكان منخفض. ثم مجموعة من البلدان في الوسط نوعًا ما هنا، حيث ينخفض عدد السكان أسّيًّا فقط.
حسنًا، لكن ليس هذا هو التحليل الذي أود إجراءه على هذه البيانات، لأنه ممل نوعًا ما. بدلًا من ذلك، ما سنحلله هو فعلًا الأرقام الأولى فقط من عدد سكان كل بلد. ما أود فعله إذًا من مجموعة البيانات تلك هو، بعد أن أحصل على قائمة بكل أعداد سكان البلدان، أن أستخرج ذلك الرقم الأول.
الطريقة التي نفعل بها ذلك هي أنه، إذا كان لدينا عدد سكان، لا أدري، 2542136، أيًّا كان، فسآخذ هذا العدد وأحوّله إلى سلسلة نصية. هذا ما يفعله سطر الكود الواحد هذا، كل ذلك في واحد. يحوّله إلى سلسلة نصية، ويستخرج العنصر عند الفهرس 0. يصبح ذلك السلسلة النصية "2". ثم نحوّل ذلك إلى عدد صحيح ليعطينا 2.
إذًا، ينفّذ ذلك السطر من الكود كل تلك الخطوات بالترتيب. وفي نهاية هذه الحلقة، يكون لدي قائمة الأرقام الأولى هذه، التي تحتوي على كل الأرقام الأولى لكل واحد من أعداد سكان تلك البلدان. سأطبع ذلك لكم فقط لتعطيكم فكرة عن شكله. نرى هذا إذًا. كان لدينا بلدان في الأعلى لديهما مليار شخص — مليار شخص. ثم البلد التالي في الأسفل كان لديه 300 مليون شخص، ثم 200 مليون، ثم 200 مليون، ثم 200 مليون، و100 مليون، وهكذا.
باستخراج ذلك الرقم الأول فقط، نرى هذا النمط من القيم. فإذا رسمنا ذلك — وهذا بالضبط ما نفعله هنا في الأسفل، وسأفعله على الشرائح فقط — إذا رسمنا تلك القائمة بذلك الترتيب، نحصل على رسم يبدو على نحو كهذا. إنه نمط صغير جميل يشبه أسنان المنشار (Sawtooth Pattern).
إذا حدقنا فيه قليلًا، فسيبدو منطقيًّا، لأن الأعداد التي حصلنا عليها مباشرة من الملف كانت بالفعل مرتّبة حسب الرتبة، من أعلى عدد سكان إلى أدنى عدد سكان. هنا في الأسفل، كان لدينا — عذرًا، هنا في الأسفل، هذه النقطة الصغيرة هنا تمامًا، كان فيها بلدان كان عددهما مليارًا، أي 1، 1، ثم بلد واحد كان لديه 300 مليون. ثم ثلاثة بلدان كان لديها 200 مليون، ثم مجموعة من البلدان كان لديها 100 مليون وشيء، أي 1، 1، 1، 1، 1، 1.
وبما أننا نسير بترتيب تنازلي من حيث الرتبة، فما إن ننتهي من الوصول إلى ذلك الرقم الدال، عندما ننتقل إلى الأسفل، سنبدأ النظر إلى بلدان لديها 90 مليون، و90 مليون، و90 مليون، و80 مليون، و80 مليون، وهكذا. إذًا، مجرد ترتيب كل هذه القيم، الأرقام الأولى لكل واحدة من هذه القيم، يجعل وجود نمط أسنان المنشار منطقيًّا، أليس كذلك؟ لدينا في الأساس 9، 8، 7، 6، 5، 4، 3، 2، 1، ثم 9، 8، 7، 6، 5، 4، 3، 2، 1، وهكذا.
صفحة المصدر 13
نحصل إذًا على هذا النمط. ما أود فعله هو السؤال: كم بلدًا رقمه الأول 1؟ يبدو أن هناك كثيرًا، أليس كذلك؟ إذا عددنا عدد هذه البلدان هنا في الأسفل، فيبدو كثيرًا. كم بلدًا رقمه الأول 2؟ مجددًا، نعدّ عدد البلدان على هذه الدرجة من أسنان المنشار لدي. كم بلدًا رقمه الأول 3؟ وهكذا.
ويبدو نوعًا ما، لا أدري، أن البلدان التي رقمها الأول 1 ربما أكثر من البلدان التي رقمها الأول 9، أليس كذلك؟ هناك اثنان فقط هنا، ربما نحو خمسة هنا، وربما واحد هنا، واثنان هنا، واثنان هنا، بينما البلدان التي لديها 1 كثيرة فعلًا.
فلنحاول رسم هذه البيانات، القيم هنا. ما أهتم بفعله هو إنشاء مدرّج تكراري (Histogram). في المدرّج التكراري، يحتوي محور x على مجموعة من الفئات (Bins). في هذه الحالة المحددة، الطريقة التي أود بها تقسيم بياناتي إلى فئات هي أن أقول إن فئاتي ستكون الأرقام 1، 2، 3، 4، 5، 6، 7، 8، 9. هذا محور x. ومحور y سيكون عدًّا، تكرارًا (Frequency) لعدد البلدان لدي التي يكون العدد 1 رقمها الأول، وعدد البلدان التي يكون العدد 2 رقمها الأول، وهكذا.
إذًا، فيما يتعلق بهذه القائمة التي تحتوي على كل الأرقام الأولى للبلدان، أنا في الأساس لدي — لدي في الأساس فئة واحدة تعدّ كم واحدًا لدي في هذه القائمة، وفئة أخرى تعدّ كم اثنين لدي في هذه القائمة، وفئة أخرى تعدّ كم ثلاثة لدي في هذه القائمة، وهكذا.
إذا رسمت ذلك المدرّج التكراري، فإنه يبدو هكذا. كنت سأتوقع أن يكون هذا المدرّج التكراري متساويًا تقريبًا، أليس كذلك؟ لماذا يهم الرقم الأول؟ يبدو أنه في هذه الحالة المحددة، احتمال أن يكون الرقم الأول 1 أعلى من احتمال أن يكون 9. لكن حدسيًّا، كنت سأتوقع أن يظهر كل رقم باحتمال متساوٍ، 11%، أي 1/9.
لكن بدلًا من ذلك، ما نحصل عليه هو هذه النتيجة المفاجئة جدًّا: يبدو أن الرقم الأول نحو 30%. أن يكون الأول — عذرًا، أن يكون الرقم الأول 1 يبدو نحو 30%. وأن يكون الرقم الأول 2 يبدو نحو 18 بالمئة أو شيء ما، وهكذا وهكذا. ثم كون الرقم الأول 9 منخفض إلى حد كبير. سيكون نحو، ما هذا، 12 من أصل 200 بلد. إذًا احتمال منخفض جدًّا.
يتبين أن هذا الرسم يتبع فعلًا شيئًا يسمى قانون بنفورد (Benford's Law). وهذا قانون مثبت جيدًا. ينطبق على مجموعة من مجموعات البيانات المختلفة التي نجدها في الطبيعة، مجموعات بيانات ليست لها حقًّا حدود عليا أو دنيا، مثل أعداد سكان البلدان. يقول قانون بنفورد عمليًّا إن احتمال أن يكون الرقم الأول في مجموعة كبيرة من الأعداد 1 أو 2 أو 3 أو أيًّا كان — حيث d هذه هي 1 أو 2 أو 3 أو أيًّا كان — يتبع هذه الصيغة.
ملاحظة المترجم: تشير المتحدثة إلى صيغة على الشريحة دون قراءتها حرفيًّا. لإيضاح الإشارة نصيًّا، صيغة قانون بنفورد هي
P(d) = log10(1 + 1/d)، حيثdمن 1 إلى 9. هذا إدراج توضيحي من المترجم، لا جملة إضافية من التفريغ.
إذا أوجدنا احتمال أن يكون ذلك الرقم الأول 1، فإننا نحسب في الأساس لوغاريتم 2 للأساس 10، وهو نحو 0.28. احتمال أن يكون الرقم الأول 2 هو لوغاريتم واحد ونصف للأساس 10، وهو نحو 0.17. فبياناتنا، أعداد سكان البلدان، إذا نظرنا إلى الرقم الأول فقط من بياناتنا، تتبع هذا القانون أيضًا، وهذا لطيف جدًّا.
ملاحظة المترجم: التقريبان العدديان منقولان من الكلام؛ القيمتان الأدق هما
log10(2) ≈ 0.3010وlog10(1.5) ≈ 0.1761.
كثير من البيانات التي نتعامل معها يوميًّا يتبع هذا القانون: عدد متابعي وسائل التواصل الاجتماعي، وعدد المنشورات التي ينشئها الناس، وقيم الأسهم، وأسعار البقالة، والإحصاءات الرياضية، وارتفاعات المباني، وضرائب الدخل، وأشياء كهذه، كلها تتبع هذا القانون، وهذا رائع جدًّا.
وعلى سبيل الاستطراد، إحدى الطرق التي يكتشف بها الناس الاحتيال الضريبي في ضرائب الدخل هي تطبيق قانون بنفورد على ضرائب الدخل المقدَّمة. الناس، عندما يقدّمون أعدادًا احتيالية، يميلون إلى جعل كل عدد يظهر باحتمال متساوٍ. ينسون قانون بنفورد. لذا يطبّقون قانون بنفورد هذا على الإقرارات الضريبية التي يُحتمل أن تكون احتيالية. ويكتشفون أن ما قدّمه أولئك الناس لا يتبع هذا القانون فعلًا. ومن ثم، فهو احتيالي. لذا إذا كنتم تختلقون أعدادًا، فتذكروا قانون بنفورد فحسب.
ملاحظة المترجم: لا تنطبق توزيعات بنفورد على جميع مجموعات البيانات لمجرد كونها عددية، وعدم مطابقتها ليس وحده إثباتًا للاحتيال. يمكن استخدامها مؤشرًا للفحص عندما تكون خصائص البيانات ملائمة، مع أدلة أخرى؛ أُبقيت صياغة الاستطراد الأصلي منفصلة عن هذا التقييد.
صفحة المصدر 14
جميل. نعم، هذا شيء مثير للاهتمام حقًّا يمكن أن ينتج من بعض البيانات. ومجددًا، استطعنا تصويره ورؤية القانون يعمل. حسنًا، أريد المرور على مثال أخير واحد. سيُظهر هذا مجموعة من الأشياء المختلفة. سيكون فيه كثير من الكود. سأتحدث عنه باختصار فقط. لكن الكود موجود في الشرائح — أو عذرًا، في ملف Python، إذا أردتم النظر إليه بعمق أكبر.
سأقارن درجات حرارة المدن مجددًا، لكننا سنجري تحليلًا أعمق يتعامل مع بيانات أكثر بكثير. في مجموعة البيانات هذه تحديدًا، لدي درجات حرارة يومية لـ55 سنة، لـ21 مدينة مختلفة. كمية البيانات التي لدي هنا ستكون إذًا 365 * 55 * 21. هذا هو عدد الصفوف التي ستوجد في مجموعة بياناتي.
هذه أعداد كثيرة ينبغي النظر إليها يدويًّا. لذا بدلًا من ذلك، سنعتمد على تجميعها (Aggregating) باستخدام المتوسطات وأشياء كهذه لفهم كل هذه البيانات. هذا ما سيبدو عليه الملف. لدي ثلاثة أعمدة، عمليًّا، تفصل بينها فواصل. الأول يقابل المدينة. والثاني يقابل درجة الحرارة بالسيلسيوس (Celsius). والثالث هو تاريخ أخذها.
وهو مرتب على نحو جميل. يُفصّل التاريخ هكذا. فيه سنة، سنة، سنة، سنة، شهر، شهر، يوم، يوم. هذا إذًا 1961، الرابع من يناير. هكذا نقرأ ذلك. لاحقًا، عندما نحاول التفكير في أيّ من — الحصول على درجات حرارة معينة لسنة محددة أو أشياء كهذه، يمكننا استخدام التنسيق — إبقاء التنسيق في ذهننا واستخدامه لاستخراج المعلومات ذات الصلة.
حسنًا، أول شيء نريد فعله هو أخذ هذه البيانات وحفظها مجددًا في بنية بيانات جميلة تسمح لنا بالتعامل معها كما نشاء. وهي قائمة. سنفتح ملفنا إذًا للقراءة. أنشئ قائمتين هنا، واحدة لدرجات الحرارة، والأخرى للتواريخ. سأمرّ في حلقة عبر كل سطر في ملفي. وأعرف أنه مفصول بفواصل، فسأقسمه عند الفاصلة. الشيء عند الفهرس 0 سيكون التاريخ — سيكون المدينة. والشيء عند الفهرس 1 سيكون قيمة درجة الحرارة. والشيء عند الفهرس 2 سيكون التاريخ.
أود أخذ قيمة درجة الحرارة وحفظها على هيئة عدد، لأنني أريد رسم هذه الأعداد. ستحصل هذه الدالة المحددة على قائمة بكل درجات الحرارة لمدينة معينة. فالمدينة هنا ستكون معاملًا. وبينما أقرأ الملف، لا أود إلا أخذ الأسطر التي تطابق تلك المدينة. هنا لدي عبارة if هذه. لن أنفّذ إلا هذه الأشياء داخل عبارة if هذه إذا كانت المدينة تطابق ما يهمني.
وماذا نفعل بعد ذلك؟ حسنًا، سنأخذ قيمة درجة الحرارة، وهي الشيء عند الفهرس 1، لأنني قسمت عند الفواصل. ونحوّلها إلى عدد عشري (Float). لا توجد فواصل أو أيّ شيء غريب في ذلك العدد. فهو عدد عشري فقط، 0.55 على هيئة سلسلة نصية. إذا حوّلناه إلى float، فستفعل Python ذلك لنا بكل سرور. ثم سنشغّل دالة التحويل من السيلسيوس إلى الفهرنهايت (Fahrenheit)، عودة إلى المحاضرة الأولى، لتحويل تلك القيمة من السيلسيوس إلى الفهرنهايت. ثم سنلحق كل درجات الحرارة هذه في قائمة جميلة.
وفي نهاية الدالة، سنعيد كل درجات الحرارة. سيكون العدد 365 * 55. هذا عدد قيم درجات الحرارة التي لدينا لمدينة واحدة. وما نود فعله كخطوة أولى هو الحصول فقط على فكرة عن متوسط درجات الحرارة لكل واحدة من هذه المدن المختلفة. إذًا، على كل نقطة بيانات لدينا لمدينة معينة، ما متوسط درجة الحرارة عبر كل هذه الأيام لكل هذه السنوات؟ أود عددًا واحدًا يمثل درجة الحرارة لكل مدينة.
صفحة المصدر 15
هذا إذًا ما يفعله هذا الكود. سيحصل أولًا على كل المدن الموجودة في ملفي، أي كل القيم الفريدة (Unique Values). ثم سيحصل على متوسط درجة الحرارة عبر كل تلك الأيام، 365 مضروبة في 55 سنة. ثم سيأخذ اسم مدينتي على هيئة أول محرفين فقط، ثم سينشئ مخططًا مبعثرًا جميلًا. فلا أريد ربط كل قيم هذه المدن معًا. أود فقط أن تكون نقاطًا في رسمي.
إذا فعلنا ذلك، نحصل على شيء يبدو هكذا. هذه النقطة هنا تمثل درجة الحرارة في سياتل عبر كل يوم على مدى 55 سنة. أي نقطة درجة حرارة واحدة تمثل درجة حرارة سياتل لكل هذه البيانات التي لدي. ماذا يخبرنا هذا؟ حسنًا، ليس كثيرًا مما لم نكن نعرفه بالفعل. لدي هذه المدن هنا في الأسفل، وهي باردة جدًّا، وتلك المدن هناك في الأعلى، وهي دافئة جدًّا. ثم كل بقية مدني في مكان ما في الوسط، في المتوسط. فلا شيء لم نكن نعرفه حقًّا، ولا شيء ثوري هنا.
ما سيكون ألطف للنظر إليه هو تغير درجة الحرارة بمرور الزمن. هنا تخبرني نقطة بياناتي الواحدة بدرجة الحرارة التي تمثل تلك المدينة. لكن ما أود فعله هو أخذ درجة الحرارة التي تمثل تلك المدينة لكل سنة. لكل سنة، أود الحصول على متوسط درجة الحرارة لتلك السنة. وربما يمكنني رؤية اتجاه (Trend) لدرجات الحرارة إلى أن تصبح أدفأ بمرور الزمن، أو أبرد بمرور الزمن، أو شيء كهذا — أو ألا يكون هناك أيّ تغير إطلاقًا.
هذا هو الكود الذي يفعل ذلك. لدي الحصول على درجات الحرارة حسب السنة لمدينة. هذه نوعًا ما هي الدالة التي تُشغَّل. وهي تستدعي التي في الأعلى. هنا لدي الكود من الشريحة السابقة. يحصل على قائمة بكل درجات الحرارة لمدينة معينة، أي عبر كل تلك السنوات الخمس والخمسين. ثم أهتم بكل هذه السنوات المختلفة. لكل واحدة من هذه السنوات، سأحصل على قيمة درجة حرارة.
دالة getTempsForYear هذه هي الدالة هناك في الأعلى. وكل ما تفعله أنها تنظر إلى ذلك العمود الثالث وتحصل على السنة. تطابق أول أربعة محارف من خانة السنة. وما دامت تطابق تلك السنة، فستُضاف إلى هذا المجموع التراكمي (Running Sum). وفي النهاية سأحصل على متوسط السنة.
لنقل إنني سأقارن أربع مدن مختلفة. لدي 55 قيمة لكل مدينة تمثل متوسط درجة الحرارة في تلك السنوات الخمس والخمسين. ولدي أربع مدن لأقارنها. هذا ما سيبدو عليه رسم واحد لبوسطن — عذرًا، هذا ما سيبدو عليه الرسم. لدي خط واحد لبوسطن، وهو الأزرق؛ وخط واحد لسان دييغو، الأحمر؛ وخط واحد لفينيكس، البرتقالي؛ وخط واحد لميامي، الأخضر.
ماذا نرى؟ حسنًا، نعم، بوسطن في المتوسط أبرد كثيرًا من أيّ واحدة من المدن الثلاث الأخرى. جميل. كنا نعرف ذلك. ميامي وفينيكس حارتان على نحو جميل هناك. أود أن أكون هناك الآن. وماذا عن الاتجاهات؟ لهذا أجرينا هذا التحليل. ماذا نرى من الاتجاهات هنا؟ حسنًا، ربما تزداد درجة حرارة بوسطن قليلًا، زيادة طفيفة بمرور الزمن. سان دييغو، يبدو أنها تبقى ثابتة تقريبًا.
درجة حرارة فينيكس تبدو أنها تزداد على نحو كبير جدًّا مع مرور الزمن، في المتوسط. وربما ازدادت ميامي قليلًا أيضًا. لكن هذا يخبرنا فقط بمتوسط درجات الحرارة. شيء يمكننا فعله إذًا هو فحص القيم القصوى (Extremes). إضافة إلى رسم المتوسط، فلنرسم أيضًا الحد الأدنى لبوسطن والحد الأقصى لبوسطن، ونرَ بالضبط مدى قرب ذلك المتوسط.
هل المتوسط في الوسط، ثم الحدود الدنيا والعليا بعيدة جدًّا عن المتوسط؟ أم أنها كلها قريبة إلى حد كبير من المتوسط؟ هذا هو الكود الذي يفعل ذلك. الدالة هنا هي نفسها تمامًا كما على الشريحة السابقة. الفرق الوحيد هو أننا، بدل إعادة المتوسط، سنحصل أيضًا على max وmin لتلك القائمة من درجات الحرارة.
صفحة المصدر 16
ثم لدينا كل المدن المختلفة التي سنرسم لها ذلك. فنحصل على شيء يبدو هكذا. مجددًا، للوهلة الأولى أميل إلى تجاهل محاور y للوهلة الأولى. لذلك للوهلة الأولى يبدو مجددًا: مهلًا، الحدود الدنيا متساوية تقريبًا، والحدود العليا متساوية تقريبًا، والمتوسطات متساوية تقريبًا. لذا فالتفكير في ذلك مضلل. فلنساعد القارئ مرة أخرى ونعيّن حدودًا لمحاور y لدينا.
هنا لدي حدّ لدالتي أو لكودي. سيجعل كل واحد من رسومي يبدأ عند 0 ويبلغ حدّه الأعلى عند 100. والآن أصبحت الرسوم قابلة للمقارنة على نحو جميل. أرسم الآن متوسط درجة الحرارة لكل سنة. توجد إذًا 55 نقطة بيانات من هذه، وأدنى درجة حرارة لكل سنة، وأعلى درجة حرارة لكل سنة. أي 55 نقطة بيانات تُرسم.
ماذا يمكننا معرفة؟ صار استنتاج المعلومات من هذا أسهل كثيرًا، أليس كذلك؟ يمكننا أن نرى أن متوسط درجة الحرارة في بوسطن هو أدنى درجة حرارة في ميامي وسان دييغو. ماذا يمكننا أيضًا أن نرى؟ التفاوت (Variation) في بوسطن عالٍ إلى حد كبير. والتفاوت في ميامي وسان دييغو أقل كثيرًا. سان دييغو تتراوح من 40 إلى 80، بينما تتراوح بوسطن من 0 إلى 90، لذا فتفاوتها عالٍ جدًّا.
يبدو أن متوسط بوسطن وسان دييغو متساوٍ تقريبًا، لكن ذلك التفاوت مختلف جدًّا بين هاتين المدينتين. نعم، سؤال.
أحد الحضور (AUDIENCE):
ماذا يحدث إذًا إذا كانت هناك قيمة أقل من أدنى قيمة لـy؟
آنا بيل (ANA BELL):
آه، نعم، عندئذ لا تُرسم. نعم، كان ذلك على الحافة هناك، لكنه لم ينخفض. يمكنني تصور أن يكون الحد الأدنى في بوسطن دون 0 في سنة ما. لكن نعم، عندئذ لن يُرسم فحسب. يمكنك استخدام ذلك لتوجيه حدودك. يمكن للكود هنا أن يقول إن حد y يساوي الحد الأدنى لتلك القوائم الثلاث، وعندئذ ستكون متأكدًا من ضمان أن — ستضمن أن ذلك الحد الأدنى يلامس الحدود. سؤال رائع.
ملاحظة المترجم: المقصود بقول «لا تُرسم» هنا أن القيمة الواقعة خارج حدود المحور لا تظهر داخل مساحة الرسم المعروضة؛ لا يعني ذلك حذفها من البيانات الأصلية.
حسنًا، شيء آخر يمكننا النظر إليه هو توزيع درجات الحرارة (Temperature Distribution). هذا رسم جميل. يعطينا نوعًا ما نظرة عامة إلى ما يحدث سنة بسنة. لكن ماذا لو ركزنا على سنة واحدة محددة؟ والآن، لتلك السنة، فلنفكر في شكل توزيع درجات الحرارة. ما أهتم برسمه هو شيء كهذا.
لدي على محور x ربما فئات تقابل درجات حرارة مختلفة، أي درجة حرارة 0، ودرجة حرارة 1، ودرجة حرارة 2، و3، و4، وهكذا. وسيكون ذلك كبيرًا إلى حد ما، لأن أعلى درجة حرارة لدي ربما ستكون 100. لسنة معينة واحدة، أود أن تكون لدي 100 فئة. وارتفاع كل فئة سيكون عدًّا لعدد الأيام خلال تلك السنة التي بلغنا فيها درجة حرارة 0، وعدد الأيام خلال السنة التي بلغنا فيها درجة حرارة 1.
ويمكننا أخذ المتوسطات، أو يمكننا تقريب درجات الحرارة (Round)، لأن درجة الحرارة ستكون طبعًا مثل 20.6 أو شيء كهذا. ثم يمكننا تقريبها فقط لتناسب إحدى هذه الفئات. هذا بالضبط ما يفعله هذا الكود. هنا يمرّ في حلقة على كل واحد من التواريخ. وننشئ هذه القائمة من درجات الحرارة. والقائمة لسنة محددة واحدة. هذه السنة هي معاملي هنا.
هنا يمرّ فقط عبر البيانات، ويضمن أنني آخذ الصفوف التي تطابق تلك السنة فقط. ثم هنا في الأسفل هو الموضع الذي أنشئ فيه قائمة من 100 عنصر. يمكنكم التفكير فيما هنا في الأسفل على أنه قائمة، كهذه. والفهرس على نحو جميل — نجح ذلك على نحو جميل جدًّا — سيقابل الفهرس قيمة درجة حرارة، وهو أمر غريب عند التفكير فيه. لا ينجح هذا إلا في هذا السيناريو المحدد مع درجات الحرارة بالفهرنهايت.
صفحة المصدر 17
لكن الفهرس في هذه القائمة يقابل درجة حرارة. وبينما أمرّ عبر قائمتي لدرجات الحرارة على مدى 365 يومًا في سنة، سأقرّب درجة الحرارة تلك. وسأضيفها إلى الفهرس الذي أعتقد أنها تنتمي إليه. بهذه الطريقة، سيكون لدي — إذا كانت درجة الحرارة 4 درجات، فعند ذلك الفهرس 4 سأزيد عدِّي واحدًا. وإذا كان لدي لاحقًا في القائمة درجة حرارة أخرى مقدارها 4، فسأزيده مرة أخرى عند الفهرس 4.
لدي إذًا هذه القائمة الجميلة، وهذه الأعداد الجميلة لكل درجات الحرارة عند مختلف — عذرًا، كل الأعداد عند كل درجات الحرارة المختلفة هذه. من أصل تلك الأيام الـ365، كم يومًا كانت فيه درجة الحرارة 4؟ ومن أصل 365 يومًا، كم يومًا كانت فيه درجة الحرارة 85؟ ثم يمكننا رسم ذلك. ولن نرسم رسمًا عاديًّا، لأننا لا نريد وصل هذه القيم.
لن ننشئ مدرّجًا تكراريًّا، لأننا صنعنا مدرّجنا التكراري بأنفسنا هنا. بدلًا من ذلك، سننشئ مخطط أعمدة (Bar Plot). ومخطط الأعمدة يأخذ محور x ومحور y لدي؛ محور x هو هذه القائمة من 0 حتى 100، درجات الحرارة المقابلة، ومحور y هو عدد الأيام التي كانت فيها كل واحدة من درجات الحرارة تلك. ونحصل على شيء يبدو هكذا.
ملاحظة المترجم: ذُكرت قائمة من 100 عنصر في الصفحة السابقة، ثم المجال من 0 حتى 100 هنا. القائمة ذات 100 عنصر لها فهارس من 0 حتى 99؛ يجب التعامل صراحةً مع القيم السالبة أو التي تبلغ 100 أو تتجاوزها عند تطبيق هذا الأسلوب. هذه ملاحظة حدود للتطبيق، وليست تغييرًا لوصف المتحدثة.
هذا لسنة واحدة فقط. إذا جمعنا كل هذه الأعمدة، عدد مرات الظهور، فينبغي أن يكون المجموع 365 يومًا. هذا هو التوزيع، أظن، في 1961. على اليسار بوسطن، وعلى اليمين سان دييغو. يمكننا بالفعل معرفة بعض الأشياء المثيرة للاهتمام من هذا. إذًا في 1961، كيف يبدو التوزيع لهاتين المدينتين؟
حسنًا، يبدو أنه — هذا شيء كان يمكننا معرفته بالفعل من الحد الأدنى والحد الأقصى — يبدو أن درجات الحرارة في بوسطن تراوحت نوعًا ما من نحو 0 إلى 85. لكن ما يخبرنا به التوزيع، ولم يكن الحد الأدنى والحد الأقصى يستطيعان إخبارنا به، هو كم يومًا كان منخفضًا إلى هذا الحد، وكم يومًا كان مرتفعًا إلى هذا الحد.
هل لدينا نوع من توزيع جميل يشبه المنحنى الجرسي (Bell Curve)، حيث تقع معظم درجات حرارتنا بأريحية في هذا المجال الأوسط؟ هذا أحد الخيارات. أو ربما توجد مدينة ما لها توزيع متساوٍ فقط. في الأساس ستكون لديها درجات حرارة — عذرًا، عدّ درجات الحرارة في الأساس متساوٍ. فلا يهم حقًّا أيّ درجة حرارة تتحدث عنها، سيكون هناك عدد متساوٍ من الأيام خلال السنة عند تلك الدرجة.
هذا النوع من الرسوم يمكنه إخبارنا بذلك. يبدو إذًا أن درجة حرارة بوسطن ربما تتبع نوعًا ما منحنى جرسيًّا واسعًا جدًّا، ربما فيه حدبتان، ثنائي المنوال (Bimodal). درجة حرارة سان دييغو، مجددًا، أقل كثيرًا كثيرًا في التغير (Variability)، لكنها تبدو أيضًا أنها تتبع هذا النوع من المنحنيات الجرسية هنا، حيث — ثنائي المنوال، بحدبتين هنا، واحدة عند درجات حرارة في حدود 55، ودرجات حرارة قليلة جدًّا في الوسط، ثم مجموعة من درجات الحرارة في السبعينيات.
هذا إذًا هو التوزيع لعام 1961. ويمكننا أن نسأل مجددًا عما يحدث للتوزيع في سنة لاحقة. إذا أخذنا أكثر من سنة واحدة لنرسمها، فسأرسم هنا 1961 و2015. سنتان فقط، وليس كل ما بينهما. سيكون ذلك رسمًا مزدحمًا جدًّا جدًّا. سأميّز درجات حرارة 1961 بالأزرق، ودرجات حرارة 2015 بالأحمر.
ثم أحصل على شيء يبدو هكذا. يصعب تمييزه قليلًا. ما يمكننا فعله لهذا الرسم هو أننا نستطيع فعلًا إضافة شيء يسمى قيمة ألفا (Alpha Value)، أي شفافية (Transparency)، لنتمكن من رؤية ما وراء الأحمر. هل يمتد الأزرق إلى هنا في الأسفل تمامًا؟ هل الأزرق أقل من الأحمر قليلًا فقط؟ يصعب معرفة ذلك من هذا. شيء يمكننا فعله هو إضافة تلك الشفافية، كما قلت. وشيء آخر يمكننا فعله هو رسمهما في رسمين فرعيين منفصلين فقط.
صفحة المصدر 18
ثم يمكننا محاولة مقارنتهما لنرى بالضبط ما حدث من 1961 من حيث التوزيع إلى 2015، مجددًا من حيث التوزيع. يمكنكم، إذا أردتم، التجريب بمدن مختلفة، مدينتكم الأصلية، ورؤية ما حدث بالضبط لدرجات الحرارة عبر كل تلك السنوات. إنه شيء لطيف نوعًا ما لتجربته. هل هناك أي أسئلة؟
حسنًا، هذه هي النهاية. لم نفعل اليوم سوى خدش سطح الأشياء التي يمكنكم فعلها بالرسم. رأينا كيف نخصص رسومنا. ورأينا كيف ننشئ تسميات، بعض الأشياء الأساسية جدًّا جدًّا. لكنني آمل أنكم، نوعًا ما عبر كل هذا، رأيتم مدى فائدة تصوير البيانات.
الأوامر ليست مهمة إلى ذلك الحد، لأنكم تستطيعون دائمًا البحث عنها. لكن المهم هو أخذ مجموعة بيانات ما، ستعملون بها في العالم الحقيقي، إذا شاركتم في UROP، أو إذا قررتم أخذ مقررات أخرى في علوم الحاسوب في أقسام أخرى، مقررات في الحوسبة. ستعملون بالبيانات. وبمجرد أن تحصلوا عليها. من المهم فقط تهيئتها لتروا كيف تبدو، وتحصلوا على فكرة عامة عنها. وما إن تحصلوا على فكرة عنها، فقد يقودكم ذلك إلى مزيد من الأسئلة، وهو ما سيجعلكم تصوّرون البيانات بطريقة مختلفة قليلًا، تصبح أكثر فائدة في الإجابة عن الأسئلة، وربما طرح أسئلة جديدة للفحص.
ملاحظة المترجم: ورد في التفريغ الرسمي فعل «initialize» هنا، فنُقل بمعنى «تهيئتها». السياق المحيط يدعو إلى عرض البيانات بصريًّا للحصول على انطباع أولي، وليس إلى استبدال قيمها أو إعادة ضبطها.
حسنًا، هذا كل شيء لليوم. في المحاضرة التالية، سنربط فقط بعض الخيوط المتبقية المتعلقة بالقواميس، وبعض الأفكار عن جداول التجزئة (Hash Tables) وكيف تُخزَّن القواميس في الذاكرة، وسنجري أيضًا تمهيدًا صغيرًا للمحاكاة (Simulations)، وهي تقنية مفيدة جدًّا. مجددًا، إذا كنتم ستدرسون مزيدًا من مقررات الحوسبة في أقسام أخرى، فالمحاكاة شيء سيكون مفيدًا جدًّا جدًّا.