0%
NumPy: الدليل الشامل للحوسبة العددية في Python

NumPy: الدليل الشامل للحوسبة العددية في Python

دليل شامل لإتقان NumPy: مصفوفات ndarray، والبث (broadcasting)، والعمليات المتجهة، والجبر الخطي في Python مع أمثلة عملية.

I

InSkillCoach

· دقيقة

NumPy: الدليل الشامل للحوسبة العددية في Python

تُعدّ NumPy (اختصار Numerical Python) المكتبة الأساسية للحوسبة العددية في Python. فهي توفّر كائنًا محوريًا هو المصفوفة متعددة الأبعاد ndarray، إلى جانب مجموعة واسعة من الدوال الرياضية المحسّنة المكتوبة بلغة C. ويعتمد على NumPy تقريبًا كامل المنظومة العلمية في Python (Pandas وSciPy وscikit-learn وMatplotlib). يقدّم لك هذا الدليل المفاهيم الأساسية للتعامل بكفاءة مع المصفوفات العددية: الإنشاء، والفهرسة، والبث (broadcasting)، والتوجيه (vectorisation)، والجبر الخطي.

لماذا NumPy بدلًا من قوائم Python؟

قوائم Python مرنة، لكنها بطيئة في الحوسبة العددية. فكل عنصر في القائمة هو كائن Python كامل له ترويسته الخاصة في الذاكرة، في حين تخزّن مصفوفة NumPy قيمها في كتلة ذاكرة متجاورة ومتجانسة. وبذلك تُسند العمليات إلى شيفرة C مُصرَّفة، مما يزيل كلفة المفسِّر (interpreter).

import numpy as np
import time

n = 10_000_000
liste = list(range(n))
tableau = np.arange(n)

# Multiplication par 2 avec une liste
debut = time.perf_counter()
resultat_liste = [x * 2 for x in liste]
print(f"Liste : {time.perf_counter() - debut:.3f} s")   # Liste : 0.520 s (ordre de grandeur)

# Multiplication par 2 avec NumPy
debut = time.perf_counter()
resultat_np = tableau * 2
print(f"NumPy : {time.perf_counter() - debut:.3f} s")   # NumPy : 0.015 s (ordre de grandeur)

يبلغ الكسب عادةً ما بين 10 و100 ضعف بحسب العملية.

المعيارقائمة Pythonمصفوفة NumPy
أنواع العناصرغير متجانسةمتجانسة (dtype واحد)
تخزين الذاكرةمؤشرات إلى كائنات متفرقةكتلة متجاورة مدمجة
العمليات عنصرًا بعنصرحلقة Python صريحةمتجهة في C
استهلاك الذاكرةمرتفعمنخفض
الأبعادتداخل يدويN بُعدًا بشكل أصلي

إنشاء المصفوفات

انطلاقًا من بيانات موجودة

import numpy as np

# Depuis une liste
a = np.array([1, 2, 3, 4])
print(a)          # [1 2 3 4]

# Tableau 2D depuis une liste de listes
m = np.array([[1, 2, 3], [4, 5, 6]])
print(m)
# [[1 2 3]
#  [4 5 6]]

دوال التوليد

# Tableau de zéros (3 lignes, 4 colonnes)
zeros = np.zeros((3, 4))

# Tableau de uns
uns = np.ones((2, 3))

# Tableau non initialisé (plus rapide, contenu arbitraire)
vide = np.empty((2, 2))

# Valeur constante
sept = np.full((2, 2), 7)

# Matrice identité
identite = np.eye(3)

# Séquence avec un pas (comme range, mais renvoie un ndarray)
seq = np.arange(0, 10, 2)
print(seq)        # [0 2 4 6 8]

# Nombre fixe de points également espacés entre deux bornes (bornes incluses)
lin = np.linspace(0, 1, 5)
print(lin)        # [0.   0.25 0.5  0.75 1.  ]

يُفضَّل استخدام linspace بدلًا من arange مع القيم العشرية: فعدد النقاط مضمون، بينما قد تُنتج الخطوة العشرية في arange مفاجآت ناتجة عن التقريب.

dtype وshape: بطاقة هوية المصفوفة

لكل مصفوفة نوع عناصر (dtype) وشكل (shape).

a = np.array([[1, 2, 3], [4, 5, 6]])

print(a.shape)    # (2, 3)  -> 2 lignes, 3 colonnes
print(a.ndim)     # 2       -> nombre de dimensions
print(a.size)     # 6       -> nombre total d'éléments
print(a.dtype)    # int64 (int32 sous Windows)

# Imposer un type à la création
b = np.array([1, 2, 3], dtype=np.float64)
print(b.dtype)    # float64

# Conversion de type (crée un nouveau tableau)
c = b.astype(np.int32)
print(c)          # [1 2 3]

# Changer la forme sans copier les données
d = np.arange(12).reshape(3, 4)
print(d.shape)    # (3, 4)

# Aplatir en 1D
print(d.ravel())  # [ 0  1  2  3  4  5  6  7  8  9 10 11]

احذر من تجاوز السعة: فالنوع int8 لا يستطيع حمل سوى قيم بين -128 و127. وفي علم البيانات يُعدّ كلٌّ من float64 وint64 الخيارين الافتراضيين الآمنين؛ لا تقلّل الدقة إلا إذا أصبحت الذاكرة قيدًا فعليًا.

الفهرسة والتقطيع (slicing)

تعمّم فهرسة NumPy فهرسة قوائم Python على المصفوفات متعددة الأبعاد.

a = np.arange(10)          # [0 1 2 3 4 5 6 7 8 9]

print(a[0])                # 0
print(a[-1])               # 9
print(a[2:7])              # [2 3 4 5 6]
print(a[::2])              # [0 2 4 6 8]
print(a[::-1])             # [9 8 7 6 5 4 3 2 1 0]

m = np.arange(12).reshape(3, 4)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

print(m[1, 2])             # 6      -> ligne 1, colonne 2
print(m[0])                # [0 1 2 3]  -> première ligne
print(m[:, 1])             # [1 5 9]    -> deuxième colonne
print(m[1:, 2:])           # [[ 6  7]
                           #  [10 11]]

نقطة مهمة: يُرجع تقطيع NumPy عرضًا (view) وليس نسخة. وتعديل العرض يعدّل المصفوفة الأصلية.

v = m[0, :]
v[0] = 99
print(m[0, 0])             # 99  -> le tableau original a été modifié

# Pour obtenir une copie indépendante
c = m[0, :].copy()

الأقنعة المنطقية (Masques booléens)

يُعدّ الترشيح بالشروط من أقوى أساليب NumPy.

notes = np.array([12, 8, 15, 17, 9, 14])

masque = notes >= 10
print(masque)              # [ True False  True  True False  True]

# Sélection des éléments qui vérifient la condition
print(notes[masque])       # [12 15 17 14]

# Conditions combinées : & (et), | (ou), ~ (non), avec parenthèses obligatoires
print(notes[(notes >= 10) & (notes < 16)])   # [12 15 14]

# Modification conditionnelle
notes[notes < 10] = 10
print(notes)               # [12 10 15 17 10 14]

# np.where : choisir entre deux valeurs selon une condition
mention = np.where(notes >= 14, "admis avec mention", "admis")
print(mention)
# ['admis' 'admis' 'admis avec mention' 'admis avec mention' 'admis' 'admis avec mention']

شرح البث (broadcasting) خطوة بخطوة

البث هو الآلية التي تتيح لـ NumPy الجمع بين مصفوفات ذات أشكال مختلفة دون نسخ البيانات. وقواعده كما يلي:

  1. تُحاذى الأشكال من اليمين.
  2. يكون البعدان متوافقين إذا كانا متساويين، أو إذا كان أحدهما يساوي 1.
  3. يُمدَّد البعد ذو الحجم 1 افتراضيًا ليطابق البعد الآخر.

الحالة 1: مصفوفة وقيمة عددية

a = np.array([1, 2, 3])
print(a + 10)              # [11 12 13]
# Le scalaire 10 est traité comme un tableau de forme (1,) étiré en (3,)

الحالة 2: مصفوفة ومتجه صفّي

m = np.arange(6).reshape(2, 3)     # forme (2, 3)
v = np.array([10, 20, 30])         # forme (3,)

# Alignement par la droite : (2, 3) et (   3) -> compatible
print(m + v)
# [[10 21 32]
#  [13 24 35]]
# v est ajouté à chaque ligne de m

الحالة 3: متجه عمودي ومتجه صفّي

col = np.array([[1], [2], [3]])    # forme (3, 1)
lig = np.array([10, 20, 30])       # forme (3,)

# (3, 1) et (3,) -> alignées en (3, 1) et (1, 3) -> résultat (3, 3)
print(col + lig)
# [[11 21 31]
#  [12 22 32]
#  [13 23 33]]

حالة عدم التوافق

a = np.ones((2, 3))
b = np.ones((2, 2))
# a + b  -> ValueError: operands could not be broadcast together
# (2, 3) et (2, 2) : la dernière dimension diffère et aucune ne vaut 1

لتمركز أعمدة مصفوفة حول متوسطها، يجعل البث الشيفرة موجزة جدًا:

donnees = np.array([[1.0, 200.0], [2.0, 300.0], [3.0, 400.0]])
centrees = donnees - donnees.mean(axis=0)
print(centrees)
# [[-1. -100.]
#  [ 0.    0.]
#  [ 1.  100.]]

العمليات المتجهة والدوال الشاملة

تُطبَّق الدوال الشاملة (ufuncs) عنصرًا بعنصر دون حلقة Python.

a = np.array([1.0, 4.0, 9.0])

print(np.sqrt(a))          # [1. 2. 3.]
print(np.exp(a))           # [2.71828183e+00 5.45981500e+01 8.10308393e+03]
print(np.log(a))           # [0.         1.38629436 2.19722458]

angles = np.linspace(0, np.pi, 3)
print(np.sin(angles))      # [0.0000000e+00 1.0000000e+00 1.2246468e-16]

# Opérateurs arithmétiques : tous vectorisés
b = np.array([10.0, 20.0, 30.0])
print(a + b)               # [11. 24. 39.]
print(a * b)               # [ 10.  80. 270.]  -> produit élément par élément
print(b / a)               # [10.  5.  3.33333333]
print(a ** 2)              # [ 1. 16. 81.]

# Comparaisons vectorisées
print(a > 3)               # [False  True  True]

# Arrondis
x = np.array([1.234, 5.678])
print(np.round(x, 1))      # [1.2 5.7]
print(np.floor(x))         # [1. 5.]
print(np.ceil(x))          # [2. 6.]

القاعدة الذهبية: إذا كتبتَ حلقة for على عناصر مصفوفة NumPy، فهناك دائمًا تقريبًا صياغة متجهة أسرع وأوضح.

التجميعات: sum وmean والمعامل axis

m = np.array([[1, 2, 3],
              [4, 5, 6]])

# Agrégation globale
print(m.sum())             # 21
print(m.mean())            # 3.5
print(m.min(), m.max())    # 1 6
print(m.std())             # 1.707825127659933

# axis=0 : on réduit le long des lignes -> un résultat par colonne
print(m.sum(axis=0))       # [5 7 9]

# axis=1 : on réduit le long des colonnes -> un résultat par ligne
print(m.sum(axis=1))       # [ 6 15]

# Indice du maximum
print(m.argmax())          # 5 (indice dans le tableau aplati)
print(m.argmax(axis=0))    # [1 1 1]

# Sommes cumulées
print(np.cumsum([1, 2, 3, 4]))   # [ 1  3  6 10]

قاعدة تساعد على التذكّر: يشير axis إلى البُعد الذي يختفي. فمع axis=0 على شكل (2, 3) يكون شكل الناتج (3,).

الجبر الخطي

A = np.array([[1, 2],
              [3, 4]])
B = np.array([[5, 6],
              [7, 8]])

# Produit matriciel : opérateur @ (recommandé) ou np.dot
print(A @ B)
# [[19 22]
#  [43 50]]

print(np.dot(A, B))        # résultat identique

# Attention : * est le produit élément par élément, pas matriciel
print(A * B)
# [[ 5 12]
#  [21 32]]

# Transposée
print(A.T)
# [[1 3]
#  [2 4]]

# Déterminant et inverse
print(np.linalg.det(A))    # -2.0000000000000004
print(np.linalg.inv(A))
# [[-2.   1. ]
#  [ 1.5 -0.5]]

# Résolution du système linéaire A x = b (préférable à inv(A) @ b)
b = np.array([5, 11])
x = np.linalg.solve(A, b)
print(x)                   # [1. 2.]

# Valeurs propres et vecteurs propres
valeurs, vecteurs = np.linalg.eig(A)
print(valeurs)             # [-0.37228132  5.37228132]

# Norme d'un vecteur
v = np.array([3.0, 4.0])
print(np.linalg.norm(v))   # 5.0

توليد الأعداد العشوائية باستخدام default_rng

منذ NumPy 1.17، تعتمد الواجهة الموصى بها على مولّد صريح يُنشأ بواسطة np.random.default_rng، وهو أسرع وأفضل تصميمًا من الدوال القديمة مثل np.random.rand وما شابهها.

# Générateur avec graine pour des résultats reproductibles
rng = np.random.default_rng(seed=42)

# Flottants uniformes dans [0, 1)
print(rng.random(3))              # [0.77395605 0.43887844 0.85859792]

# Entiers uniformes dans [0, 10)
print(rng.integers(0, 10, size=5))    # [8 6 5 2 3]

# Loi normale (moyenne 0, écart-type 1)
print(rng.normal(loc=0, scale=1, size=3))   # [ 0.7611397  -0.86157352  0.36371758]

# Tirage sans remise dans un ensemble
print(rng.choice([10, 20, 30, 40], size=2, replace=False))   # [20 40]

# Mélange en place
a = np.arange(5)
rng.shuffle(a)
print(a)                          # [4 0 3 1 2] (par exemple)

قراءة المصفوفات وكتابتها

a = np.arange(12).reshape(3, 4)

# Format binaire natif .npy : rapide, préserve dtype et shape
np.save("tableau.npy", a)
b = np.load("tableau.npy")
print(np.array_equal(a, b))       # True

# Plusieurs tableaux dans une archive .npz
np.savez("archive.npz", donnees=a, labels=np.array([0, 1, 2]))
archive = np.load("archive.npz")
print(archive["labels"])          # [0 1 2]

# Format texte : lisible mais plus lent et moins précis
np.savetxt("tableau.csv", a, delimiter=",", fmt="%d")
c = np.loadtxt("tableau.csv", delimiter=",")
print(c.dtype)                    # float64 (loadtxt lit en float par défaut)

بالنسبة لملفات CSV ذات الترويسات أو الأعمدة غير المتجانسة أو القيم المفقودة، يُفضَّل استخدام pandas.read_csv فهو أكثر متانة بكثير من loadtxt.

أفضل الممارسات

  • استخدم التوجيه (vectorisation) دائمًا: استبدل حلقات for بعمليات على مصفوفات كاملة؛ فهذا أسرع وأوضح.
  • احذر من العروض (views): تتشارك الشرائح ذاكرة المصفوفة الأصلية؛ استخدم .copy() عندما تحتاج إلى مصفوفة مستقلة.
  • ثبّت البذرة العشوائية باستخدام np.random.default_rng(seed) لجعل تجاربك قابلة لإعادة الإنتاج.
  • استخدم @ للضرب المصفوفي واحتفظ بـ * للضرب عنصرًا بعنصر لتفادي الالتباس.
  • فضّل np.linalg.solve على عكس المصفوفة لحل نظام خطي: فهو أكثر استقرارًا من الناحية العددية.
  • تحقّق من shape وdtype كلما فاجأتك نتيجة: فمعظم أخطاء NumPy تأتي من بث غير مقصود أو من نوع غير متوقع.
  • اختر صيغة .npy/.npz لحفظ المصفوفات بين جلسات Python، واستخدم CSV فقط للتبادل مع أدوات أخرى.

الخاتمة

تمثّل NumPy الأساس الذي يقوم عليه كل تحليل البيانات في Python. وبإتقان ndarray والفهرسة والأقنعة المنطقية والبث والعمليات المتجهة، تمتلك الأدوات اللازمة لكتابة شيفرة عددية موجزة وعالية الأداء في آن واحد. وتظهر هذه المفاهيم مباشرة في Pandas (التي تكون أعمدتها مصفوفات NumPy)، وفي SciPy (التي توسّع NumPy نحو الحوسبة العلمية المتقدمة)، وفي مكتبات تعلّم الآلة. والخطوة الطبيعية التالية هي استكشاف Pandas للتعامل مع البيانات الجدولية، ثم Matplotlib للتصوير البياني.

InSkillCoach

نبذة عن InSkillCoach

خبير في التدريب والتقنيات

مدرّب متخصص في التقنيات المتقدمة والذكاء الاصطناعي، تدعمه شركة GNeurone Inc.

الشهادات:

  • AWS Certified Solutions Architect – Professional
  • شهادات Google Cloud
  • Microsoft Certified: DevOps Engineer Expert
  • Certified Kubernetes Administrator (CKA)
  • CompTIA Security+
1.5k
176

التعليقات

التعليقات مدعومة عبر GitHub Discussions

سجّل الدخول باستخدام GitHub للمشاركة في النقاش

تم نسخ الرابط!