كيفية تحويل مصفوفة NumPy Matrix إلى Array (مع أمثلة)
تُعد لغة بايثون (Python) اليوم الركيزة الأساسية للحوسبة العلمية وهندسة البيانات والذكاء الاصطناعي، ويعود الفضل الأكبر في هذا التفوق إلى منظومة المكتبات الحسابية المتطورة، وعلى رأسها مكتبة NumPy (Numerical Python). توفر هذه المكتبة هياكل بيانات مصفوفية فائقة الأداء تتيح للمطورين والباحثين تنفيذ العمليات الجبرية المعقدة ومعالجة المتجهات بكفاءة تقترب من اللغات منخفضة المستوى مثل C و Fortran، مع الاحتفاظ بمرونة وبساطة بناء الجمل البرمجية الخاصة بلغة بايثون.
مع تطور بيئة الحوسبة العددية في بايثون، وُجد هيكلان رئيسيان لتمثيل البيانات الشبكية: الهيكل الكلاسيكي numpy.matrix والمصفوفة العامة متعددة الأبعاد numpy.ndarray. ورغم أن كائن numpy.matrix صُمم في البدايات لتسهيل التحول على مستخدمي لغات الحوسبة القديمة، إلا أن التطور الهائل في خوارزميات تعلم الآلة ومعالجة الإشارات أثبت تفوق وقوة كائن numpy.ndarray، مما دفع المجتمع البرمجي والمطورين الرسميين لمكتبة NumPy إلى تصنيف كائن numpy.matrix كبنية قديمة غير موصى بها (Deprecated) وتوجيه المطورين نحو التخلي عنها.
يتناول هذا الدليل التخصصي الشامل دراسة مستفيضة ومتعمقة حول كيفية تحويل كائنات numpy.matrix إلى numpy.ndarray بمختلف الطرق والتقنيات البرمجية. سنغطي الأبعاد النظرية للبنية التحتية لإدارة الذاكرة، مع تقديم أمثلة برمجية شاملة، وتحليلات مقارنة دقيقة للأداء الحسابي، وتطبيقات عملية في أطر عمل تعلم الآلة وتصور البيانات، لضمان بناء شيفرات برمجية حديثة، مستقرة، وعالية الكفاءة.
- 1. مقدمة عامة حول هياكل البيانات في مكتبة NumPy والفروق الجوهرية بين Matrix و Array
- 2. دوافع التحويل من كائن Matrix إلى كائن Array
- 3. الطريقة الأولى: التحويل المباشر باستخدام الخاصية .A1
- 4. الطريقة الثانية: التحويل المشترك باستخدام np.asarray() مع .ravel()
- 5. الطريقة الثالثة: التحويل باستخدام الخاصية .A (للاحتفاظ بالأبعاد الأصلية)
- 6. الطريقة الرابعة: التحويل باستخدام np.array() و np.squeeze()
- 7. الطريقة الخامسة: التحويل باستخدام الدالة .flatten()
- 8. التحقق والتدقيق البرمجي لنجاح عملية التحويل
- 9. المقارنة المعيارية للأداء الحسابي (Benchmarking) بين مختلف الطرق
- 10. الأخطاء الشائعة واستكشاف المشكلات وإصلاحها أثناء التحويل
- 11. تطبيقات وسيناريوهات برمجية متقدمة
- 12. أفضل الممارسات البرمجية والتوجيهات المستقبلية لكود نظيف ومستدام
- الخلاصة والخاتمة التنفيذية
- References
1. مقدمة عامة حول هياكل البيانات في مكتبة NumPy والفروق الجوهرية بين Matrix و Array
1.1 التطور التاريخي لكائني Matrix و Array في بايثون
تعود جذور الحوسبة العددية في بايثون إلى منتصف التسعينيات مع ظهور مكتبة Numeric ومن ثم مكتبة Numarray، واللتين تم دمجهما لاحقاً على يد ترافيس أوليفانت (Travis Oliphant) عام 2005 لإنشاء مكتبة NumPy الحديثة. خلال تلك الحقبة التأسيسية، كان قطاع واسع من المهندسين والعلماء يعتمدون كلياً على بيئات مثل MATLAB، حيث تُعامل جميع المتغيرات افتراضياً كمصفوفات ثنائية الأبعاد وتخضع لقواعد الجبر الخطي القياسية في العمليات الحسابية كالضرب والجمع.
لتسهيل هجرة هؤلاء الباحثين إلى بايثون وتقليل منحنى التعلم، أضافت NumPy كائن numpy.matrix (المعروف اختصاراً باسم np.matrix)، والذي يقدم واجهة برمجية شبيهة ببيئة ماتلاب؛ حيث تُنفذ عملية الضرب بواسطة الرمز النجمي * كضرب مصفوفي بدلاً من الضرب العنصري، وتظل الكائنات محصورة دائماً في بعدين. في المقابل، صُمم كائن numpy.ndarray ليكون هيكلاً عاماً مرناً يستوعب فضاءات ذات أبعاد متعددة (N-Dimensional)، متحرراً من قيود الجبر الخطي الصارمة ليمثل اللبنة الأساسية لجميع العمليات الحسابية المتطورة.
مع اعتماد مقترح تحسين بايثون PEP 465 في إصدار بايثون 3.5، والذي قدّم المعامل الرياضي المخصص للضرب المصفوفي @، انتفت الحاجة التقنية لوجود فئة مخصصة مثل np.matrix. وبناءً على ذلك، أعلن التوثيق الرسمي لمكتبة NumPy تصنيف هذه الفئة كبنية مهملة (Deprecated)، مع تحذير المطورين من استخدامها في الشيفرات الجديدة والتوصية بالاعتماد الكامل على np.ndarray لتفادي التعارضات البرمجية المستقبلية وسهولة صيانة الأنظمة المعقدة.
1.2 الفروق البنيوية والسلوكية بين np.matrix و np.ndarray
تختلف بنية np.matrix عن np.ndarray في جوانب معمارية وسلوكية جوهرية تحدد كيفية إدارة العمليات في الذاكرة. الفارق البنيوي الأكثر وضوحاً هو أن np.matrix تفرض قيداً صارماً بكونها ثنائية الأبعاد دائماً (2D)؛ فإذا حاولت استخراج صف واحد أو عنصر محدد، فإن الناتج يظل مصفوفة ثنائية الأبعاد برتبة (1, N) أو (1, 1). بينما يمتلك كائن np.ndarray مرونة كاملة لتمثيل مصفوفات تبدأ من البعد الصفري (Scalars) إلى البعد الأحادي (1D Vectors) وتمتد إلى الموترات متعددة الأبعاد (Tensors) ذات الرتب العالية.
يمتد هذا التباين إلى السلوك الرياضي للمعاملات الأساسية؛ فعند تطبيق المعامل * بين كائني np.ndarray، يتم تنفيذ الضرب العنصري (Hadamard Product) الذي يضرب كل عنصر بنظيره المقابل، بينما تطبق np.matrix الضرب النقطي المصفوفي القياسي (Dot Product). كما تختلف استجابة الهيكلين لآلية البث (Broadcasting) الحسابية؛ حيث تتمتع np.ndarray بقدرة متقدمة على مواءمة المصفوفات ذات الأبعاد المختلفة رياضياً، في حين تفرض np.matrix قيوداً تعرقل عمليات البث المعقدة.
تؤثر هذه القيود البنيوية على كفاءة الخوارزميات الحسابية المتقدمة؛ حيث إن فرض البعد الثنائي يُلزم معالج الذاكرة بالاحتفاظ بهياكل بيانات وصفية إضافية (Metadata Overhead)، ويمنع دوال التحسين الخطي من استغلال التوازي التلقائي وتقنيات الفهرسة المتطورة المتاحة فقط في np.ndarray، مما يجعل الأخيرة المعيار الصناعي المعتمد للأداء الفائق.
1.3 أسباب تفضيل استخدام np.ndarray في التطبيقات الحديثة
يعد التوافق البرمجي الشامل السبب الرئيسي لتفضيل np.ndarray؛ إذ إن جميع المكتبات الرائدة في مجالات الذكاء الاصطناعي، وتحليل البيانات، ومعالجة الصور مثل Scikit-Learn و PyTorch و TensorFlow و OpenCV تعتمد على ndarray كنوع إدخال قياسي حصري. تمرير كائن np.matrix إلى هذه الأطر يؤدي في الغالب إلى أخطاء استثنائية (Exceptions) أو تحويلات غير متوقعة للأبعاد تعطل تدفق خطوط معالجة البيانات (Data Pipelines).
إلى جانب التوافقية، تتميز np.ndarray بتفوقها في كفاءة إدارة الذاكرة وسرعة المعالجة المتوازية؛ حيث ترتبط مباشرة ببروتوكولات الذاكرة المؤقتة للغة C و Python Buffer Protocol، مما يتيح تمرير كتل البيانات الضخمة دون الحاجة إلى نسخ إضافي (Zero-copy operations). هذا التصميم يقلل من العبء المالي والزمني لمعالجة مجموعات البيانات الكبيرة (Big Data).
توفر np.ndarray أيضاً مرونة فائقة في التعامل مع العمليات الهندسية المتقدمة مثل تغيير الأبعاد، التقليص (Squeezing)، والتمديد (Expanding)، وتطبيق التحويلات الخطية عبر فضاءات متعددة الأبعاد، مما يجعلها الأداة المثلى للمشاريع البرمجية القابلة للتوسع والإنتاج الفعلي.
2. دوافع التحويل من كائن Matrix إلى كائن Array
2.1 التوافقية البرمجية مع مكتبات تحليل البيانات وتعلم الآلة
تشترط خطوط أنابيب تعلم الآلة الحديثة تجانساً صارماً في هياكل البيانات الممررة بين مراحل الاستخراج، التنظيف، والتدريب. عندما يتم استخدام كائن np.matrix، تتولد مشكلات توافقية مستمرة مع أدوات التحويل مثل StandardScaler أو خوارزميات التدريب في Scikit-Learn؛ حيث تتوقع هذه الدوال مصفوفات أحادية البعد لمتجهات الأهداف y ذات الشكل (n_samples,)، في حين تُرجع np.matrix دائماً شكلاً ثنائياً (n_samples, 1)، مما يطلق تحذيرات DataConversionWarning أو يوقف التنفيذ كلياً.
يمتد هذا التحدي إلى مكتبة Pandas؛ حيث يتطلب تحويل البيانات بين المصفوفات وأطر البيانات DataFrames وسلاسل Series اتساقاً دقيقاً في الأبعاد. تحويل كائن np.matrix إلى عمود في إطار بيانات قد يؤدي إلى إنشاء سلاسل متداخلة تحتوي على مصفوفات صغيرة بدلاً من قيم فردية، مما يعقد عمليات التجميع والتصفية الإحصائية.
لذلك، يضمن التحويل المبكر والمنتظم لكافة المصفوفات إلى كائنات np.ndarray سلامة البنية البرمجية، ويمنع ظهور الأخطاء الصامتة (Silent Bugs) التي يصعب اكتشافها في بيئات الإنتاج، ويتيح تكاملاً سلساً مع منصات الحوسبة الموزعة والسحابية.
2.2 تحسين الأداء الحسابي وإدارة الذاكرة
تفرض فئة np.matrix طبقة تغليف إضافية (Wrapper Overhead) حول كائن ndarray الأصلي لتعديل سلوك العمليات الجبرية والفهرسة. هذا التغليف يضيف تكلفة حسابية متراكمة عند تنفيذ ملايين العمليات المتكررة، مثل الحلقات التكرارية في خوارزميات التحسين العددي وعمليات المحاكاة الحسابية.
من زاوية إدارة الذاكرة، تعتمد np.ndarray على استغلال مكتبات الجبر الخطي الأساسية عالية التحسين والمكتوبة بلغات منخفضة المستوى مثل BLAS و LAPACK، إضافة إلى دعم تعليمات المعالجة الشعاعية المتقدمة مثل AVX-512 على المعالجات المركزية الحديثة. هذه التحسينات تتطلب تخطيطاً مستمراً للذاكرة (Contiguous Memory Layout) تتفوق ndarray في تنظيمه وصيانته مقارنة بـ np.matrix.
يعزز استخدام ndarray أيضاً من كفاءة التخزين المحلي في الذاكرة المؤقتة للمعالج (Cache Locality)، مما يؤدي إلى تسريع زمن القراءة والكتابة للبيانات وتقليل معدلات الإخفاق في الذاكرة المؤقتة (Cache Misses)، وهو أمر حاسم في معالجة مصفوفات البيانات الضخمة.
2.3 تجنب السلوكيات غير المتوقعة للعمليات الخطية
يعد الغموض الدلالي للعمليات الحسابية أحد أخطر عيوب كائن np.matrix في المشاريع التعاونية. فعندما يقرأ مطور برمجيات تعبيراً حسابياً مثل A * B، يتوقع في بيئة بايثون القياسية تطبيق الضرب العنصري، لكن في حال كان أحد المتغيرين من نوع np.matrix، يتحول السلوك فجأة إلى الضرب المصفوفي، مما قد يؤدي إلى نتائج رياضية خاطئة تماماً دون إطلاق أي استثناء تحذيري.
يوضح الجدول التالي الفروق الجوهرية في سلوك العمليات الرياضية بين الكائنين:
| العملية الحسابية | سلوك كائن np.ndarray | سلوك كائن np.matrix |
|---|---|---|
| الرمز الحسابي (*) | ضرب عنصري (Element-wise multiplication) | ضرب نقطي مصفوفي (Matrix dot product) |
| الرمز الحسابي (@) | ضرب نقطي مصفوفي (بدءاً من Python 3.5) | ضرب نقطي مصفوفي |
| الرفع إلى قوة (**2) | تربيع كل عنصر على حدة | ضرب المصفوفة في نفسها جبرياً (M x M) |
| الفهرسة لصف واحد [0] | تُرجع مصفوفة أحادية البعد (1D Array) | تُرجع مصفوفة ثنائية الأبعاد (1xN Matrix) |
التحويل إلى np.ndarray يعيد ترسيخ السلوك الحسابي الواضح والمتوقع؛ حيث يصبح الضرب العنصري عبر المعامل * والضرب المصفوفي عبر المعامل @، مما يرفع من قابلية قراءة الشيفرة البرمجية ويسهل عمليات التدقيق والصيانة البرمجية.
3. الطريقة الأولى: التحويل المباشر باستخدام الخاصية .A1
3.1 المفهوم النظري لخاصية .A1 وآلية عملها
تُعد الخاصية .A1 واحدة من أكثر الطرق كفاءة وسرعة لتحويل كائن np.matrix إلى np.ndarray مسطحة أحادية البعد (1D ndarray). صُممت هذه الخاصية كواجهة وصول مباشرة (Property Accessor) مدمجة داخل فئة matrix، وتعمل على تسطيح الهيكل الداخلي للمصفوفة فوراً دون الحاجة إلى استدعاء دوال خارجية أو تمرير وسائط إضافية.
من الناحية المعمارية، تكافئ الخاصية .A1 استدعاء العملية np.asarray(x).ravel()، ولكنها تُنفذ داخلياً على مستوى لغة C بطريقة محسنة ومختصرة للغاية. ترجع الخاصية كائن ndarray أحادي البعد يحتوي على جميع العناصر مرتبة وفق النمط المتصل في الذاكرة، مما يجعلها الخيار المثالي عند الرغبة في تحويل مصفوفة Matrix إلى متجه خطي للاستخدام في دوال الحساب الرياضي والإحصائي.
تتميز هذه الخاصية بكونها تُنشئ منظراً (View) للمصفوفة الأصلية كلما أمكن ذلك بدلاً من نسخ البيانات بالكامل، مما يوفر استهلاك الذاكرة ويضمن سرعة تنفيذ لحظية حتى مع المصفوفات التي تحتوي على ملايين القيم العددية.
3.2 تطبيق برمجي مفصل باستخدام الخاصية .A1
لتطبيق هذه الطريقة عملياً، سنقوم بإنشاء مصفوفة np.matrix ثنائية الأبعاد، ثم نطبق الخاصية .A1 لتحويلها وتسطيحها، مع توضيح الشيفرة البرمجية بالكامل:
import numpy as np
# إنشاء مصفوفة Matrix ثنائية الأبعاد بحجم 2x3
original_matrix = np.matrix([[10, 20, 30], [40, 50, 60]])
# طباعة المصفوفة الأصلية ونوعها
print("المصفوفة الأصلية:")
print(original_matrix)
print("نوع المصفوفة الأصلية:", type(original_matrix))
print("شكل المصفوفة الأصلية:", original_matrix.shape)
# تطبيق التحويل المباشر باستخدام الخاصية .A1
converted_array = original_matrix.A1
# طباعة الناتج بعد التحويل
print("nالمصفوفة بعد التحويل باستخدام .A1:")
print(converted_array)
print("نوع الكائن الناتج:", type(converted_array))
print("شكل الكائن الناتج:", converted_array.shape)
عند تنفيذ هذه الشيفرة، يتم تحويل المصفوفة من الفئة numpy.matrix ذات الشكل (2, 3) إلى كائن من فئة numpy.ndarray ذي شكل أحادي البعد (6,) مباشرة وبأقل استهلاك للموارد الحوسبية.
3.3 تحليل النتيجة والتحقق من الفئة الناتجة
يوضح الفحص الدقيق للمخرجات نجاح التحويل الكامل؛ حيث يتغير نوع الكائن البرمجي من <class 'numpy.matrix'> إلى <class 'numpy.ndarray'>. كما يُظهر فحص الخاصية shape اختزال البعدين (2, 3) إلى بعد متجهي أحادي (6,)، وتصبح الخاصية ndim مساوية لـ 1 بدلاً من 2.
تتجلى قوة هذه الطريقة في قصر بنيتها البرمجية وسرعتها الفائقة، مما يجعلها الأسلوب الأنسب للمطورين عند تجهيز متجهات الخصائص أو متجهات التسميات (Labels) لتغذية خوارزميات التصنيف والانحدار، حيث تشترط معظم الدوال الرياضية مدخلات أحادية البعد حصراً.
4. الطريقة الثانية: التحويل المشترك باستخدام np.asarray() مع .ravel()
4.1 الدور الوظيفي لدالة np.asarray() في التحويل
تُعتبر دالة numpy.asarray() المدخل الأساسي والمعياري لتحويل أي بنية بيانات إلى كائن numpy.ndarray. بخلاف الدالة التقليدية np.array()، تتميز np.asarray() بكفاءتها العالية في إدارة الذاكرة، حيث تقوم بفحص الكائن المُمرر إليها؛ فإذا كان الكائن بالفعل من نوع ndarray ومطابقاً لنوع البيانات المطلوب (dtype)، فإنها لا تقوم بإنشاء نسخة جديدة في الذاكرة (No copy is made)، بل تعيد الكائن نفسه مباشرة.
عند تمرير كائن من فئة np.matrix إلى np.asarray()، يتم تجريد الكائن من التغليف المخصص لفئة المصفوفات وإرجاع كائن ndarray قياسي بنفس الأبعاد الأصلية ونفس ترتيب البيانات المخزنة، مما يضمن معالجة البنية البرمجية دون الإضرار بالأداء أو التسبب في استهلاك غير مبرر للذاكرة العشوائية.
4.2 وظيفة الدالة .ravel() في تسطيح الأبعاد
تختص دالة .ravel() في تسطيح المصفوفات متعددة الأبعاد وتحويلها إلى مصفوفة أحادية البعد (1D array). الميزة التقنية البارزة لدالة .ravel() هي أنها تحاول دائماً إرجاع منظر متصل في الذاكرة (Memory View) للكائن الأصلي بدلاً من تخصيص مساحة جديدة ونسخ العناصر، مما يجعل زمن تنفيذها شبه معدوم $O(1)$ من حيث تعقيد الذاكرة.
تدعم دالة .ravel() وسيط الترتيب order، والذي يتيح للمطور تحديد نمط قراءة وتسلسل العناصر في الذاكرة، سواء بالنمط المتوافق مع لغة C وتخزين الصفوف أولاً (C-contiguous order عبر 'C')، أو نمط لغة Fortran وتخزين الأعمدة أولاً (Fortran order عبر 'F')، مما يوفر تحكماً دقيقاً في كيفية إعادة ترتيب البيانات.
4.3 مثال عملي شامل يوضح الدمج بين الأسلوبين
يوضح النموذج البرمجي التالي كيفية الدمج المنهجي بين np.asarray() ودالة .ravel() لإنجاز عملية التحويل والتسطيح بكفاءة تامة:
import numpy as np
# إنشاء مصفوفة Matrix بحجم 3x3
mat_data = np.matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# تحويل Matrix إلى ndarray أولاً ثم تسطيحها عبر ravel
array_standard = np.asarray(mat_data)
flattened_array = array_standard.ravel()
# التحقق من نوع الكائنات والأبعاد
print("نوع الكائن بعد asarray:", type(array_standard))
print("شكل الكائن بعد asarray:", array_standard.shape)
print("المصفوفة المسطحة:", flattened_array)
print("نوع الكائن المسطح:", type(flattened_array))
print("شكل الكائن المسطح:", flattened_array.shape)
# دمج العمليتين في سطر واحد
direct_flattened = np.asarray(mat_data).ravel()
print("التحويل في سطر واحد بنجاح:", type(direct_flattened) is np.ndarray)
يتميز هذا الأسلوب بوضوحه الدلالي الصريح (Explicit Code)، مما يجعله مفضلاً في الشيفرات الإنتاجية وقواعد البيانات الضخمة التي تتطلب قراءة برمجية واضحة والتزاماً صارماً بمبادئ بيئة بايثون.
5. الطريقة الثالثة: التحويل باستخدام الخاصية .A (للاحتفاظ بالأبعاد الأصلية)
5.1 الفرق الجوهري بين الخاصية .A والخاصية .A1
توفر فئة numpy.matrix خاصيتين مدمجتين للتحويل المباشر: الخاصية .A والخاصية .A1. الفارق الجوهري بينهما يكمن في البنية الهندسية للأبعاد الناتجة (Dimensional Geometry). الخاصية .A1 كما أوضحنا تقوم دائماً بتسطيح المصفوفة وتحويلها إلى متجه أحادي البعد (N,) بصرف النظر عن شكل المصفوفة الأصلية.
في المقابل، تعمل الخاصية .A كاختصار فائق السرعة للدالة np.asarray(self)؛ حيث تقوم بتحويل فئة الكائن من numpy.matrix إلى numpy.ndarray مع الاحتفاظ الكامل والدقيق بالأبعاد الثنائية الأصلية (Rows, Columns) دون أي تغيير في الشكل الهيكلي. يوضح الجدول التالي مقارنة دقيقة بين الخاصيتين:
| الخاصية | الفئة الناتجة | الأبعاد الناتجة (Output Shape) | حالة الاستخدام الرئيسية |
|---|---|---|---|
matrix.A |
numpy.ndarray |
ثنائية الأبعاد (M, N) مطابقة للأصل |
الاحتفاظ بهيكل الصفوف والأعمدة للعمليات الرياضية |
matrix.A1 |
numpy.ndarray |
أحادية الأبعاد مسطحة (M * N,) |
تجهيز المتجهات للتصنيف، التقييم، وتصور البيانات |
5.2 حالات الاستخدام المثالية للخاصية .A
تعتبر الخاصية .A الخيار المثالي والأنسب في الحالات التي تتطلب الاحتفاظ بالهيكل المستوي ثنائي الأبعاد للبيانات، مثل مصفوفات التحويل الخطي، مصفوفات التغاير (Covariance Matrices)، أو مصفوفات الأوزان في الشبكات العصبية الاصطناعية، حيث يؤدي فقدان الأبعاد الثنائية إلى كسر العمليات الحسابية اللاحقة.
تُعد هذه الخاصية بالغة الأهمية أيضاً عند الرغبة في الاستفادة من ميزات البث الرياضي (Broadcasting) المعقدة التي يوفرها كائن ndarray دون تعديل شكل المصفوفة الأصلية، مما يسمح بإجراء عمليات الجمع والضرب المتجهي بكفاءة ومرونة عبر الصفوف والأعمدة.
5.3 تطبيق عملي خطوة بخطوة للتحويل عبر .A
يوضح المثال البرمجي التالي كيفية استدعاء الخاصية .A لتحويل مصفوفة ثنائية مع الحفاظ التام على أبعادها وسلامة بياناتها:
import numpy as np
# إنشاء مصفوفة Matrix ثنائية الأبعاد بحجم 2x4
mat_2d = np.matrix([[100, 200, 300, 400], [500, 600, 700, 800]])
print("--- قبل التحويل ---")
print("المصفوفة:")
print(mat_2d)
print("النوع:", type(mat_2d))
print("الأبعاد:", mat_2d.shape)
# تنفيذ التحويل مع الاحتفاظ بالأبعاد
array_2d = mat_2d.A
print("n--- بعد التحويل باستخدام .A ---")
print("المصفوفة الناتجة:")
print(array_2d)
print("النوع:", type(array_2d))
print("الأبعاد:", array_2d.shape)
print("عدد الأبعاد (ndim):", array_2d.ndim)
نلاحظ أن الكائن الناتج أصبح numpy.ndarray بالكامل، مع بقاء أبعاده (2, 4) وقيمته ndim = 2 كما هي، مما يتيح استخدامه المباشر في كافة خوارزميات الجبر الخطي الحديثة المعتمدة على ndarray.
6. الطريقة الرابعة: التحويل باستخدام np.array() و np.squeeze()
6.1 المنشئ الأساسي np.array() وآلية النسخ العميق
يُعد المنشئ numpy.array() الأداة التأسيسية لبناء المصفوفات في NumPy. على عكس np.asarray()، يمتلك هذا المنشئ سلوكاً أكثر حزماً تجاه النسخ وتحديد الأنواع؛ حيث يحتوي على المعامل copy (وقيمته الافتراضية True في معظم الاستخدامات النمطية)، والذي يضمن إنشاء نسخة جديدة تماماً في الذاكرة (Deep/Explicit Copy) منفصلة عن الكائن الأصلي.
يتيح المنشئ np.array() للمطور أيضاً تحديد نوع البيانات المستهدف صراحة عبر المعامل dtype (مثل np.float64 أو np.int32)، مما يجعله أداة ممتازة للتحويل عندما يتطلب التطبيق إعادة ضبط دقة الأرقام (Casting) وعزل البيانات المحولة تماماً عن أي تعديلات قد تطرأ مستقبلاً على المصفوفة المصدرية في الذاكرة.
6.2 إزالة الأبعاد الأحادية الزائدة عبر دالة np.squeeze()
تقوم دالة numpy.squeeze() بمهمة معمارية محددة: فحص أبعاد المصفوفة وإزالة كافة المحاور التي يكون طولها مساوياً للواحد الصحيح (Dimensions with length 1). في سياق التعامل مع np.matrix، تكون المصفوفات الناتجة عن عمليات الاستخلاص أو المتجهات دائماً بشكل (1, N) أو (N, 1) نتيجة للقيود الثنائية المفروضة على فئة Matrix.
عند تطبيق np.squeeze() على هذه المصفوفات بعد تحويلها إلى ndarray، يتم تقليص الأبعاد الأحادية الزائدة فوراً، ليتحول الشكل من (1, N) إلى شكل متجهي أحادي نقي (N,). هذا الدمج يوفر حلاً مرناً ودقيقاً للتعامل مع المتجهات المستخرجة من المصفوفات الكبيرة.
6.3 مثال تطبيقي يوضح التحويل والتقليص المتزامن
يوضح الكود التالي كيفية استخدام np.array() مقترنة بـ np.squeeze() لتحويل مصفوفة Matrix ثنائية ذات صف واحد إلى مصفوفة Array أحادية البعد معزولة الذاكرة:
import numpy as np
# إنشاء مصفوفة Matrix ذات صف واحد وأربعة أعمدة (1x4)
row_matrix = np.matrix([[5.5, 10.2, 15.8, 20.1]])
# تحويل المصفوفة إلى ndarray مع إنشاء نسخة جديدة وضبط النوع
base_array = np.array(row_matrix, dtype=np.float32, copy=True)
print("شكل المصفوفة بعد np.array:", base_array.shape) # الناتج: (1, 4)
# تطبيق التقليص لإزالة البعد الأحادي الزائد
squeezed_array = np.squeeze(base_array)
print("nالمصفوفة بعد التقليص (Squeeze):")
print(squeezed_array)
print("النوع:", type(squeezed_array))
print("الأبعاد الجديدة:", squeezed_array.shape) # الناتج: (4,)
print("نوع البيانات الداخلي:", squeezed_array.dtype)
يوفر هذا الأسلوب أقصى درجات التحكم الهندسي ونوعية البيانات، ويضمن استقلالية تامة للذاكرة، مما يجعله مناسباً جداً للبيئات الحسابية الحرجة وأنظمة الحوسبة المتوازية (Multi-threaded pipelines).
7. الطريقة الخامسة: التحويل باستخدام الدالة .flatten()
7.1 الأسس التقنية لدالة .flatten()
تُعد دالة .flatten() إحدى الطرق المدمجة لتسطيح المصفوفات وتحويلها إلى مصفوفات أحادية البعد. السمة التقنية المميزة لهذه الدالة هي التزامها الصارم بإنشاء نسخة جديدة ومستقلة تماماً في الذاكرة (Deep Copy) للبيانات، بصرف النظر عن حالة المصفوفة الأصلية أو ترتيب عناصرها.
تأخذ الدالة المعامل الاختياري order الذي يحدد آلية قراءة البيانات:
'C': الترتيب القياسي للغة C (قراءة البيانات صفاً تلو الآخر Row-major).'F': الترتيب المتوافق مع Fortran (قراءة البيانات عموداً تلو الآخر Column-major).'A': الترتيب التلقائي وفق كيفية تخزين المصفوفة الأصلية في الذاكرة.
تضمن هذه الدالة عزلاً تاماً للمصفوفة الناتجة، بحيث لا يؤدي أي تعديل في عناصرها إلى التأثير على الكائن الأصلي إطلاقاً.
7.2 مقارنة منهجية بين .flatten() و .ravel()
يعد فهم الفارق بين .flatten() و .ravel() من أساسيات تحسين الأداء وإدارة الموارد في مكتبة NumPy. يلخص الجدول التالي المقارنة الشاملة بينهما:
| وجه المقارنة | الدالة .flatten() | الدالة .ravel() |
|---|---|---|
| تخصيص الذاكرة | تُنشئ دائماً نسخة جديدة مستقلة (Copy) | تُرجع منظراً في الذاكرة (View) كلما أمكن ذلك |
| الاستهلاك الزمني | أبطأ نسبياً بسبب حجز الذاكرة ونسخ البيانات $O(N)$ | فائقة السرعة $O(1)$ لعدم وجود نسخ حقيقي |
| أمان البيانات (Safety) | عالي جداً؛ التعديل لا يمس المصفوفة الأصلية | يتطلب الحذر؛ تعديل المنظر قد يعدل الأصل |
| التوصية البرمجية | عند الحاجة لعزل البيانات والتعديل المستقل | لعمليات القراءة السريعة والتحليلات الضخمة |
7.3 مثال عملي على التحويل باستخدام .flatten()
يوضح النموذج التالي كيفية استدعاء .flatten() مع توضيح استقلالية الذاكرة الناتجة عن النسخ العميق:
import numpy as np
# إنشاء مصفوفة Matrix بحجم 2x2
source_matrix = np.matrix([[10, 20], [30, 40]])
# تحويل المصفوفة إلى ndarray وتسطيحها باستخدام flatten
# ملحوظة: تحويل Matrix أولاً لضمان إرجاع ndarray نقي
flat_array = np.asarray(source_matrix).flatten(order='C')
print("المصفوفة المسطحة الناتجة:", flat_array)
print("نوع الكائن:", type(flat_array))
# تعديل العنصر الأول في المصفوفة المسطحة
flat_array[0] = 999
# التحقق من استقلالية المصفوفة الأصلية
print("nبعد التعديل على المصفوفة المسطحة:")
print("المصفوفة المسطحة:", flat_array)
print("المصفوفة الأصلية (لم تتأثر):")
print(source_matrix)
تثبت المخرجات أن تعديل العنصر في flat_array لم يغير قيمة العنصر في source_matrix، مما يؤكد أن دالة .flatten() هي الخيار الأمثل عند الحاجة إلى حماية البيانات الأصلية وتجنب الآثار الجانبية غير المرغوبة (Side Effects).
8. التحقق والتدقيق البرمجي لنجاح عملية التحويل
8.1 فحص الفئة الكائنية باستخدام دالتي type() و isinstance()
بعد إتمام عملية التحويل، تقتضي أفضل الممارسات البرمجية تضمين فحوصات تأكيدية (Validation Assertions) للتحقق من سلامة البنية الكائنية، خصوصاً عند بناء دوال مخصصة أو حزم برمجية قابلة لإعادة الاستخدام. تتيح دالة type() المدمجة في بايثون فحص الفئة الدقيقة للكائن، بينما تتيح isinstance() فحص الوراثة الكائنية بشكل مرن وآمن برمجياً.
يوضح الكود التالي آلية تطبيق هذا التدقيق:
import numpy as np
mat = np.matrix([[1, 2], [3, 4]])
arr = mat.A
# التدقيق باستخدام isinstance
assert isinstance(arr, np.ndarray), "خطأ: الكائن ليس مصفوفة ndarray!"
assert not isinstance(arr, np.matrix), "خطأ: الكائن لا يزال يحمل فئة np.matrix!"
print("تم التحقق من نوع الكائن بنجاح: الكائن ndarray قياسي وشرعي.")
يضمن دمج هذه الفحوصات ضمن اختبارات الوحدة (Unit Tests) اكتشاف أي شذوذ في معالجة البيانات قبل وصولها إلى خوارزميات التدريب المعقدة.
8.2 فحص خصائص الشكل والأبعاد (shape & ndim)
لا يقتصر التحقق على نوع الكائن فحسب، بل يمتد إلى مطابقة الخصائص الهندسية للمصفوفة عبر فحص الخاصيتين shape و ndim. يحدد ndim عدد المحاور الرياضية للكائن (مثلاً 1 للمتجهات، 2 للمصفوفات المستوية، 3 للمكعبات الحجمية)، في حين يحدد shape أبعاد كل محور بدقة.
# فحص الخصائص الهندسية
print(f"عدد الأبعاد (ndim): {arr.ndim}")
print(f"أبعاد الشكل (shape): {arr.shape}")
print(f"إجمالي عدد العناصر (size): {arr.size}")
# التحقق المنطقي من تطابق الأبعاد المتوقعة
if arr.ndim == 2 and arr.shape == (2, 2):
print("الهيكل الهندسي للمصفوفة متطابق تماماً مع المتطلبات.")
تساعد هذه الفحوصات في منع أخطاء عدم تطابق الأبعاد الشهيرة (Broadcast and Dimension Mismatch Errors) التي تحدث عند تنفيذ العمليات الجبرية بين مصفوفات غير متوافقة هندسياً.
8.3 اختبار مشاركة الذاكرة عبر خاصية base ومؤشرات التخزين
للتحقق مما إذا كانت المصفوفة الناتجة تشارك الذاكرة مع المصفوفة الأصلية (View) أم أنها نسخة مستقلة تماماً (Copy)، توفر مكتبة NumPy الخاصية المتقدمة .base. إذا كانت الخاصية .base تُرجع كائناً، فإن المصفوفة الحالية هي منظر يعتمد على ذلك الكائن المصدر؛ أما إذا كانت تُرجع None، فهذا يعني أن المصفوفة تمتلك بياناتها الخاصة في الذاكرة.
mat_orig = np.matrix([[1, 2], [3, 4]])
# إنشاء منظر عبر .A
view_arr = mat_orig.A
# إنشاء نسخة عبر .copy()
copy_arr = mat_orig.A.copy()
print("فحص المنظر (View):", view_arr.base is not None) # True
print("فحص النسخة (Copy):", copy_arr.base is None) # True
# فحص مؤشر الذاكرة الفعلي
print("عنوان ذاكرة الأصل:", mat_orig.__array_interface__['data'][0])
print("عنوان ذاكرة المنظر:", view_arr.__array_interface__['data'][0])
print("عنوان ذاكرة النسخة:", copy_arr.__array_interface__['data'][0])
كما يمكن التحقق من نوع البيانات المخزن dtype لضمان عدم حدوث أي تغيير غير مقصود في دقة الأرقام (Precision Drift) أثناء خطوة التحويل.
9. المقارنة المعيارية للأداء الحسابي (Benchmarking) بين مختلف الطرق
9.1 منهجية القياس الزمني باستخدام وحدة timeit
لتقييم الكفاءة الحسابية لمختلف طرق التحويل، قمنا بإجراء اختبار معياري دقيق (Benchmarking) باستخدام وحدة timeit في بايثون. تم تصميم الاختبار على مصفوفة np.matrix بحجم $1000 \times 1000$ (تحتوي على مليون عنصر من نوع float64)، وتكرار كل عملية 10,000 مرة في بيئة معيارية مغلقة لتقليل التباين الإحصائي وتحديد الزمن الحقيقي المستغرق لكل طريقة.
import timeit
import numpy as np
setup_code = """
import numpy as np
large_mat = np.matrix(np.random.rand(1000, 1000))
"""
# قياس أزمنة الطرق المختلفة
t_A1 = timeit.timeit("large_mat.A1", setup=setup_code, number=10000)
t_A = timeit.timeit("large_mat.A", setup=setup_code, number=10000)
t_ravel = timeit.timeit("np.asarray(large_mat).ravel()", setup=setup_code, number=10000)
t_flatten = timeit.timeit("np.asarray(large_mat).flatten()", setup=setup_code, number=10000)
t_array = timeit.timeit("np.array(large_mat)", setup=setup_code, number=10000)
print(f"زمن تنفيذ .A1: {t_A1:.4f} ثانية")
print(f"زمن تنفيذ .A: {t_A:.4f} ثانية")
print(f"زمن تنفيذ asarray + ravel: {t_ravel:.4f} ثانية")
print(f"زمن تنفيذ asarray + flatten: {t_flatten:.4f} ثانية")
print(f"زمن تنفيذ np.array: {t_array:.4f} ثانية")
9.2 تحليل استهلاك الذاكرة وإدارة الموارد (Memory Profiling)
أظهر تحليل إدارة الذاكرة تبايناً جذرياً بين الطرق التي تُنشئ مناظر (Views) وتلك التي تُنشئ نسخاً مستقلة (Copies). الطرق المعتمدة على المناظر مثل .A و .A1 و np.asarray().ravel() لم تسجل أي استهلاك إضافي يُذكر للذاكرة العشوائية؛ حيث تكتفي بإنشاء رأس وصفي جديد للكائن (Object Header) يزن بضع مئات من البايتات فقط، مشيراً إلى نفس كتلة الذاكرة الأصلية البالغة 8 ميغابايت.
في المقابل، سجلت الطرق التي تعتمد النسخ الإجباري مثل np.array() و .flatten() مضاعفة فورية في استهلاك الذاكرة؛ حيث تطلبت تخصيص 8 ميغابايت إضافية من الذاكرة العشوائية لكل استدعاء، إضافة إلى استنزاف دورات المعالج في نسخ البيانات بايت تلو الآخر، مما يوضح سبب التباطؤ الزمني لهذه الطرق عند التعامل مع مصفوفات ضخمة.
9.3 جدول مقارنة شامل واستخلاص النتائج الحوسبية
يلخص الجدول المعياري التالي النتائج التقنية والحوسبية الشاملة لطرق التحويل المدروسة:
| الطريقة البرمجية | الشكل الناتج | إدارة الذاكرة | السرعة النسبية | مستوى التعقيد البرمجي |
|---|---|---|---|---|
matrix.A1 |
أحادي (1D) | منظر (View) – استهلاك شبه معدوم | فائقة جداً (الأسرع للتسطيح) | بسيط ومختصر للغاية (سطر واحد) |
matrix.A |
ثنائي (2D) | منظر (View) – استهلاك شبه معدوم | فائقة جداً (الأسرع مع حفظ الأبعاد) | بسيط ومختصر للغاية (سطر واحد) |
np.asarray().ravel() |
أحادي (1D) | منظر (View) غالباً | سريعة جداً | متوسط (دوال صريحة) |
np.asarray().flatten() |
أحادي (1D) | نسخة جديدة (Deep Copy) | متوسطة إلى بطيئة (حسب الحجم) | متوسط |
np.array(mat) |
ثنائي (2D) | نسخة جديدة (Deep Copy) | متوسطة إلى بطيئة | بسيط |
np.squeeze(np.array(mat)) |
مرن (حسب الأبعاد) | نسخة جديدة (Deep Copy) | متوسطة | مركب |
نستنتج من هذه المقارنة أن الخاصيتين المباشرتين .A1 (للتسطيح) و .A (للحفاظ على الأبعاد) هما الخياران الأمثل من حيث سرعة الأداء وكفاءة استهلاك الذاكرة وبساطة الشيفرة البرمجية.
10. الأخطاء الشائعة واستكشاف المشكلات وإصلاحها أثناء التحويل
10.1 الخلط بين المصفوفات أحادية البعد وثنائية الأبعاد
يعد الخلط بين المتجه أحادي البعد ذي الشكل (N,) والمصفوفة ثنائية الأبعاد ذات الصف الواحد (1, N) أو العمود الواحد (N, 1) من أكثر الأخطاء البرمجية شيوعاً. تفترض خوارزميات مثل الانحدار اللوجستي في مكتبة Scikit-Learn أن مصفوفة الميزات X ثنائية الأبعاد (n_samples, n_features) بينما متجه التسميات y أحادي البعد (n_samples,).
إذا استخدم المطور الخاصية .A لتحويل متجه أهداف بدلاً من .A1، سيظل المتجه بشكل (N, 1)، مما يؤدي إلى إطلاق التحذير الشهير:
DataConversionWarning: A column-vector y was passed when a 1d array was expected.
لحل هذه المشكلة، يجب دائماً استخدام .A1 أو تطبيق .ravel() على متجهات الأهداف لضمان استقرار خطوط التدريب وتفادي تحذيرات الأبعاد.
10.2 تعديل البيانات غير المقصود بسبب المنظور المشترك (View Modification)
نظراً لأن الطرق السريعة مثل .A و .A1 تُرجع منظراً (View) يشير إلى نفس كتلة الذاكرة، فإن أي تعديل لاحق على عناصر المصفوفة المحولة سيؤدي مباشرة وبشكل صامت إلى تعديل قيم المصفوفة الأصلية. إذا كانت الشيفرة البرمجية تعتمد على بقاء المصفوفة الأصلية دون تغيير لاستخدامها في حسابات أخرى، فإن هذا السلوك سيؤدي إلى أخطاء منطقية مدمرة.
يوضح المثال التالي هذا الخطأ وكيفية معالجته:
# الوقوع في خطأ تعديل المنظر المشترك
original_m = np.matrix([[1, 2], [3, 4]])
shared_arr = original_m.A
shared_arr[0, 0] = 999
print("الأصل تغير عن غير قصد:", original_m[0, 0]) # طُبعت 999!
# الحل الصحيح: فرض النسخ المستقل عند الحاجة للتعديل
safe_m = np.matrix([[1, 2], [3, 4]])
safe_arr = safe_m.A.copy()
safe_arr[0, 0] = 999
print("الأصل محمي ومستقر:", safe_m[0, 0]) # بقيت 1
10.3 التعامل مع المصفوفات ذات الأنواع المركبة والقيم المفقودة (NaNs)
عند التعامل مع مصفوفات تحتوي على قيم غير معرفة رياضياً np.nan أو قيم لانهائية np.inf، تظل هذه القيم محتفظة بخصائصها الرياضية بعد التحويل إلى ndarray. ومع ذلك، يجب توخي الحذر عند استخدام دوال التقليص والتسطيح مع مصفوفات الكائنات العامة (Object Arrays) التي تحتوي على سلاسل نصية أو كائنات مخصصة، حيث يمكن أن تؤدي عمليات التحويل غير المدروسة إلى أخطاء في تحديد النوع TypeError.
import numpy as np
# مصفوفة تحتوي على قيم مفقودة
nan_matrix = np.matrix([[1.0, np.nan], [np.inf, 4.0]])
clean_array = nan_matrix.A
# التحقق من وجود القيم الخاصة ومعالجتها
print("هل توجد قيم NaN؟", np.isnan(clean_array).any())
print("هل توجد قيم متناهية؟", np.isfinite(clean_array))
# استبدال القيم المفقودة بأمان بعد التحويل
clean_array = np.nan_to_num(clean_array, nan=0.0, posinf=9999.0)
print("المصفوفة بعد المعالجة:n", clean_array)
11. تطبيقات وسيناريوهات برمجية متقدمة
11.1 تجهيز البيانات لنماذج تعلم الآلة عبر Scikit-Learn
في بيئات تعلم الآلة العملية، غالباً ما نحتاج إلى استخراج البيانات من وحدات حسابية قديمة تُرجع كائنات np.matrix، وتجهيزها لتدريب نماذج متقدمة مثل RandomForestClassifier أو خوارزميات التجميع مثل KMeans.
import numpy as np
from sklearn.linear_model import Ridge
from sklearn.preprocessing import StandardScaler
# محاكاة بيانات تدريب قادمة من وحدة قديمة كـ Matrix
X_legacy = np.matrix(np.random.randn(100, 5))
y_legacy = np.matrix(np.random.randn(100, 1))
# التحويل المعياري السليم
# مصفوفة الميزات: نحتفظ بالبعدين عبر .A
X = StandardScaler().fit_transform(X_legacy.A)
# متجه الأهداف: نسطح الأبعاد إلى 1D عبر .A1
y = y_legacy.A1
# تدريب النموذج بكل سلاسة وبدون أي تحذيرات أبعاد
model = Ridge(alpha=1.0)
model.fit(X, y)
print("تم تدريب النموذج بنجاح. معاملات النموذج:")
print(model.coef_)
11.2 رسم البيانات وتصورها باستخدام Matplotlib و Seaborn
تتطلب مكتبات تصور البيانات الحديثة مثل Matplotlib و Seaborn مدخلات مصفوفية متوافقة لإنشاء المخططات البيانية والخرائط الحرارية (Heatmaps) بنجاح. تمرير np.matrix ثنائية إلى دالة مثل plt.plot() قد يؤدي إلى رسم عدة خطوط غير متوقعة بدلاً من خط بياني واحد متصل.
import numpy as np
import matplotlib.pyplot as plt
# توليد إشارة جيبية كـ Matrix
time_mat = np.matrix(np.linspace(0, 10, 100))
signal_mat = np.matrix(np.sin(time_mat))
# التحويل إلى متجهات أحادية البعد عبر .A1 للرسم الصحيح
time_arr = time_mat.A1
signal_arr = signal_mat.A1
# رسم الإشارة البيانية
plt.figure(figsize=(8, 4))
plt.plot(time_arr, signal_arr, label='Sine Wave Signal', color='darkblue')
plt.title('Visualization of Converted 1D NumPy Array')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.grid(True)
# ملاحظة: يتم استدعاء plt.show() لعرض الرسم
11.3 التكامل مع إطارات البيانات في مكتبة Pandas
يعد دمج مخرجات NumPy الحسابية مع هياكل مكتبة Pandas أمراً جوهرياً في تحليلات الأعمال وهندسة البيانات. يوضح المثال التالي كيفية تحويل كائنات np.matrix إلى إطارات بيانات DataFrame وسلاسل Series دون مشاكل الفهرسة:
import numpy as np
import pandas as pd
# مصفوفة ميزات Matrix
features_mat = np.matrix([[25, 50000], [30, 65000], [35, 80000]])
labels_mat = np.matrix([[0], [1], [1]])
# بناء DataFrame وسلسلة Target متوافقة تماماً
df = pd.DataFrame(features_mat.A, columns=['Age', 'Salary'])
df['Target'] = labels_mat.A1 # استخدام .A1 لإنشاء Series أحادية
print("إطار البيانات الناتج من Pandas:")
print(df)
print("nمعلومات الإطار:")
print(df.info())
12. أفضل الممارسات البرمجية والتوجيهات المستقبلية لكود نظيف ومستدام
12.1 الالتزام بتوصيات التوثيق الرسمي لمكتبة NumPy
ينص التوثيق الرسمي لمكتبة NumPy صراحة على تجنب استخدام كائن numpy.matrix في أي مشاريع برمجية جديدة، والاعتماد الحصري على numpy.ndarray. لكتابة شيفرات برمجية حديثة ونظيفة تتوافق مع معايير PEP 8، يُنصح بالاعتماد الكامل على المعامل @ لإجراء كافة عمليات الضرب المصفوفي، واستخدام الدوال القياسية مثل np.dot() و np.matmul() و np.linalg.inv() مباشرة على كائنات ndarray.
12.2 كتابة دوال مرنة للتعامل مع مختلف أنواع المدخلات
عند بناء مكتبات برمجية مشتركة، يجب تصميم الدوال البرمجية بطريقة تضمن مرونة المدخلات الدفاعية (Defensive Programming)؛ بحيث تقبل الدالة أي نوع بيانات شبيه بالمصفوفات (Array-like) وتقوم بتحويله تلقائياً إلى ndarray باستخدام np.asarray() مع دعم التلميحات النوعية (Type Hinting):
from typing import Union
import numpy as np
import numpy.typing as npt
def robust_linear_transform(data: Union[np.ndarray, np.matrix, list]) -> np.ndarray:
"""
دالة مرنة تستقبل مختلف أنواع البيانات وتحولها تلقائياً إلى ndarray نقي.
"""
# ضمان التحويل السريع إلى ndarray دون نسخ زائد
arr = np.asarray(data)
# تطبيق التحويل الحسابي
transformed = arr * 2.5 + 1.0
return transformed
12.3 خارطة طريق لتحديث المشاريع البرمجية القديمة (Refactoring Legacy Code)
لتحديث المشاريع البرمجية الضخمة التي تعتمد على np.matrix بشكل آمن ودون تعطيل الأنظمة، يوصى باتباع الخطوات المنهجية التالية:
- المسح البرمجي الشامل (Static Code Analysis): استخدام أدوات مثل
grepأو برامج التحليل الساكن لتحديد جميع مواضع استدعاءnp.matrixوnp.matوasmatrixفي قاعدة الشيفرة. - الاستبدال التدريجي: استبدال
np.matrixبالخاصية.Aللمصفوفات ثنائية الأبعاد، وبالخاصية.A1للمتجهات الخطية المسطحة. - تحديث معاملات الضرب: استبدال المعامل
*بالمعامل@أينما كان المقصود هو الضرب المصفوفي الجبري. - اختبارات التراجع (Regression Testing): تشغيل مجموعات اختبارات الوحدة للتأكد من مطابقة النتائج العددية بدقة مع خوارزميات النظام القديم.
الخلاصة والخاتمة التنفيذية
يمثل التحول من فئة numpy.matrix إلى كائن numpy.ndarray خطوة هندسية حتمية لكل مطور وباحث يعمل في منظومة الحوسبة العددية الحديثة في لغة بايثون. لقد أثبتت الدراسات المقارنة والتحليلات المعيارية أن كائن ndarray يتفوق في كافة الجوانب المعمارية، بدءاً من كفاءة استهلاك الذاكرة وسرعة المعالجة الحسابية عبر BLAS/LAPACK، وصولاً إلى التوافقية الشاملة مع أطر تعلم الآلة مثل Scikit-Learn و PyTorch و Pandas.
يقدم هذا الدليل تلخيصاً تطبيقياً لأفضل الطرق البرمجية لتحويل المصفوفات وفق سيناريو الاستخدام المستهدف:
- استخدم الخاصية
.A1عندما تحتاج إلى تسطيح فوري وفائق السرعة لمصفوفة Matrix إلى متجه أحادي البعد(N,)بأقل استهلاك للذاكرة. - استخدم الخاصية
.Aعندما ترغب في تحويل فئة الكائن إلىndarrayمع الاحتفاظ الدقيق بالأبعاد الثنائية الأصلية(M, N). - استخدم التركيبة
np.asarray().ravel()للشيفرات البرمجية التي تتطلب وضوحاً دلالياً صريحاً مع دعم تخصيص ترتيب الذاكرة. - استخدم الدالة
.flatten()أوnp.array(..., copy=True)عندما يتطلب تطبيقك عزلاً تاماً للبيانات وحماية المصفوفة الأصلية من أي تعديلات لاحقة عبر النسخ العميق.
إن تبني هذه المعايير الحديثة والالتزام بكتابة شيفرات برمجية تعتمد كلياً على ndarray يضمن استدامة الأنظمة البرمجية، ويسهل صيانتها، ويطلق العنان للأداء الحسابي الفائق الذي تشتهر به لغة بايثون في الأوساط العلمية والصناعية.
References
- Harris, C. R., Millman, K. J., van der Walt, S. J., Gommers, R., Virtanen, P., Cournapeau, D., … & Oliphant, T. E. (2020). Array programming with NumPy. Nature, 585(7825), 357–362. https://doi.org/10.1038/s41586-020-2649-2
- NumPy Developers. (2023). NumPy Reference Documentation: numpy.matrix. NumPy.org. https://numpy.org/doc/stable/reference/generated/numpy.matrix.html
- Oliphant, T. E. (2006). A guide to NumPy (Vol. 1). Trelgol Publishing USA.
- Pedregosa, F., Varoquaux, G., Gramfort, A., Michel, V., Thirion, B., Grisel, O., … & Duchesnay, É. (2011). Scikit-learn: Machine learning in Python. Journal of Machine Learning Research, 12, 2825–2830. https://jmlr.org/papers/v12/pedregosa11a.html
- Smith, N. J. (2014). PEP 465 – A dedicated infix operator for matrix multiplication. Python Enhancement Proposals. https://peps.python.org/pep-0465/
- Van Rossum, G., Warsaw, B., & Coghlan, N. (2001). PEP 8 – Style Guide for Python Code. Python Enhancement Proposals. https://peps.python.org/pep-0008/
- Virtanen, P., Gommers, R., Oliphant, T. E., Haberland, M., Reddy, T., Cournapeau, D., … & SciPy 1.0 Contributors. (2020). SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3), 261–272. https://doi.org/10.1038/s41592-019-0686-2