6.11. عمليات الاختزال

تقوم عملية الاختزال بطي مصفوفة على طول محور واحد أو أكثر عن طريق الجمع أو حساب المتوسط أو أخذ القيمة الدنيا، وما إلى ذلك. كل عملية اختزال هي استدعاء واحد للمكتبة على المصفوفة بأكملها، وهي أسرع بكثير من حلقة Python المكافئة. تغطي numpy العمليات الشائعة اليومية:

  • sum() -- مجموع كل عنصر

  • mean() -- المتوسط الحسابي (المجموع مقسومًا على عدد العناصر)

  • std() -- الانحراف المعياري، حيث يضبط ddof= المقسوم عليه (N - ddof)

  • min() / max() -- أصغر وأكبر عنصر

  • median() -- القيمة الوسطى عند ترتيب العناصر (المئين الخمسين)

  • argmin() / argmax() -- فهرس العنصر الأدنى أو الأعلى

  • all() / any() -- عمليات اختزال على القيم المنطقية لمصفوفات بوليانية

6.11.1. بدون الكلمة المفتاحية axis

عند الاستدعاء بدون axis=، تُرجع عملية الاختزال قيمة عددية تغطي المصفوفة بأكملها:

a = np.array([1, 2, 3, 4], dtype=np.float)
np.sum(a)           # 10.0
np.mean(a)          # 2.5
np.std(a)           # 1.118...
np.median(a)        # 2.5

b = np.array([40, 10, 30, 20], dtype=np.float)
np.max(b)           # 40.0
np.argmax(b)        # 0  (index of the maximum)

6.11.2. مع الكلمة المفتاحية axis

تقلّص axis= محورًا واحدًا محددًا وتترك المحاور الأخرى سليمة. النتيجة هي مصفوفة برتبة أقل بدرجة واحدة من المدخلات:

m = np.arange(12, dtype=np.float).reshape((3, 4))

np.sum(m)               # 66.0          - scalar
np.sum(m, axis=0)       # length-4      - column sums
np.sum(m, axis=1)       # length-3      - row sums

تنطبق قاعدة الشكل ذاتها على كل عملية اختزال: تطوي axis=0 المحور الأول، وتطوي axis=1 المحور الثاني، وهكذا. فالمتوسط / الانحراف المعياري على طول صف، على سبيل المثال، يُكتبان np.mean(m, axis=1) و np.std(m, axis=1). وتكون للنتيجة طول المحور الآخر.

تُبقي الكلمة المفتاحية keepdims=True المحور المقلَّص في مكانه بطول 1 بدلًا من إسقاطه. ويظهر الفرق عندما تحتاج النتيجة المختزَلة إلى البث مرة أخرى مقابل الأصل: إذ تحافظ keepdims على الرتبة، مما يُبقي قواعد البث متوافقة محورًا بمحور.

إن طرح متوسط كل صف من ذلك الصف هو الاستخدام النموذجي:

m = np.arange(12, dtype=np.float).reshape((3, 4))
row_means = np.mean(m, axis=1, keepdims=True)
# row_means has shape (3, 1)
centred = m - row_means
# (3, 4) - (3, 1) -> (3, 4), each row centred on its own mean

بدون keepdims، تُرجع np.mean(m, axis=1) نتيجة أحادية البُعد بشكل (3,). فعند بث (3, 4) - (3,) تصطف (3,) على هيئة (1, 3) بعد إضافة الرتبة المسبقة، وهذا غير متوافق مع (3, 4): إذ تتعارض المحاور الأخيرة (4 مقابل 3) وليس أيٌّ منها يساوي 1، لذا تُطلق numpy الاستثناء ValueError. والكلمة المفتاحية keepdims=True هي ما يُبقي عملية الطرح صالحة.

6.11.3. التخطيط مهم

بالاقتران مع تخطيط الصفوف الكبرى (row-major) المشروح في الشكل والخطوات (strides)، يكون الاختزال على طول المحور الأخير هو الحالة الأقل تكلفة. إذ تجتاز عملية الاختزال كتلة البيانات في الاتجاه الذي خُزّنت به، دون أي قفزات من صف إلى صف:

m = np.arange(2000, dtype=np.float).reshape((2, 1000))
np.sum(m, axis=1)       # cheap - long axis is the inner one
np.sum(m, axis=0)       # has to jump rows on every step

عندما يكون لدى التطبيق خيار حول كيفية تخطيط المخزن المؤقت، ضع المحور الطويل في النهاية كي تجري عمليات الاختزال على طوله في الاتجاه السريع.

6.11.4. العناصر القابلة للتكرار كمدخلات

تقبل معظم عمليات الاختزال عنصرًا قابلًا للتكرار في Python (list أو range أو tuple) بدلًا من ndarray. وتكلّف هذه الراحة بضع ميكروثوانٍ للتحويل الضمني -- وهو ما يتراكم بسرعة في الحلقات. عندما تُختزل البيانات نفسها عدة مرات، أنشئ ndarray مرة واحدة ومرّرها.