In ganzen Arrays denken, nicht in Elementen
Vektorisierung heißt, eine Rechnung als Operationen auf ganzen Arrays zu schreiben und NumPy die Schleife in schnellem kompiliertem Code ausführen zu lassen. a + b addiert zwei Arrays elementweise, a * b multipliziert sie, a ** 2 quadriert jedes Element und np.sqrt(a) zieht jede Wurzel. Das sind universelle Funktionen (ufuncs). Das Ergebnis ist kürzer zu schreiben, besser lesbar und bei großen Daten oft fünfzigmal schneller als die entsprechende Python-for-Schleife, die bei jedem einzelnen Element den Interpreter-Aufwand zahlt.
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.]Renditen ohne Schleife: Verschieben mit Slices
Eine Rechnung, die jedes Element mit seinem Nachbarn vergleicht, braucht eine zweite Kopie des Arrays, um eins verschoben, und Slices liefern sie gratis. p[1:] ist der heutige Preis und p[:-1] der gestrige, beide mit Länge n - 1; die einfachen Renditen sind also p[1:] / p[:-1] - 1. Dasselbe Paar liefert Differenzen (p[1:] - p[:-1] oder np.diff(p)). Laufende Summen und Produkte sind np.cumsum und np.cumprod; ein Wachstumspfad ist np.cumprod(1 + returns). Vergleiche ganzer Arrays (r > 0) und np.where(bedingung, wenn_wahr, wenn_falsch) ersetzen das if in einer Schleife.
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. ]Aggregationen entlang einer Achse und Matrixprodukte
Funktionen wie sum, mean, std, min und max fassen ein Array zusammen. Bei einem 2-D-Array von Renditen (eine Zeile pro Tag, eine Spalte pro Wert) fasst axis=0 die Zeilen zusammen und liefert einen Wert pro Spalte (den Durchschnitt jedes Werts über die Zeit), axis=1 liefert einen Wert pro Zeile (den Tagesdurchschnitt über die Werte). Das Matrixprodukt R @ w eines (Tage, Werte)-Arrays mit einem Gewichtsvektor der Länge Werte ergibt die Portfoliorendite jedes Tages in einem Aufruf: das Skalarprodukt aus dem Vektorthema, für alle Zeilen auf einmal.
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]Gleitende Mittel mit cumsum und die Grenzen der Vektorisierung
Ein gleitender Durchschnitt über ein Fenster w scheint eine Schleife zu brauchen, mit einer laufenden Summe aber nicht: Setzen Sie eine 0 vor np.cumsum(x), und die Summe beliebiger w aufeinanderfolgender Werte ist eine Differenz zweier laufender Summen, cs[w:] - cs[:-w]; durch w teilen. Das Ergebnis hat len(x) - w + 1 Werte. Vektorisieren Sie die Arithmetik, nicht Logik, die vom vorherigen Ergebnis abhängt (etwa eine Position, die von der gestrigen Ausführung abhängt): Die bleibt in einer Schleife oder wandert später zu kompilierten Werkzeugen wie Numba. Achten Sie auch auf den Speicher, denn jedes Zwischenarray ist eine volle Kopie.
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(schnell, langsam) auf Zufallsdaten, bevor Sie sich darauf verlassen. Beachten Sie auch die Ränder: ein leeres Array, ein einzelnes Element, eine konstante Reihe (Standardabweichung null, ein z-Wert teilt also durch null) und nan-Werte verlangen eine Entscheidung, keinen Absturz und keine stillschweigend falsche Zahl.- Operationen auf ganzen Arrays (
a + b,a ** 2,np.sqrt(a)) ersetzen Schleifen; vergleichen Sie mitnp.where. - Verschobene Slices:
p[1:] / p[:-1] - 1;cumsumundcumprodfür laufende Summen und Wachstum. axis=0fasst Zeilen zusammen (pro Spalte),axis=1Spalten (pro Zeile);R @ wist ein Skalarprodukt pro Zeile.
Als Nächstes: Broadcasting, die Regel, mit der Arrays unterschiedlicher Form zusammenarbeiten.
Lab: Was bewertet wird
- Mit
simple_returnsalle Renditen auf einmal berechnen25 Pkt. - Mit
portfolio_returnsdie Portfoliorendite jedes Tages berechnen25 Pkt. - Mit
moving_averageeine Reihe ohne Schleife glätten25 Pkt. - Mit
zscoreWerte standardisieren25 Pkt.