فكّر بالمصفوفات الكاملة لا بالعناصر
يعني التوجيه (vectorisation) كتابة الحساب كعمليات على مصفوفات كاملة وترك NumPy تشغّل الحلقة بكود مُترجَم سريع. فـa + b تجمع مصفوفتين عنصرًا عنصرًا وa * b تضربهما وa ** 2 تربّع كل عنصر وnp.sqrt(a) تأخذ كل جذر تربيعي. وهذه دوال شاملة (ufuncs). والنتيجة أقصر كتابةً وأسهل قراءةً، وهي في البيانات الكبيرة أسرع غالبًا بخمسين ضعفًا من حلقة for المكافئة في بايثون التي تدفع كلفة المفسّر عند كل عنصر.
import numpy as np
a = np.array([1.0, 2.0, 3.0])
b = np.array([10.0, 20.0, 30.0])
print(a + b) # [11. 22. 33.]
print(a * b) # [10. 40. 90.]
print(a ** 2) # [1. 4. 9.]
print(np.sqrt(a)) # [1. 1.41421356 1.73205081]
# The loop version of a * b, which vectorisation replaces
result = np.empty(3)
for i in range(3):
result[i] = a[i] * b[i]
print(result) # [10. 40. 90.]عوائد بلا حلقة: الإزاحة بالشرائح
يحتاج الحساب الذي يقارن كل عنصر بجاره إلى نسخة ثانية من المصفوفة مزاحة بمقدار واحد، وتعطيها الشرائح مجانًا. فـp[1:] سعر اليوم وp[:-1] سعر الأمس، وطول كل منهما n - 1، فتكون العوائد البسيطة p[1:] / p[:-1] - 1. والزوج نفسه يعطي الفروق (p[1:] - p[:-1] أو np.diff(p)). والمجاميع والجداءات التراكمية هي np.cumsum وnp.cumprod؛ ومسار النمو هو np.cumprod(1 + returns). وتحلّ مقارنات المصفوفات الكاملة (r > 0) وnp.where(شرط, إن_صح, إن_لم) محل if داخل حلقة.
import numpy as np
p = np.array([100.0, 102.0, 101.0, 103.0])
returns = p[1:] / p[:-1] - 1 # today / yesterday - 1, no loop
print(np.round(returns, 4)) # [ 0.02 -0.0098 0.0198]
print(np.diff(p)) # [ 2. -1. 2.]
print(np.cumprod(1 + np.array([0.01, 0.02]))) # [1.01 1.0302]
# np.where replaces if/else inside a loop
r = np.array([0.01, -0.02, 0.03, -0.01])
print(np.where(r > 0, "up", "down")) # ['up' 'down' 'up' 'down']
print(np.where(r > 0, r, 0.0)) # [0.01 0. 0.03 0. ]التجميع على طول محور وضرب المصفوفات
تختزل دوال مثل sum وmean وstd وmin وmax المصفوفة. ومع مصفوفة ثنائية الأبعاد من العوائد (صف لكل يوم وعمود لكل أصل) يختزل axis=0 الصفوف ويعطي قيمة لكل عمود (متوسط كل أصل عبر الزمن)، بينما يعطي axis=1 قيمة لكل صف (متوسط كل يوم عبر الأصول). وحاصل الضرب المصفوفي R @ w لمصفوفة (أيام, أصول) في متجه أوزان طوله أصول يعطي عائد المحفظة لكل يوم في استدعاء واحد: وهو الجداء النقطي من موضوع المتجهات منفّذًا على كل الصفوف دفعة واحدة.
import numpy as np
# 2 days x 3 assets
R = np.array([[0.02, -0.01, 0.03],
[0.01, 0.00, 0.02]])
w = np.array([0.5, 0.3, 0.2])
print(R.shape) # (2, 3)
print(R.mean(axis=0)) # [ 0.015 -0.005 0.025] one value per asset
print(R.sum(axis=1)) # [0.04 0.03] one value per day
print(R @ w) # [0.013 0.009] portfolio return each day
# Standardise: how many standard deviations from the mean?
z = np.array([1.0, 2.0, 3.0, 4.0])
print((z - z.mean()) / z.std()) # [-1.34164079 -0.4472136 0.4472136 1.34164079]المتوسطات المتحركة بـ cumsum وحدود التوجيه
يبدو المتوسط المتحرك على نافذة w محتاجًا إلى حلقة، لكنه مع مجموع تراكمي لا يحتاج: ضع 0 أمام np.cumsum(x) فيصبح مجموع أي w قيمة متتالية فرقًا بين مجموعين تراكميين، cs[w:] - cs[:-w]؛ ثم اقسم على w. وللنتيجة len(x) - w + 1 قيمة. وجّه الحساب لا المنطق الذي يعتمد على النتيجة السابقة (كمركز يعتمد على تنفيذ الأمس مثلًا): فهذا يبقى في حلقة أو ينتقل لاحقًا إلى أدوات مُترجَمة مثل Numba. وراقب الذاكرة أيضًا لأن كل مصفوفة وسيطة نسخة كاملة.
import numpy as np
x = np.array([1.0, 2.0, 4.0, 8.0, 16.0])
w = 3
cs = np.concatenate(([0.0], np.cumsum(x))) # [0, 1, 3, 7, 15, 31]
print((cs[w:] - cs[:-w]) / w) # [2.33333333 4.66666667 9.33333333]
# Check against the plain definition
print([x[i:i + w].mean() for i in range(len(x) - w + 1)]) # three values, the samenp.allclose(السريعة, البطيئة) على بيانات عشوائية قبل الاعتماد عليها. واعنَ بالحواف أيضًا: مصفوفة فارغة أو عنصر واحد أو سلسلة ثابتة (انحراف معياري صفر فتقسم الدرجة المعيارية على صفر) وقيم nan تستحق قرارًا لا انهيارًا ولا رقمًا خاطئًا صامتًا.- عمليات المصفوفات الكاملة (
a + bوa ** 2وnp.sqrt(a)) تحل محل الحلقات؛ وقارن بـnp.where. - شرائح مزاحة:
p[1:] / p[:-1] - 1؛ وcumsumوcumprodللمجاميع التراكمية والنمو. axis=0يختزل الصفوف (لكل عمود) وaxis=1يختزل الأعمدة (لكل صف)؛ وR @ wجداء نقطي لكل صف.
التالي: البث (broadcasting)، القاعدة التي تتيح لمصفوفات مختلفة الشكل أن تعمل معًا.
المختبر: ما الذي يُقيَّم
- احسب كل العوائد دفعة واحدة باستخدام
simple_returns25 نقطة - احصل على عائد المحفظة لكل يوم باستخدام
portfolio_returns25 نقطة - نعِّم سلسلة بلا حلقة باستخدام
moving_average25 نقطة - قيِّس القيم معياريًا باستخدام
zscore25 نقطة