Des tableaux de formes différentes qui travaillent ensemble
Jusqu'ici nous combinions des tableaux de même forme. Le broadcasting est l'ensemble des règles qui permet à NumPy de combiner des tableaux de formes différentes sans copier de données. Le cas le plus simple est un scalaire : a + 10 ajoute 10 à chaque élément, comme si 10 avait été étiré à la forme de a. Le même étirement fonctionne entre tableaux : un vecteur de trois nombres peut s'ajouter à chaque ligne d'une matrice de trois colonnes, une valeur par colonne. C'est ainsi que l'on retranche la moyenne de chaque colonne, que l'on met chaque actif à l'échelle de sa volatilité ou que l'on construit un tableau de toutes les différences deux à deux sans une seule boucle.
import numpy as np
a = np.array([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]]) # shape (2, 3)
print(a + 10) # a scalar is stretched to every element
# [[11. 12. 13.]
# [14. 15. 16.]]
per_column = np.array([1, 10, 100]) # shape (3,): one value per column
print(a * per_column)
# [[ 1. 20. 300.]
# [ 4. 50. 600.]]
print(a - a.mean(axis=0)) # subtract each column's mean
# [[-1.5 -1.5 -1.5]
# [ 1.5 1.5 1.5]]La règle : comparer les formes à partir de la droite
NumPy aligne les deux formes à partir de la droite. À chaque position, les deux tailles doivent être égales, ou l'une doit valoir 1 (elle est alors étirée), ou l'un des tableaux n'a simplement pas de dimension à cet endroit (traitée comme 1). Les formes (2, 3) et (3,) conviennent : 3 rencontre 3, et la dimension manquante est étirée sur les 2 lignes. Les formes (2, 3) et (2,) échouent, car 3 rencontre 2 : NumPy lève ValueError: operands could not be broadcast together. La forme du résultat prend la plus grande taille à chaque position.
import numpy as np
a = np.ones((2, 3))
print((a + np.ones(3)).shape) # (2, 3) 3 meets 3
try:
a + np.ones(2) # 3 meets 2: no match
except ValueError as error:
print(error) # operands could not be broadcast together with shapes (2,3) (2,)
# Size 1 stretches: (3, 1) with (1, 4) gives (3, 4)
print((np.ones((3, 1)) + np.ones((1, 4))).shape) # (3, 4)Vecteurs colonnes et tableaux de paires
Un tableau 1-D de forme (n,) s'aligne toujours sur le dernier axe : il se comporte comme une ligne. Pour appliquer une valeur par ligne, il faut une colonne de forme (n, 1) : écrivez x[:, np.newaxis] ou x.reshape(-1, 1). Combiner une colonne et une ligne construit un tableau entier : x[:, None] - x[None, :] est la matrice n×n de toutes les différences x[i] - x[j], et corr * vols[:, None] * vols[None, :] transforme une matrice de corrélation en matrice de covariance. Attention à l'erreur inverse : additionner des formes (3,) et (3, 1) donne silencieusement un tableau 3×3, pas un vecteur de trois nombres.
import numpy as np
x = np.array([1.0, 2.0, 4.0])
print(x[:, np.newaxis].shape, x.reshape(-1, 1).shape, x[None, :].shape) # (3, 1) (3, 1) (1, 3)
# All pairwise differences x[i] - x[j]
print(x[:, None] - x[None, :])
# [[ 0. -1. -3.]
# [ 1. 0. -2.]
# [ 3. 2. 0.]]
# One factor per row needs a column
a = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
print(a * np.array([1.0, 2.0])[:, None])
# [[ 1. 2. 3.]
# [ 8. 10. 12.]]
# Correlation matrix to covariance matrix
corr = np.array([[1.0, 0.5], [0.5, 1.0]])
vols = np.array([0.2, 0.3])
print(corr * vols[:, None] * vols[None, :])
# [[0.04 0.03]
# [0.03 0.09]]
print((np.ones(3) + np.ones((3, 1))).shape) # (3, 3) a silent table, not a vectorimport numpy as np
# Standardise every column at once: (x - mean) / std, column by column
a = np.array([[1.0, 10.0],
[2.0, 20.0],
[3.0, 30.0]])
z = (a - a.mean(axis=0)) / a.std(axis=0) # (3, 2) - (2,) -> (3, 2), then divided by (2,)
print(z.round(3).tolist()) # [[-1.225, -1.225], [0.0, 0.0], [1.225, 1.225]]
print(z.mean(axis=0).round(9).tolist()) # [0.0, 0.0]
# keepdims keeps the reduced axis, so the result can be broadcast back by row too
row_share = a / a.sum(axis=1, keepdims=True)
print(row_share.sum(axis=1).tolist()) # [1.0, 1.0, 1.0]result.shape. Un résultat (n, n) là où l'on attendait (n,), ou un tableau de milliards d'entrées à partir de deux longs vecteurs, est le signe classique d'un [:, None] oublié ou en trop. Le résultat est construit en mémoire : deux vecteurs de 100 000 valeurs font un tableau de 10 milliards d'entrées.- Les formes s'alignent à partir de la droite : les tailles doivent être égales ou 1 (ou absentes) ; le résultat prend la plus grande.
(n,)se comporte comme une ligne ; faites une colonne avecx[:, None]oux.reshape(-1, 1).- Colonne avec ligne = un tableau :
x[:, None] - x[None, :]; vérifiezresult.shape, les écarts échouent avecValueError.
À suivre : les Series pandas, des tableaux à une dimension qui portent une étiquette avec chaque valeur.
Lab : ce qui est noté
- Centrer chaque colonne avec
demean_columns25 pts - Donner à chaque ligne son propre facteur avec
scale_rows25 pts - Construire le tableau de toutes les différences avec
pairwise_differences25 pts - Transformer des corrélations en covariances avec
covariance_from_correlation25 pts