مكتبة NumPy هي مكتبة بايثون تُستخدم في الحوسبة العلمية. تُوفر لك مصفوفة متعددة الأبعاد لتخزين البيانات وتحليلها بطرق متنوعة. في هذا الدرس، ستشاهد أمثلة على بعض الميزات التي تُقدمها NumPy والتي لا تُذكر عادةً في البرامج التعليمية الأخرى. ستتاح لك أيضًا فرصة ممارسة مهاراتك الجديدة من خلال تمارين مُتنوعة.
إذا كنت جديدًا على NumPy، فمن الجيد أن تتعرف على أساسيات علم البيانات في بايثون قبل البدء. ستستخدم أيضًا Matplotlib في هذا الدرس لإنشاء المخططات البيانية. مع أن هذا ليس ضروريًا، إلا أن التعرف على Matplotlib مسبقًا قد يكون مفيدًا.
إعداد بيئة العمل الخاصة بك
قبل البدء بهذا الدرس، ستحتاج إلى بعض الإعدادات الأولية. بالإضافة إلى NumPy، ستحتاج إلى تثبيت مكتبة Matplotlib، التي ستستخدمها لرسم بياناتك بيانيًا. ستستخدم أيضًا مكتبة pathlib من Python للوصول إلى نظام ملفات جهاز الكمبيوتر، ولكن لا داعي لتثبيت pathlib لأنها جزء من مكتبة Python القياسية.
يمكنك التفكير في استخدام بيئة افتراضية للتأكد من أن إعداد البرنامج التعليمي لا يتداخل مع أي شيء في بيئة Python الحالية لديك.
يُعد استخدام Jupyter Notebook ضمن JupyterLab لتشغيل الكود الخاص بك بدلاً من Python REPL خيارًا مفيدًا آخر. يتيح لك هذا الخيار إجراء التجارب وتوثيق النتائج، بالإضافة إلى عرض الملفات وتحريرها بسرعة. تُعرض النسخة القابلة للتنزيل من الكود وحلول التمارين بتنسيق Jupyter Notebook.
تظهر أدناه الأوامر الخاصة بإعداد الأشياء على المنصات المشتركة:
PS> python -m venv venv\
PS> venv\Scripts\activate
(venv) PS> python -m pip install numpy matplotlib jupyterlab
(venv) PS> jupyter lab
هنا، أنشئ بيئة افتراضية باسم venv\
، ثم قم بتنشيطها. إذا نجح التنشيط، فسيظهر اسم البيئة الافتراضية قبل موجه الأوامر في نافذة Powershell. بعد ذلك، ثبّت numpy وmatplotlib في هذه البيئة الافتراضية، ثم ثبّت jupyterlab الاختياري. وأخيرًا، شغّل JupyterLab.
ستلاحظ أن موجه الأوامر الخاص بك يسبقه (venv). هذا يعني أن أي شيء تقوم به من الآن فصاعدًا سيبقى في هذه البيئة، منفصلًا عن أي عمل بايثون آخر لديك في مكان آخر.
الآن بعد أن قمت بإعداد كل شيء، فقد حان الوقت لبدء الجزء الرئيسي من رحلة التعلم الخاصة بك.
NumPy مثال 1: إنشاء مصفوفات متعددة الأبعاد من الملفات
عند إنشاء مصفوفة NumPy، تُنشئ بنية بيانات مُحسّنة للغاية. أحد أسباب ذلك هو أن مصفوفة NumPy تُخزّن جميع عناصرها في منطقة ذاكرة متجاورة. تعني هذه التقنية لإدارة الذاكرة تخزين البيانات في منطقة الذاكرة نفسها، مما يُسرّع أوقات الوصول. هذا بالطبع أمر مرغوب فيه للغاية، ولكن قد تحدث مشكلة عند الحاجة إلى توسيع المصفوفة.
لنفترض أنك بحاجة إلى استيراد ملفات متعددة إلى مصفوفة متعددة الأبعاد. يمكنك قراءتها في مصفوفات منفصلة ثم دمجها باستخدام دالة ()np.concatenate. مع ذلك، سيؤدي هذا إلى إنشاء نسخة من المصفوفة الأصلية قبل توسيع النسخة بالبيانات الإضافية. يُعدّ النسخ ضروريًا لضمان بقاء المصفوفة المُحدّثة متجاورة في الذاكرة، نظرًا لأن المصفوفة الأصلية قد تحتوي على محتوى غير ذي صلة بجوارها.
إن نسخ المصفوفات باستمرار في كل مرة تُضيف فيها بيانات جديدة من ملف قد يُبطئ المعالجة ويُهدر ذاكرة نظامك. وتزداد المشكلة سوءًا كلما زادت البيانات المُضافة إلى المصفوفة. على الرغم من أن عملية النسخ هذه مُدمجة في NumPy، إلا أنه يُمكنك تقليل آثارها باتباع الخطوتين التاليتين:
- عند إعداد مصفوفتك الأولية، حدد الحجم المطلوب قبل تعبئتها. يمكنك حتى التفكير في زيادة حجمها لاستيعاب أي بيانات إضافية مستقبلية. بمجرد معرفة هذه الأحجام، يمكنك إنشاء مصفوفتك مسبقًا.
- الخطوة الثانية هي ملئها ببيانات المصدر. سيتم إدراج هذه البيانات في مصفوفتك الحالية دون الحاجة إلى توسيعها.
بعد ذلك، سوف تستكشف كيفية ملء مجموعة NumPy ثلاثية الأبعاد.
ملء المصفوفات ببيانات الملف
في هذا المثال الأول، ستستخدم بيانات ثلاثة ملفات لملء مصفوفة ثلاثية الأبعاد. محتوى كل ملف موضح أدناه، وستجد هذه الملفات أيضًا في المواد القابلة للتنزيل:
يحتوي الملف الأول على صفين وثلاثة أعمدة بالمحتوى التالي:
1.1, 1.2, 1.3
1.4, 1.5, 1.6
يحتوي الملف الثاني، والذي له أيضًا نفس الأبعاد، على ما يلي:
2.1, 2.2, 2.3
2.4, 2.5, 2.6
الملف الثالث، بنفس الأبعاد أيضًا، يخزن هذه الأرقام:
3.1, 3.2, 3.3
3.4, 3.5, 3.6
قبل المتابعة، أضف هذه الملفات الثلاثة إلى مجلد البرنامج. تحتوي المواد القابلة للتنزيل أيضًا على ملفين باسم file10.csv وfile11.csv، وستستخدمهما لاحقًا.
يُظهر الرسم البياني أدناه مجموعة NumPy الناتجة التي ستنشئها من الملفات الثلاثة:

كما ترى، يشكل الملف file1.csv الجزء الأمامي من المصفوفة، والملف file2.csv القسم الأوسط، والملف file3.csv في الجزء الخلفي.
يظهر أدناه الكود المستخدم لإنشاء هذه المصفوفة. قبل تشغيل هذا الكود، تأكد من إنشاء الملفات الثلاثة الموضحة في الرسم التخطيطي أو استخدام الإصدارات المتوفرة في المواد القابلة للتنزيل. في كلتا الحالتين، ضعها في نفس المجلد الذي تُشغّل فيه الكود، ثم شغّله:
>>> from pathlib import Path
>>> import numpy as np
>>> array = np.zeros((3, 2, 3))
>>> print(id(array))
2250027286128
>>> for file_count, csv_file in enumerate(Path.cwd().glob("file?.csv")):
... array[file_count] = np.loadtxt(csv_file.name, delimiter=",")
...
>>> print(id(array))
2250027286128
>>> print(array.shape)
(3, 2, 3)
>>> array
array([[[1.1, 1.2, 1.3],
[1.4, 1.5, 1.6]],
[[2.1, 2.2, 2.3],
[2.4, 2.5, 2.6]],
[[3.1, 3.2, 3.3],
[3.4, 3.5, 3.6]]])
للبدء، يمكنك استعراض كل ملف واستخدام المعلومات لتحديد الشكل النهائي للمصفوفة المتوقعة. في هذا المثال، تحتوي الملفات الثلاثة على نفس عدد العناصر، مرتبة في صفين وثلاثة أعمدة. ستكون للمصفوفة الناتجة خاصية الشكل (3، 2، 3). ألقِ نظرة أخرى على الرسم التخطيطي وستتمكن من رؤية ذلك.
يُظهر السطران ١ و٢ استيراد مكتبة NumPy باسمها البديل القياسي np، بالإضافة إلى استيراد فئة Path من مكتبة pathlib. تتيح هذه المكتبة لبايثون الوصول إلى نظام ملفات جهاز الكمبيوتر باستخدام نهج كائني التوجه. تتيح لك كائنات فئة Path تحديد مسار ملف، وتحتوي أيضًا على دوال تُمكّنك من إجراء استدعاءات نظام لنظام التشغيل. تُستخدم هذه الميزة لاحقًا في الكود.
لقد تعلمت سابقًا أنه من الجيد إنشاء مصفوفة مُسبقًا قبل البدء بتعبئتها بالبيانات لتقليل استهلاك الذاكرة. في السطر الرابع، أنشئ مصفوفة تحتوي على أصفار بشكل (3، 2، 3) كما حددت سابقًا.
بعد ذلك، تُملأ مصفوفتك ببيانات الملفات. أنشئ حلقة for باستخدام دالة التعداد المُدمجة في بايثون، والموضحة في السطرين 8 و9. تتيح لك مُعاملاتها تكرار مجموعة من الملفات وإرجاع مرجع لكل ملف. كما تُحافظ على عدد الملفات التي تمت مواجهتها. يُخزَّن كل مرجع ملف في متغير csv_file، بينما يُخزَّن العداد المتزايد في متغير file_count.
للوصول إلى كل ملف من ملفات .csv
الثلاثة بالترتيب، استخدم الدالة Path. باستدعاء Path.cwd()
، فأنت تطلب من بايثون البحث في مجلد العمل الحالي عن الملفات، أي المجلد الذي تُشغّل منه البرنامج. يُرجع هذا كائن Path الذي يُمثل المجلد الحالي، والذي منه تستدعي الدالة .glob()
لتحديد أسماء الملفات التي ترغب في الوصول إليها.
في هذه الحالة، لأنك تريد الوصول إلى الملفات المسماة file1.csv وfile2.csv وfile3.csv، فأنت تُمرر أسماءها كسلسلة نصية file?.csv
. هذا يُخبر دالة .glob()
باختيار الملفات التي يجب أن تتطابق أسماؤها مع هذه الأحرف تمامًا، ولكن يمكن أن يكون الحرف الخامس فيها أي حرف، كما هو مُحدد بواسطة حرف البدل (?).
للأسف، قد لا ترجع دالة .glob()
الملفات بالترتيب المتوقع. في هذا المثال، يعمل كل شيء كما هو متوقع لأن اسم كل ملف يحتوي على رقم واحد كحرف خامس. لو كان هناك ملف باسم file11.csv، لكان قد قُرئ بالترتيب الخاطئ. ستتعرف على المزيد حول سبب ذلك وكيفية حله لاحقًا.
في كل مرة تتكرر فيها الحلقة، تستدعي الدالة np.loadtxt()
وتُمرر إليها اسم ملف، والذي يُحدد باستخدام خاصية الاسم. كما تطلب منها استخدام الفاصلة (,) كفاصل للحقول لفصل كل رقم في الملف. بعد ذلك، يُخصص محتوى كل ملف للمصفوفة التي أنشأتها سابقًا.
للتأكد من إدراج محتوى كل ملف في الموضع الصحيح على طول المحور 0، استخدم الدالة array[file_count]
. في أول مرة تُنفَّذ فيها الحلقة، سيتم تعيين محتوى الملف file1.csv إلى المصفوفة [0]، أو الموضع 0 على طول المحور 0. في التكرار التالي في الحلقة، سيتم تعيين الملف file2.csv إلى المصفوفة [1] على طول هذا المحور، قبل تعيين الملف file3.csv إلى المصفوفة [2]. انظر مرة أخرى إلى الرسم التخطيطي وسترى ما حدث بالضبط.
في السطرين 5 و11، طبعتَ نتيجة الدالة id(array)
. تُرجع الدالة ()id هوية الكائن. لكل كائن قيمة هوية فريدة، إذ يشغل كل كائن مكانًا فريدًا في ذاكرة الحاسوب. عند تشغيل الشيفرة البرمجية على حاسوبك، ستكون الأرقام متطابقة أيضًا، ولكنها غالبًا ما تكون مختلفة عن تلك المعروضة.
في السطرين 6 و12، تُثبت قيم الهوية المعروضة أن كائن array
الذي بدأ باحتوائه على أصفار فقط هو نفسه الكائن الذي احتوى لاحقًا على محتويات كل ملف. يُظهر هذا أنه تم استخدام كائن واحد فقط طوال العملية، وأن الذاكرة استُخدمت بكفاءة.
عند إنشاء المصفوفات بهذه الطريقة، يُنصح بالتأكد من أن كل ملف إدخال يحتوي على نفس عدد الصفوف والأعمدة من العناصر. بعد ذلك، ستتعلم كيفية التعامل مع الحالات التي لا تكون فيها ملفات البيانات متجانسة تمامًا.
التعامل مع أحجام البيانات المختلفة
للبدء، ستضيف بعض البيانات صغيرة الحجم. ستجدها في ملف باسم short_file.csv، والذي يحتوي على صف واحد فقط:
#short_file.csv
4.1, 4.2, 4.3
تريد إضافته إلى الجزء الخلفي من المصفوفة الخاصة بك كما هو موضح أدناه:

قبل تشغيل الكود المستخدم لإنشاء هذه المجموعة الثانية، تأكد من تنزيل الملف المسمى short_file.csv أو إضافته إلى نفس الدليل الذي تقوم فيه بتشغيل الكود:
>>> array = np.zeros((4, 2, 3))
>>> for file_count, csv_file in enumerate(Path.cwd().glob("file?.csv")):
... array[file_count] = np.loadtxt(csv_file.name, delimiter=",")
...
>>> array[3, 0] = np.loadtxt("short_file.csv", delimiter=",")
>>> array
array([[[1.1, 1.2, 1.3],
[1.4, 1.5, 1.6]],
[[2.1, 2.2, 2.3],
[2.4, 2.5, 2.6]],
[[3.1, 3.2, 3.3],
[3.4, 3.5, 3.6]],
[[4.1, 4.2, 4.3],
[0. , 0. , 0. ]]])
هذه المرة، ستقرأ في أربعة ملفات منفصلة، لذا سيكون شكل المصفوفة التي تُنشئها مبدئيًا (4، 2، 3). ستحتاج إلى بُعد إضافي على طول المحور 0 لاستيعاب الملف الرابع، لذا أنشئ هذا في السطر 1.
تُستخدم حلقة for لقراءة الملفات الثلاثة الأولى كما في السابق. لقراءة الملف القصير، عليك تحديد مكانه الدقيق في المصفوفة. في السابق، كان يتم ذلك بتحديد موضع، مثل array[2]، لإدراج البيانات في الموضع 2 على طول المحور 0. نجحت هذه الطريقة لأن البيانات التي أدخلتها ملأت المصفوفة الموجودة في ذلك الموضع. لكن هذه المرة، الأمر مختلف.
لإخبار بايثون برغبتك في إدراج الملف القصير بدءًا من أعلى موضع الفهرس 3 في المحور 2، استخدم array[3, 0]. يمثل الرقم 3 موضع المحور 2 كما في السابق، ولكن يجب أيضًا إضافة 0 للإشارة إلى وجوب إدراج البيانات بدءًا من الصف 0. يمكنك إلقاء نظرة على السطرين 18 و19 من مخرجات البرنامج، ثم على الرسم التخطيطي لمعرفة مكان وضع البيانات.
كما في السابق، كائن المصفوفة المُنشأ في بداية الكود هو الكائن الوحيد المُستخدم طوال العمل. على الرغم من أن الكود أضاف بيانات إليه عدة مرات، إلا أنه لم تكن هناك حاجة لنسخ غير فعال لأنك أنشأت المصفوفة مُسبقًا.
لنفترض أن الملف الرابع كان طويلًا جدًا، بدلًا من أن يكون قصيرًا جدًا. قد تتساءل عن كيفية التعامل مع هذه الملفات، وهو أمر قد يُسبب مشاكل. هذه المرة، ستستخدم ملفًا باسم long_file.csv، والذي يحتوي على سطر إضافي:
#long_file.csv
4.1, 4.2, 4.3
4.4, 4.5, 4.6
4.7, 4.8, 4.9
الآن، عليك دمجه في المصفوفة في الموضع الموضح أدناه. كما هو موضح في الرسم التخطيطي، ستحتاج إلى إضافة صف إضافي لتوسيع باقي المصفوفة لاستيعابه:

يظهر أدناه الكود المستخدم لإنشاء هذه المصفوفة الثالثة. قبل تشغيلها، تأكد من تنزيل أو إنشاء الملف long_file.csv في نفس المجلد الذي تُشغّل فيه الكود:
>>> array = np.zeros((4, 2, 3))
>>> print(id(array))
2250027278352
>>> for file_count, csv_file in enumerate(Path.cwd().glob("file?.csv")):
... array[file_count] = np.loadtxt(csv_file.name, delimiter=",")
...
>>> array = np.insert(arr=array, obj=2, values=0, axis=1)
>>> array[3] = np.loadtxt("long_file.csv", delimiter=",")
>>> print(id(array))
2250027286224
>>> array
array([[[1.1, 1.2, 1.3],
[1.4, 1.5, 1.6],
[0. , 0. , 0. ]],
[[2.1, 2.2, 2.3],
[2.4, 2.5, 2.6],
[0. , 0. , 0. ]],
[[3.1, 3.2, 3.3],
[3.4, 3.5, 3.6],
[0. , 0. , 0. ]],
[[4.1, 4.2, 4.3],
[4.4, 4.5, 4.6],
[4.7, 4.8, 4.9]]])
هذه المرة، لأن المصفوفة الأصلية قصيرة جدًا بحيث لا تستوعب محتوى ملف long_file.csv، ستحتاج إلى إطالتها بإضافة صف على طول المحور 1. يمكنك بعد ذلك إضافة محتوى ملف long_file.csv. يمكنك القيام بذلك في السطر 9 باستخدام الدالة ()np.insert، وهي دالة تسمح لك بإدراج قيم على طول المحور.
مرر أربعة معلمات إلى دالة ()np.insert. تحدد معلمة arr المصفوفة التي تريد إدراج القيم فيها، بينما يسمح لك ضبط obj إلى 2، وaxis إلى 1، وvalues إلى 0 بإدراج 0 قيمة في موضع الفهرس 2 على طول المحور 1. أي على طول الصف السفلي من المصفوفة كما هو موضح في الرسم التخطيطي. وأخيرًا، لإضافة محتوى long_file.csv إلى المصفوفة، استخدم ()loadtxt مرة أخرى كما هو موضح في السطر 11.
خذ بعض الوقت لإلقاء نظرة على الرسم التخطيطي والمصفوفة الناتجة عن الكود وستجد أن كل شيء يعمل كما هو متوقع.
لاحظ أن السطرين 4 و14 يشيران إلى اختلاف كائنات المصفوفة قبل وبعد إدخال البيانات الجديدة. يحدث هذا لأن دالة ()insert تُرجع نسخة من المصفوفة الأصلية. لتجنب هذا الهدر في الذاكرة، يُنصح بالتأكد من ضبط حجم المصفوفة الأولية بشكل صحيح قبل البدء في تعبئتها.
التأكد من صحة ترتيب الملفات
عند تشغيل الكود Path.cwd().glob("file?.csv")
، يُرجع مُكرِّر مُولِّد يُمكن استخدامه لعرض مجموعة من كائنات WindowsPath أو PosixPath. يُمثل كلٌّ منها مسار ملف نظام تشغيل واسم ملف يُطابق نمط file?.csv. مع ذلك، فإن ترتيب إرجاع هذه الملفات بواسطة المُولِّد ليس كما هو مُتوقع.
لمشاهدة مثال على ذلك، أضف الملفين اللذين يحملان اسم file10.csv وfile11.csv :
#file10.csv
10.1,10.2,10.3
10.4,10.5,10.6
ربما خمنت بالفعل ما كان موجودًا في ملف file10.csv. إذا كان الأمر كذلك، فلن تُفاجأ عندما ترى ما هو موجود في ملف file11.csv:
#file11.csv
11.1,11.2,11.3
11.4,11.5,11.6
الآن قم بتشغيل الكود التالي:
>>> for csv_file in Path.cwd().glob("file*.csv"):
... print(csv_file.name)
...
file1.csv
file10.csv
file11.csv
file2.csv
file3.csv
للتأكد من رؤية المُولِّد لكلٍّ من هذه الملفات الإضافية، عليك ضبط معيار المطابقة إلى file.csv. يُمثِّل حرف البدل (*) أي عدد من الأحرف غير المعروفة. لو احتفظتَ بحرف البدل (?)، لكان من المُضمَّن فقط الملفات التي تحتوي على حرف واحد بعد ملف السلسلة النصية. حتى مع هذه الإضافة، لا يزال هناك خطأ ما.
ملاحظة: على Linux وmacOS، قد تحتاج إلى إضافة استدعاء إلى ()sorted لرؤية الملفات مرتبة كما هو موضح أعلاه.
ستلاحظ أن الملفات الجديدة موضوعة بين الملفين file1.csv وfile2.csv، وليس في النهاية، كما توقعت على الأرجح. والسبب هو أن أسماء الملفات مُرتَّبة أبجديًا. هذا يعني أن آلية الفرز تقرأ أسماء الملفات من اليسار إلى اليمين، وتحسب جميعها متساوية حتى تجد فرقًا. بمجرد وجوده، يعتمد الفرز على هذا الفرق.
على سبيل المثال، عند تحليل أحرف كل اسم ملف، تُعتبر جميع الأحرف الأربعة الأولى متساوية في اسم الملف – وهو في هذه الحالة ” file
“. بعد ذلك، يُحدد بايثون أي الأحرف الخمسة يأتي أولاً، وذلك من خلال مراعاة أرقام رموز أحرف يونيكود لكل منها.
رمز يونيكود للحرف 1 هو 49، بينما رمزا 2 و3 هما 50 و51 على التوالي. نتيجةً لذلك، سيتم ترتيب أي اسم ملف يحتوي على الحرف 1 كحرف خامس قبل الأسماء التي تحتوي على الحرفين 2 أو 3 في نفس الموضع.
في حالة الملفات file1.csv وfile10.csv وfile11.csv، يكون الحرف الخامس من كل اسم ملف هو نفسه. لذلك، يُحدد ترتيب الفرز بناءً على الحرف السادس. عند النظر إلى قيم أحرف Unicode، تكون قيمة النقطة (.) 46، وهي تسبق الحرفين 0 و1، واللذين تبلغ قيمتيهما 48 و49 على التوالي. وبالتالي، يكون ترتيب الفرز هو file1.csv، يليه file10.csv، ثم file11.csv.
قد ترغب في تجربة دالة ()sorted المدمجة في Python لمعرفة ما إذا كان ذلك مفيدًا:
>>> for csv_file in sorted(Path.cwd().glob("file*.csv")):
... print(csv_file.name)
...
file1.csv
file10.csv
file11.csv
file2.csv
file3.csv
ومع ذلك، فإن دالة ()sorted أعطتك نفس النتيجة غير المرغوب فيها كما في السابق.
لقراءة الملفات بترتيب طبيعي، يمكنك استخدام مكتبة natsort. أولاً، ثبّتها بتشغيل الأمر python -m pip install natsort
. بعد التثبيت، يمكنك استيراد دالة ()natsorted واستخدامها بدلاً من دالة ()sorted المدمجة لتحقيق ترتيب طبيعي للملفات. يوضح الكود التالي ذلك:
>>> from natsort import natsorted
>>> for csv_file in natsorted(Path.cwd().glob("file*.csv")):
... print(csv_file.name)
...
file1.csv
file2.csv
file3.csv
file10.csv
file11.csv
أخيرًا، تمكنت من حل مشكلة فرز أسماء الملفات. يمكنك الآن تطوير الكود السابق وإضافة محتويات الملف إلى الأماكن الصحيحة في مصفوفتك:
>>> array = np.zeros((5, 2, 3))
>>> for file_count, csv_file in enumerate(
... natsorted(Path.cwd().glob("file*.csv"))
... ):
... array[file_count] = np.loadtxt(csv_file.name, delimiter=",")
...
>>> array
array([[[ 1.1, 1.2, 1.3],
[ 1.4, 1.5, 1.6]],
[[ 2.1, 2.2, 2.3],
[ 2.4, 2.5, 2.6]],
[[ 3.1, 3.2, 3.3],
[ 3.4, 3.5, 3.6]],
[[10.1, 10.2, 10.3],
[10.4, 10.5, 10.6]],
[[11.1, 11.2, 11.3],
[11.4, 11.5, 11.6]]])
هذه المرة، مرر مسارات الملفات المختلفة إلى دالة ()natsorted، والتي سترتبها بالطريقة التي قصدتها على الأرجح. يُظهر الناتج أن محتوى كلٍّ من الملفين file10.csv وfile11.csv أصبح الآن في أماكنه الصحيحة داخل المصفوفات. لاحظ مرة أخرى كيف استُخدم عامل البدل (*) هنا. كما رُفعت أبعاد هذا الإصدار من المصفوفة إلى (5، 2، 3) لتوفير مساحة للبيانات الجديدة.
كما ترى، من الممكن تمامًا إنشاء مصفوفات NumPy من بيانات الملفات. مع ذلك، يجب مراعاة الأحجام المختلفة.
NumPy مثال 2: التوفيق بين البيانات باستخدام المصفوفات المنظمة
باستخدام مصفوفات NumPy التي أنشأتها في القسم السابق، لم يكن من الممكن معرفة معنى بيانات كل عمود. أليس من الأفضل لو أمكنك الإشارة إلى أعمدة محددة بأسماء ذات معنى بدلاً من أرقام الفهرس؟ على سبيل المثال، بدلاً من استخدام student_grades = results[:, 1]
، يمكنك استخدام student_grades = results["exam_grade"]
. خبر سار! يمكنك القيام بذلك بإنشاء مصفوفة منظمة.
إنشاء مصفوفة منظمة
المصفوفة المنظمة هي مصفوفة NumPy، نوع بياناتها مكوّن من مجموعة من العناصر، كل منها يحتوي على اسم حقل ونوع بيانات عادي. بعد تعريف هذه العناصر، يمكنك الوصول إلى كل حقل على حدة وتعديله باستخدام اسمه.
يوفر الكود أدناه مثالاً لكيفية إنشاء مجموعة NumPy منظمة والإشارة إليها:
>>> import numpy as np
>>> race_results = np.array(
... [
... ("At The Back", 1.2, 3),
... ("Fast Eddie", 1.3, 1),
... ("Almost There", 1.1, 2),
... ],
... dtype=[
... ("horse_name", "U12"),
... ("price", "f4"),
... ("position", "i4"),
... ],
... )
>>> race_results["horse_name"]
array(['At The Back', 'Fast Eddie', 'Almost There'], dtype='<U12')
>>> np.sort(race_results, order="position")[
... ["horse_name", "price"]
... ]
array([('Fast Eddie', 1.3), ('Almost There', 1.1), ('At The Back', 1.2)],
dtype={'names': ['horse_name', 'price'],
⮑ 'formats': ['<U12', '<f4'],
⮑ 'offsets': [0, 48], 'itemsize': 56})
>>> race_results[race_results["position"] == 1]["horse_name"]
array(['Fast Eddie'], dtype='<U12')
تُعرَّف المصفوفة المُهيكلة في الأسطر من 3 إلى 14، ولكن ابدأ بالنظر إلى الأسطر من 4 إلى 8، والتي تُعرِّف ما يبدو أنه مصفوفة NumPy عادية. تتكون من ثلاثة صفوف وثلاثة أعمدة من البيانات المتعلقة بسباق الخيل. يمكنك بسهولة تحديد أسماء الخيول من البيانات، ولكن قد تواجه صعوبة في فهم معنى الرقمين الآخرين.
هذه المصفوفة في الواقع مصفوفة منظمة، وذلك بفضل تعريف أنواع بياناتها في السطر 9. يتكون كل نوع بيانات من اسم حقل ونوع بيانات مرتبط به. الحقول الثلاثة هي: horse_name، وprice، وposition. تُعرّف أنواع البيانات المرتبطة بها باستخدام رموز بروتوكول واجهة المصفوفة. يُعرّف U12 سلسلة نصية من 12 حرفًا، بينما يُحدد f4 وi4 تنسيقي الفاصلة العائمة والأعداد الصحيحة المكونين من 4 بايتات، على التوالي.
بعد إعداد المصفوفة الهيكلية، يمكنك استخدام أسماء الحقول هذه للإشارة إلى الأعمدة. في السطر ١٦، استخدمتَ “horse_name” لعرض مصفوفة أسماء خيول السباق. لمعرفة ترتيب الوصول، مررتَ حقل “position” إلى دالة ()np.sort في السطر ١٩. أدى ذلك إلى فرز المتسابقين حسب ترتيب الوصول. ثم قمتَ بتصفية النتائج لعرض horse_name
و price
فقط. وأخيرًا، في السطر ٢٧، حددتَ اسم الحصان الفائز.
التوفيق بين المصفوفات المختلفة
لإدراج أسماء الحقول في مصفوفات NumPy أغراضٌ مفيدةٌ عديدة. لنفترض أنك تريد مطابقة السجلات بمطابقة أسماء الحقول في مصفوفات NumPy منفصلة. للقيام بذلك، اربط المصفوفات معًا بحيث تظهر السجلات المطابقة فقط من كل مصفوفة. ستكون هذه الفكرة مألوفة لك إذا سبق لك إجراء ربط داخلي باستخدام SQL بين جدولي قاعدة بيانات علائقية. يُستخدم مصطلح ” inner” لتعريف الربط المستخدم هنا.
في هذا القسم، ستعمل مع ملفين جديدين: released_checks.csv و cashed_checks.csv.
يحتوي ملف issued_checks.csv على أربعة حقول: check_id، وPayee، وAmount، وDate_Issued. يُحاكي هذا الملف مجموعة من الشيكات الصادرة عن شركتك لدائنيها.
#issued_checks.csv
Check_ID,Payee,Amount,Date_Issued
1341,K Starmer,150.00,2024-03-29
1342,R Sunak,175.00,2024-03-29
1343,L Truss,30.00,2024-03-29
1344,B Johnson,45.00,2024-03-22
1345,T May,65.00,2024-03-22
1346,D Cameron,430.00,2024-03-22
1347,G Brown,100.00,2024-03-15
1348,T Blair,250.00,2024-03-15
1349,J Major,500.00,2024-03-15
1350,M Thatcher,220.00,2024-03-15
يحتوي ملف cashed_checks.csv على ثلاثة حقول فقط: check_ID، والمبلغ، وتاريخ الصرف. يُحاكي هذا الملف مجموعة من الشيكات التي صرفها دائنو شركتك.
#
Check_ID,Amount,Date_Cashed
1341,150.00,2024-04-12
1342,175.00,2024-04-16
1343,30.00,2024-04-12
1345,65.00,2024-04-12
1346,430.00,2024-04-08
1349,500.00,2024-04-08
1350,220.00,2024-04-15
لنفترض أنك تريد الاطلاع على بيانات المستفيد وتاريخ الإصدار وتاريخ الصرف للشيكات التي تم صرفها. إذا دققت النظر، ستجد أن بيانات المستفيد وتاريخ الإصدار غير مضمنة في ملف cashed_checks.csv، بينما الحقلان الآخران مضمنان. لعرض جميع البيانات المطلوبة، ستحتاج إلى ضم الملفين معًا.
أضف ملفات released_checks.csv و cashed_checks.csv إلى مجلد البرنامج الخاص بك، ثم قم بتشغيل هذا الكود:
>>> import numpy.lib.recfunctions as rfn
>>> from pathlib import Path
>>> issued_dtypes = [
... ("id", "i8"),
... ("payee", "U10"),
... ("amount", "f8"),
... ("date_issued", "U10"),
... ]
>>> cashed_dtypes = [
... ("id", "i8"),
... ("amount", "f8"),
... ("date_cashed", "U10"),
... ]
>>> issued_checks = np.loadtxt(
... Path("issued_checks.csv"),
... delimiter=",",
... dtype=issued_dtypes,
... skiprows=1,
... )
>>> cashed_checks = np.loadtxt(
... Path("cashed_checks.csv"),
... delimiter=",",
... dtype=cashed_dtypes,
... skiprows=1,
... )
>>> cashed_check_details = rfn.rec_join(
... "id",
... issued_checks,
... cashed_checks,
... jointype="inner",
... )
>>> cashed_check_details[
... ["payee", "date_issued", "date_cashed"]
... ]
array([('K Starmer', '2024-03-29', '2024-04-12'),
('R Sunak', '2024-03-29', '2024-04-16'),
('L Truss', '2024-03-29', '2024-04-12'),
('T May', '2024-03-22', '2024-04-12'),
('D Cameron', '2024-03-22', '2024-04-08'),
('J Major', '2024-03-15', '2024-04-08'),
('M Thatcher', '2024-03-15', '2024-04-15')],
dtype={'names': ['payee', 'date_issued', 'date_cashed'],
⮑'formats': ['<U10', '<U10', '<U10'],
⮑'offsets': [8, 64, 104], 'itemsize': 144})
لربط مصفوفتين من NumPy، استخدم إحدى دوال recarray المساعدة، والتي تتيح لك العمل مع المصفوفات المنظمة. للوصول إليها، يجب عليك أولًا استيراد وحدة مكتبة numpy.lib.recfunctions. مرة أخرى، ستستخدم مكتبة pathlib للوصول إلى الملفات. تم استيرادها في السطرين 1 و2.
في الأسطر من ٤ إلى ١٥، أنشئ قائمتي بايثون من الثنائيات التي تُعرّف أنواع البيانات المُستخدمة لكلٍّ من ملفي issued_checks.csv وcashed_checks.csv. تُدخل هذه البيانات في مُعامل dtype في دالة ()np.loadtext لتمكينها من قراءة الملفات في مصفوفات NumPy المُهيكلة بشكل صحيح.
يتم قراءة الملفات الفعلية بواسطة الكود في الأسطر من 17 إلى 29. يمكنك استخدام ()np.loadtext كما فعلت سابقًا، ولكن هذه المرة تقوم بتعيين معلمة skiprows الخاصة بها إلى 1. سيضمن هذا تجاهل الصف الأول من كل ملف لأن كل منها يحتوي على معلومات الرأس وليس البيانات.
يتم بعد ذلك ربط مصفوفتي NumPy المقروءتين من الملفين معًا في الأسطر من 31 إلى 36 باستخدام الدالة المساعدة .rec_join()
. تستخدم هذه الدالة أربعة معلمات. يُحدد المعلم الأول الحقل الذي سيتم ربط المصفوفتين به. في هذه الحالة، يجب ربط المصفوفتين بناءً على حقول معرفاتهما، والتي تحتوي على أرقام فريدة تُعرّف كل فحص في كلا الملفين.
بعد ذلك، قم بتمرير أسماء المصفوفات التي سيتم ضمها كمعلمات ثانية وثالثة.
المعلمة الأخيرة هي الأكثر إثارة للاهتمام. بتحديد jointype=”inner”، تُنفّذ عملية ربط داخلي. هذا يعني أن المصفوفة الناتجة ستحتوي فقط على السجلات المتطابقة من كلا الملفين، مما يوفر تفاصيل كاملة عن جميع الشيكات المصروفة. أي سجل يظهر مُعرّفه في أحد الملفين دون الآخر لن يظهر في المخرجات.
للتأكد من نجاح عملية الربط، راجع نتائج الأسطر من 38 إلى 40. تحتوي مصفوفة تفاصيل الشيك النقدي على بيانات المستفيد وتاريخ الإصدار وتاريخ الصرف. الحقلان الأولان من هذه الحقول مأخوذان من ملف issued_checks.csv، بينما الحقل الأخير مأخوذ من ملف cashed_checks.csv. كما ترى، تمت مطابقة السجلات بشكل صحيح.
التعامل مع أسماء الحقول المكررة
لنفترض الآن أنك تريد أيضًا عرض مبلغ الشيك. قد ترغب في تجربة ما يلي:
>>> cashed_check_details[
... [
... "payee",
... "date_issued",
... "date_cashed",
... "amount",
... ]
... ]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'amount'
حدث خطأ KeyError لأن حقل amount لم يعد موجودًا في المصفوفة المُضمَّنة. حدث هذا لأن كلا ملفي البيانات الأصليين احتوى على حقل مبلغ. لا يُمكن إنشاء مصفوفة مُهيكلة تحتوي على حقلين بنفس التسمية. أعادت عملية الضم تسميتهما بشكل فريد.
لمعرفة أسمائها الحالية، عليك أولاً عرض أنواع بيانات المصفوفة المُضمَّنة. يمكنك الوصول إليها عبر الخاصية .dtype
:
>>> cashed_check_details.dtype
dtype([('id', '<i8'), ('payee', '<U10'), ('amount1', '<f8'), ('amount2', '<f8'),
⮑ ('date_issued', '<U10'), ('date_cashed', '<U10')])
إذا دققت النظر في الناتج، ستلاحظ أن حقلي amount
الأصليين قد أُعيدت تسميتهما إلى amount1 و amount2. للاطلاع على محتواهما، يجب استخدام هذين الاسمين. في هذه الحالة، يحتوي كلا الحقلين على نفس البيانات، لذا لا يهم أيهما تختار:
>>> cashed_check_details[
... [
... "payee",
... "date_issued",
... "date_cashed",
... "amount1",
... ]
... ]
array([('K Starmer', '2024-03-29', '2024-04-12', 150.),
('R Sunak', '2024-03-29', '2024-04-16', 175.),
('L Truss', '2024-03-29', '2024-04-12', 30.),
('T May', '2024-03-22', '2024-04-12', 65.),
('D Cameron', '2024-03-22', '2024-04-08', 430.),
('J Major', '2024-03-15', '2024-04-08', 500.),
('M Thatcher', '2024-03-15', '2024-04-15', 220.)],
dtype={'names': ['payee', 'date_issued', 'date_cashed', 'amount1'],
⮑ 'formats': ['<U10', '<U10', '<U10', '<f8'],
⮑'offsets': [8, 64, 104, 48], 'itemsize': 144})
هذه المرة يتم تضمين مبالغ الشيكات في الناتج الخاص بك.
الآن بعد أن تعرفت على الشيكات التي أصدرتها وما تم صرفه، قد ترغب في رؤية قائمة بالشيكات التي لم يتم صرفها حاليًا:
>>> outstanding_checks = [
... check_id
... for check_id in issued_checks["id"]
... if check_id not in cashed_checks["id"]
... ]
>>> outstanding_checks
[np.int64(1344), np.int64(1347), np.int64(1348)]
>>> [int(check_id) for check_id in outstanding_checks]
[1344, 1347, 1348]
في السطر الأول، أنشئ قائمة باسم ” outstanding_checks
” باستخدام فهم القائمة. ستحتوي هذه القائمة على شيكات بقيمة مُعرِّف موجودة في مصفوفة ” issued_checks
” وليست في مصفوفة “cashed_checks
“. هذه الشيكات لا تزال معلقة.
مرة أخرى، باستخدام اسم الحقل، يمكنك تحديد الحقول التي سيتم استخدامها للمقارنة بسرعة والحفاظ على الكود الخاص بك سهل القراءة.
لتسهيل الأمر، يمكنك استخراج الأرقام من المصفوفة الناتجة باستخدام فهم القائمة الثانية في السطر 10.
من أجل استكمال الأمر، قد ترغب في معرفة ما إذا كان هناك أي شيكات تم صرفها بشكل احتيالي على حسابك ولم تصدرها:
>>> [
... check_id
... for check_id in cashed_checks["id"]
... if check_id not in issued_checks["id"]
... ]
[]
تُظهر القائمة الفارغة أنه، ولحسن الحظ، لا يوجد أيٌّ منها.
التعامل مع قيم المفاتيح المكررة
قبل ربط مصفوفات NumPy، من المهم التأكد من عدم وجود قيم مفاتيح مكررة. قد يؤدي ذلك إلى نتائج غير مرغوب فيها، إذ يُنشئ علاقات من واحد إلى متعدد. في الحالات التي تكون فيها المفاتيح المكررة صالحة، على سبيل المثال، عند الحاجة إلى ربط من واحد إلى متعدد، فإن البديل الأفضل هو استخدام دالة ()merge في pandas.
يستخدم الكود أدناه ملف check_list_duplicates.csv. هذا الملف له نفس بنية ملف released_checks.csv الذي استخدمته سابقًا، ولكن يوجد سجل مكرر:
>>> from pathlib import Path
>>> import numpy.lib.recfunctions as rfn
>>> issued_dtypes = [
... ("id", "i8"),
... ("payee", "U10"),
... ("amount", "f8"),
... ("date_issued", "U10"),
... ]
>>> issued_checks = np.loadtxt(
... Path("check_list_duplicates.csv"),
... delimiter=",",
... dtype=issued_dtypes,
... skiprows=1,
... )
>>> rfn.find_duplicates(np.ma.asarray(issued_checks))
masked_array(data=[(1344, 'B Johnson', 45.0, '2024-03-22'),
(1344, 'B Johnson', 45.0, '2024-03-22')],
mask=[(False, False, False, False),
(False, False, False, False)],
fill_value=(999999, 'N/A', 1e+20, 'N/A'),
dtype=[('id', '<i8'), ('payee', '<U10'),
⮑ ('amount', '<f8'), ('date_issued', '<U10')])
للعثور على صفوف مكررة في مصفوفة منظمة، يمكنك استخدام الدالة المساعدة .find_duplicates()
. تتطلب هذه الدالة تمرير مصفوفة مُقنّعة إليها، وهي مصفوفة قد تحتوي على مدخلات مفقودة أو غير صالحة. مع أن مصفوفتك سليمة في هذا الصدد، إلا أنه يجب عليك تحويلها إلى مصفوفة مُقنّعة قبل تمريرها إلى الدالة. يمكنك أن ترى من السطر 19 من الناتج وجود صف مكرر – يظهر السجل 1344 مرتين.
للتخلص منه، يمكنك استخدام الدالة ()np.unique:
>>> issued_checks = np.unique(issued_checks, axis=0)
>>> issued_checks
array([(1341, 'K Starmer', 150., '2024-03-29'),
(1342, 'R Sunak', 175., '2024-03-29'),
(1343, 'L Truss', 30., '2024-03-29'),
(1344, 'B Johnson', 45., '2024-03-22'),
(1345, 'T May', 65., '2024-03-22'),
(1346, 'D Cameron', 430., '2024-03-22'),
(1347, 'G Brown', 100., '2024-03-15'),
(1348, 'T Blair', 250., '2024-03-15'),
(1349, 'J Major', 500., '2024-03-15'),
(1350, 'M Thatcher', 220., '2024-03-15')],
dtype=[('id', '<i8'), ('payee', '<U10'),
⮑ ('amount', '<f8'), ('date_issued', '<U10')])
لإزالة الصفوف المكررة، مرر مصفوفة ” issued_checks
” مع axis=0 إلى دالة ()np.unique لحذف الصفوف. سيؤدي هذا إلى إنشاء مصفوفة جديدة، وسيتم حذف أي صفوف مكررة، مما يبقي نسخة واحدة فقط. عند النظر إلى الناتج، ستجد أن الصف 1344 يظهر الآن مرة واحدة فقط.
مثال NumPy 3: تحليل البيانات الهرمية ورسمها بيانيًا
البيانات الهرمية هي بيانات تتكون من مستويات مختلفة، يرتبط كل مستوى بالمستويات التي تعلوها وتليها مباشرةً. غالبًا ما تُرسم باستخدام مخطط شجري، وغالبًا ما تُوصف المستويات المختلفة بأنها ذات علاقة أب بطفل.
على سبيل المثال، قد يكون لديك مؤسسة تضم عدة أقسام، ويضم كل قسم عدة موظفين. توجد علاقة هرمية بين المؤسسة وبيانات أقسامها وبيانات الموظفين العاملين في كل قسم.
تُعد المصفوفات المُهيكلة مثالية لدمج البيانات الهرمية، إذ تتيح لك الإشارة إلى البيانات باستخدام تسميات. لاستخدام NumPy مع البيانات الهرمية، يمكنك دمجها في مصفوفة واحدة.
ملاحظة: عند ربط مصفوفات NumPy باستخدام دالة ()rec_join، يقتصر الأمر على إنشاء روابط داخلية. إذا كنت بحاجة إلى إمكانيات ربط أكثر شمولاً، فينبغي عليك استخدام دالة باندا ()merge بدلاً من ذلك.
في هذا القسم، ستتناول تحليل محفظة الأسهم. محفظة الأسهم هي الاسم الذي يُطلق على مجموعة الأسهم المملوكة في مجموعة متنوعة من الشركات. يُعد إنشاء محفظة استراتيجية ذكية للمستثمرين، إذ يُساعد على توزيع مخاطر الاستثمار. الفكرة هي أن الخسائر المتكبدة في بعض الأسهم ستُعوّض بمكاسب في أسهم أخرى.
تحتوي محفظة أسهمك على بيانات هرمية لأنها تتكون من استثمارات متعددة، ولكل منها مجموعتها الخاصة من تحركات الأسعار اليومية. بتحليل هذه البيانات، يمكنك معرفة أداء محفظتك.
إنشاء المصفوفة الفارغة
البيانات المستخدمة في هذا القسم تُحاكي بعض البيانات الهرمية. لنفترض أنك تحتفظ بملف يحتوي على قائمة بالشركات المختلفة التي تملك أسهمًا فيها. في هذا المثال، ستجد هذه المعلومات في ملف portfolio.csv كما هو موضح أدناه:
#portfolio.csv
Company,Sector
Company_A,technology
Company_B,finance
Company_C,healthcare
Company_D,technology
Company_E,finance
Company_F,healthcare
يحتوي عمود الشركة على أسماء الشركات، بينما يحتوي عمود القطاع على القطاعات التي تنتمي إليها الشركة.
كل يوم، على مدار أسبوع، تُنزّل أسعار أسهم كل شركة تُساهم فيها، وتُضيفها إلى مصفوفة NumPy مُهيكلة تُسمى portfolio. ستجد بيانات أسعار كل يوم في سلسلة من الملفات المُنفصلة تُسمى share_prices-n.csv، حيث n هو رقم بين واحد وخمسة. على سبيل المثال، يحتوي ملف share_prices-1.csv على أسعار يوم الاثنين، وملف share_prices-2.csv على أسعار يوم الثلاثاء، وهكذا.
يظهر أدناه ملف عينة لأسعار الأسهم:
#share_prices-1.csv
Company,mon
Company_A,100.5
Company_B,200.1
Company_C,50.3
Company_D,110.5
Company_E,200.1
Company_F,55.3
يحتوي ملف share_prices-1.csv هذا على عمودين. يعرض عمود “الشركة” أسماء الشركات، بينما يعرض عمود “الاثنين” أسعار أسهم كل شركة ليوم الاثنين. تتبع بقية الملفات نمطًا مشابهًا، باستثناء أعمدة “اليوم” التي تختلف.
لتحليل هذه البيانات، ستحتاج إلى سبعة حقول في مصفوفة محفظتك. بالإضافة إلى اسم الشركة والقطاع الذي تنتمي إليه، ستحتاج أيضًا إلى خمسة حقول لتسجيل الأسعار اليومية لكل شركة. الحقلان الأولان عبارة عن سلاسل نصية، بينما الباقي عبارة عن أرقام عائمة.
يؤدي الكود الموضح أدناه إلى إنشاء مجموعة المحفظة الأولية:
>>> import numpy as np
>>> from pathlib import Path
>>> days = ["mon", "tue", "wed", "thu", "fri"]
>>> days_dtype = [(day, "f8") for day in days]
>>> company_dtype = [("company", "U20"), ("sector", "U20")]
>>> portfolio_dtype = np.dtype(company_dtype + days_dtype)
>>> portfolio = np.zeros((6,), dtype=portfolio_dtype)
>>> portfolio
array([('', '', 0., 0., 0., 0., 0.), ('', '', 0., 0., 0., 0., 0.),
('', '', 0., 0., 0., 0., 0.), ('', '', 0., 0., 0., 0., 0.),
('', '', 0., 0., 0., 0., 0.), ('', '', 0., 0., 0., 0., 0.)],
dtype=[('company', '<U20'), ('sector', '<U20'), ('mon', '<f8'),
⮑ ('tue', '<f8'), ('wed', '<f8'), ('thu', '<f8'), ('fri', '<f8')])
كما في السابق، سوف تستخدم كل من مكتبتي numpy وpathlib، لذا قم باستيرادها في السطرين 1 و2.
لتحديد أسماء الحقول وأنواع بيانات كل حقل في مصفوفة محفظتك، ابدأ بإنشاء القوائم الثلاث الموضحة في الأسطر 4 و5 و6. تحتوي قائمة days_dtype على سلسلة من الثنائيات، واحدة لكل قيمة في مصفوفة الأيام التي أنشأتها في السطر 4، بنوع بيانات f8، وهو عدد عشري. تحتوي قائمة company_dtype على تعريفات لبيانات الشركة في ملف portfolio.csv.
لإنشاء كائن نوع البيانات الفعلي الذي سيحدد أنواع بيانات مصفوفة المحفظة، اربط قائمتي company_dtype وdays_dtype معًا. ثم، حوّل النتيجة إلى كائن نوع بيانات باستخدام الدالة ()np.dtype كما هو موضح في السطر 8.
يُدخل كائن نوع البيانات بعد ذلك إلى دالة ()np.zeros كمعامل نوع بيانات. يمكنك أيضًا تكوين المصفوفة بالشكل (6,) لتوفير صف منفصل لكل بيانات مشاركة. ينتج عن هذا مصفوفة تحتوي على سلاسل نصية فارغة في الحقلين الأولين وأصفار في الباقي، كما هو موضح في الناتج.
ملء المصفوفة
بعد أن أنشأتَ مصفوفةً كبيرةً بما يكفي لتخزين جميع البيانات التي تحتاجها، الخطوة التالية هي البدء بتعبئتها. للبدء، أضف تفاصيل الشركات المُخزّنة في ملف portfolio.csv:
>>> companies = np.loadtxt(
... Path("portfolio.csv"),
... delimiter=",",
... dtype=company_dtype,
... skiprows=1,
... ).reshape((6,))
>>> portfolio[["company", "sector"]] = companies
>>> portfolio
array([('Company_A', 'technology', 0., 0., 0., 0., 0.),
('Company_B', 'finance', 0., 0., 0., 0., 0.),
('Company_C', 'healthcare', 0., 0., 0., 0., 0.),
('Company_D', 'technology', 0., 0., 0., 0., 0.),
('Company_E', 'finance', 0., 0., 0., 0., 0.),
('Company_F', 'healthcare', 0., 0., 0., 0., 0.)],
dtype=[('company', '<U20'), ('sector', '<U20'), ('mon', '<f8'),
⮑ ('tue', '<f8'), ('wed', '<f8'), ('thu', '<f8'), ('fri', '<f8')])
استخدم دالة ()loadtxt مرة أخرى، هذه المرة لإضافة بيانات من ملف portfolio.csv إلى مصفوفة مُهيكلة باسم “الشركات”. لاحظ أنه تم استخدام دالة .reshape((6,)) في السطر السادس لإعطاء المصفوفة نفس شكل مصفوفة المحفظة التي أنشأتها سابقًا. هذا ضروري لإدراج الشركات في المحفظة.
السطر ٨ هو مكان الإدراج. يتم إدراج حقلي مصفوفة الشركتين كحقلي الشركة والقطاع في المحفظة، كما هو واضح في الناتج.
كل ما تبقى هو إضافة أسعار الأسهم اليومية المختلفة. الكود الخاص بذلك موضح أدناه:
>>> share_prices_dtype = [("company", "U20"),("day", "f8"),]
>>> for day, csv_file in zip(
... days, sorted(Path.cwd().glob("share_prices-?.csv"))
... ):
... portfolio[day] = np.loadtxt(
... csv_file.name,
... delimiter=",",
... dtype=share_prices_dtype,
... skiprows=1,
... )["day"]
>>> portfolio
array([('Company_A', 'technology', 100.5, 101.2, 102. , 101.8, 112.5),
('Company_B', 'finance', 200.1, 199.8, 200.5, 201. , 200.8),
('Company_C', 'healthcare', 50.3, 50.5, 51. , 50.8, 51.2),
('Company_D', 'technology', 110.5, 101.2, 102. , 111.8, 97.5),
('Company_E', 'finance', 200.1, 200.8, 200.5, 211. , 200.8),
('Company_F', 'healthcare', 55.3, 50.5, 53. , 50.8, 52.2)],
dtype=[('company', '<U20'), ('sector', '<U20'), ('mon', '<f8'),
⮑ ('tue', '<f8'), ('wed', '<f8'), ('thu', '<f8'), ('fri', '<f8')])
للبدء، أنشئ قائمة تُعرّف الحقلين داخل كل ملف من ملفات أسعار الأسهم اليومية. لإضافة هذه البيانات إلى مصفوفة محفظتك الرئيسية، أنشئ حلقةً تكرر كل ملف، ولكن هذه المرة باستخدام دالة ()zip المدمجة في بايثون. ستتم معالجة الملفات بالترتيب الذي تعلمته سابقًا.
في هذه الحالة، تُمرر دالة ()zip قائمة الأيام التي حددتها سابقًا، بالإضافة إلى كل ملف تُعالجه. يُنتج هذا سلسلة من الثنائيات، واحدة لكل يوم وزوج ملفات موجود. يُخصص كل يوم في الثنائي لمتغير اليوم في الحلقة، بينما يُخصص كل ملف لمتغير csv_file.
داخل نص الحلقة، يُقرأ كل ملف مرة أخرى باستخدام دالة ()np.loadtxt، ولكن هذه المرة، بدلاً من حفظ الملف كاملاً، تُخزَّن بيانات حقل اليوم فقط. في المرة الأولى من الحلقة، تُدرج هذه البيانات في حقل mon لمصفوفة المحفظة، وفي المرة الثانية تُدرج في حقل tue، وهكذا. يتم ذلك بتعيين البيانات المقروءة في portfolio[day] لكل يوم على حدة.
تحتوي النسخة النهائية من المصفوفة على اسم الشركة والقطاع الذي تنتمي إليه. الأرقام الخمسة الأخيرة في كل سجل هي أسعار الأسهم من الاثنين إلى الجمعة بالسنت.
بعد أن جمعت بياناتك الهرمية في مصفوفة منظمة، يمكنك تحليلها باستخدام أسماء الحقول لتبسيط العملية. لنفترض أنك تريد استخراج شركة واحدة لتحليلها عن كثب:
>>> portfolio[portfolio["company"] == "Company_C"]
array([('Company_C', 'healthcare', 50.3, 50.5, 51., 50.8, 51.2)],
dtype=[('company', '<U20'), ('sector', '<U20'),
⮑ ('mon', '<f8'), ('tue', '<f8'), ('wed', '<f8'),
⮑ ('thu', '<f8'), ('fri', '<f8')])
هنا، يمكنك استخراج شركة واحدة بتحديد موقعها في عمود الشركة. للقيام بذلك، استخدم portfolio["company"] == "Company_C
“، الذي يحدد الصفوف التي تتطابق قيم عمود الشركة فيها مع “Company_C”. هذه الطريقة أسهل بكثير من تحديد الصفوف باستخدام الفهرسة.
وبالمثل، إذا كنت تريد معرفة أداء شركات التكنولوجيا في محفظتك يوم الجمعة، فيمكنك أيضًا تحديد تلك الأرقام:
>>> portfolio[portfolio["sector"] == "technology"]["fri"]
array([112.5, 97.5])
لعرض سجلات التكنولوجيا، استخدم ["sector"] == "technology
ثم، لعرض سجلات يوم الجمعة فقط، يمكنك تصفيتها باستخدام [“fri”].
لنفترض أنك تمتلك 250 سهمًا في كل شركة من شركات التكنولوجيا التي تعمل بها. قد ترغب في معرفة قيمتها السوقية بنهاية الأسبوع:
>>> portfolio[portfolio["sector"] == "technology"]["fri"] * 250 * 0.01
array([281.25, 243.75])
للقيام بذلك، استخدم الأساليب التي تعلمتها مسبقًا لتحديد أرقام يوم الجمعة لكل عنصر من عناصر التكنولوجيا في محفظتك. ثم اضرب هذه الأرقام في 250، وهو عدد الأسهم التي تملكها. وأخيرًا، اضرب الناتج في 0.01 لتحويل المبالغ إلى دولارات، مع مراعاة أن أسعار الأسهم تُسعر بالسنت. لمعرفة صافي القيمة، ما عليك سوى استخدام الدالة ()sum:
>>> sum(portfolio[portfolio["sector"] == "technology"]["fri"] * 250 * 0.01)
np.float64(525.0)
تبلغ قيمة الجزء التكنولوجي من محفظتك 525.00 دولارًا.
كما ترى، يتيح لك استخدام المصفوفات المنظمة الوصول إلى البيانات بسهولة فائقة. وللتعمق أكثر، يمكنك أيضًا استخدام هذا النهج عند استخدام مصفوفة منظمة كأساس لمخطط Matplotlib. هذا ما ستفعله لاحقًا.
رسم البيانات بيانيًا
لنفترض أنك تريد عرض تحليل الجزء التكنولوجي من محفظتك الاستثمارية على مخطط بياني. مرة أخرى، ولأنك تعمل مع مصفوفة منظمة، يصبح الكود بديهيًا:
>>> import matplotlib.pyplot as plt
>>> tech_mask = portfolio["sector"] == "technology"
>>> tech_sector = portfolio[tech_mask]["company"]
>>> tech_valuation = portfolio[tech_mask]["fri"] * 250 * 0.01
>>> (
... plt.bar(x=tech_sector, height=tech_valuation, data=tech_valuation)[0]
... .set_color("g")
... )
>>> plt.xlabel("Tech Companies")
>>> plt.ylabel("Friday Price ($)")
>>> plt.title("Tech Share Valuation ($)")
>>> plt.show()
أولاً، أنشئ مصفوفة tech_mask المساعدة. بعد ذلك، أنشئ مصفوفتين لاستخدامهما في الرسم البياني. مصفوفة tech_sector، المُعرّفة في السطر 4، تحتوي على أسماء الشركات لكل شركة من شركات tech_sector. مصفوفة tech_valuation، المُعرّفة في السطر 5، تحتوي على تقييمات يوم الجمعة لكل شركة من شركات tech_sector.
تُنشئ الأسطر من ٧ إلى ١٠ مخططًا بيانيًا شريطيًا. يحتوي المحور السيني على أسماء شركات قطاع التكنولوجيا المستخدمة لإنشاء الأشرطة، بينما يحتوي مُعامل الارتفاع على تقييماتها. تُرسم قيم تقييم التكنولوجيا. تُنشئ الأسطر ١١ و١٢ و١٣ و١٤ تسميات للمحاور وتُعطي المخطط عنوانًا. وأخيرًا، يُعرض المخطط.
إذا شغّلت الكود أعلاه في Jupyter Notebook، فلن تحتاج إلى استخدام ()plt.show. أما إذا شغّلته في Python REPL القياسي، فستظهر مراجع الكائنات بعد الأسطر 11 و12 و13. يمكنك تجاهلها، وقد تمت إزالتها من المخرجات للتوضيح.
ويظهر الرسم البياني الناتج أدناه:

كما ترى، يبدو أن شركة A تحقق أداءً أفضل من بين الاثنتين، ولو بشكل طفيف فقط.
لقد اكتسبتَ الآن فهمًا أعمق لبعض حالات استخدام NumPy المثيرة للاهتمام. على الرغم من أن NumPy هي في الأساس مكتبة بايثون تتيح لك العمل مع كائنات مصفوفة متعددة الأبعاد، إلا أنك الآن أصبحتَ أكثر فهمًا لكيفية تطبيق ميزاتها في سيناريوهات مختلفة.
اكتشاف المزيد من بايثون العربي
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.