Complément - niveau avancé¶
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
plt.ion()<contextlib.ExitStack at 0x7f994076d430>Pour finir notre introduction à numpy, nous allons survoler à très grande vitesse quelques traits plus annexes mais qui peuvent être utiles. Je vous laisse approfondir de votre côté les parties qui vous intéressent.
Utilisation de la mémoire¶
Références croisées, vues, shallow et deep copies¶
Pour résumer ce qu’on a vu jusqu’ici :
un tableau
numpyest un objet mutable ;une slice sur un tableau retourne une vue, on est donc dans le cas d’une référence partagée ;
dans tous les cas que l’on a vus jusqu’ici, comme les cases des tableaux sont des objets atomiques, il n’y a pas de différence entre shallow et deep copie ;
pour créer une copie, utilisez
np.copy().
Et de plus :
# un tableau de base
a = np.arange(3)# une vue
v = a.view()# une slice
s = a[:]Les deux objets ne sont pas différentiables :
v.base is aTrues.base is aTrueL’option out=¶
Lorsque l’on fait du calcul vectoriel, on peut avoir tendance à créer de nombreux tableaux intermédiaires qui coûtent cher en mémoire. Pour cette raison, presque tous les opérateurs numpy proposent un paramètre optionnel out= qui permet de spécifier un tableau déjà alloué, dans lequel ranger le résultat.
Prenons l’exemple un peu factice suivant, ou on calcule sur l’intervalle :
# le domaine
X = np.linspace(0, 2*np.pi)Y = np.exp(np.sin(np.cos(X)))
plt.plot(X, Y);
# chaque fonction alloue un tableau pour ranger ses résultats,
# et si je décompose, ce qui se passe en fait c'est ceci
Y1 = np.cos(X)
Y2 = np.sin(Y1)
Y3 = np.exp(Y2)
# en tout en comptant X et Y j'aurai créé 4 tableaux
plt.plot(X, Y3);
# Mais moi je sais qu'en fait je n'ai besoin que de X et de Y
# ce qui fait que je peux optimiser comme ceci :
# je ne peux pas récrire sur X parce que j'en aurai besoin pour le plot
X1 = np.cos(X)
# par contre ici je peux recycler X1 sans souci
np.sin(X1, out=X1)
# etc ...
np.exp(X1, out=X1)
plt.plot(X, X1);
Et avec cette approche je n’ai créé que 2 tableaux en tout.
Notez bien : je ne vous recommande pas d’utiliser ceci systématiquement, car ça défigure nettement le code. Mais il faut savoir que ça existe, et savoir y penser lorsque la création de tableaux intermédiaires a un coût important dans l’algorithme.
np.add et similaires¶
Si vous vous mettez à optimiser de cette façon, vous utiliserez par exemple np.add plutôt que +, qui ne vous permet pas de choisir la destination du résultat.
Types structurés pour les cellules¶
Sans transition, jusqu’ici on a vu des tableaux atomiques, où chaque cellule est en gros un seul nombre.
En fait, on peut aussi se définir des types structurés, c’est-à-dire que chaque cellule contient l’équivalent d’un struct en C.
Pour cela, on peut se définir un dtype élaboré, qui va nous permettre de définir la structure de chacun de ces enregistrements.
Exemple¶
# un dtype structuré
my_dtype = [
# prenom est un string de taille 12
('prenom', '|S12'),
# nom est un string de taille 15
('nom', '|S15'),
# age est un entier
('age', int)
]
# un tableau qui contient des cellules de ce type
classe = np.array(
# le contenu
[ ( 'Jean', 'Dupont', 32),
( 'Daniel', 'Durand', 18),
( 'Joseph', 'Delapierre', 54),
( 'Paul', 'Girard', 20)],
# le type
dtype = my_dtype)
classearray([(b'Jean', b'Dupont', 32), (b'Daniel', b'Durand', 18),
(b'Joseph', b'Delapierre', 54), (b'Paul', b'Girard', 20)],
dtype=[('prenom', 'S12'), ('nom', 'S15'), ('age', '<i8')])Je peux avoir l’impression d’avoir créé un tableau de 4 lignes et 3 colonnes ; cependant pour numpy ce n’est pas comme ça que cela se présente :
classe.shape(4,)Rien ne m’empêcherait de créer des tableaux de ce genre en dimensions supérieures, bien entendu :
# ça n'a pas beaucoup d'intérêt ici, mais si on en a besoin
# on peut bien sûr avoir plusieurs dimensions
classe.reshape((2, 2))array([[(b'Jean', b'Dupont', 32), (b'Daniel', b'Durand', 18)],
[(b'Joseph', b'Delapierre', 54), (b'Paul', b'Girard', 20)]],
dtype=[('prenom', 'S12'), ('nom', 'S15'), ('age', '<i8')])Comment définir dtype ?¶
Il existe une grande variété de moyens pour se définir son propre dtype.
Je vous signale notamment la possibilité de spécifier à l’intérieur d’un dtype des cellules de type object, qui est l’équivalent d’une référence Python (approximativement, un pointeur dans un struct C) ; c’est un trait qui est utilisé par pandas que nous allons voir très bientôt.
Pour la définition de types structurés, voir la documentation complète ici.
Assemblages et découpages¶
Enfin, toujours sans transition, et plus anecdotique : jusqu’ici nous avons vu des fonctions qui préservent la taille. Le stacking permet de créer un tableau plus grand en (juxta/super)posant plusieurs tableaux. Voici rapidement quelques fonctions qui permettent de faire des tableaux plus petits ou plus grands.
Assemblages : hstack et vstack (tableaux 2D)¶
a = np.arange(1, 7).reshape(2, 3)
print(a)[[1 2 3]
[4 5 6]]
b = 10 * np.arange(1, 7).reshape(2, 3)
print(b)[[10 20 30]
[40 50 60]]
print(np.hstack((a, b)))[[ 1 2 3 10 20 30]
[ 4 5 6 40 50 60]]
print(np.vstack((a, b)))[[ 1 2 3]
[ 4 5 6]
[10 20 30]
[40 50 60]]
Assemblages : np.concatenate (3D et au delà)¶
a = np.ones((2, 3, 4))
print(a)[[[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]]
[[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]]]
b = np.zeros((2, 3, 2))
print(b)[[[0. 0.]
[0. 0.]
[0. 0.]]
[[0. 0.]
[0. 0.]
[0. 0.]]]
print(np.concatenate((a, b), axis = 2))[[[1. 1. 1. 1. 0. 0.]
[1. 1. 1. 1. 0. 0.]
[1. 1. 1. 1. 0. 0.]]
[[1. 1. 1. 1. 0. 0.]
[1. 1. 1. 1. 0. 0.]
[1. 1. 1. 1. 0. 0.]]]
Pour conclure :
hstacketvstackutiles sur des tableaux 2D ;au-delà, préférez
concatenatequi a une sémantique plus claire.
Répétitions : np.tile¶
Cette fonction permet de répéter un tableau dans toutes les directions :
motif = np.array([[0, 1], [2, 10]])
print(motif)[[ 0 1]
[ 2 10]]
print(np.tile(motif, (2, 3)))[[ 0 1 0 1 0 1]
[ 2 10 2 10 2 10]
[ 0 1 0 1 0 1]
[ 2 10 2 10 2 10]]
Découpage : np.split¶
Cette opération, inverse du stacking, consiste à découper un tableau en parties plus ou moins égales :
complet = np.arange(24).reshape(4, 6); print(complet)[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]
[12 13 14 15 16 17]
[18 19 20 21 22 23]]
h1, h2 = np.hsplit(complet, 2)
print(h1)[[ 0 1 2]
[ 6 7 8]
[12 13 14]
[18 19 20]]
print(h2)[[ 3 4 5]
[ 9 10 11]
[15 16 17]
[21 22 23]]
complet = np.arange(24).reshape(4, 6)
print(complet)[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]
[12 13 14 15 16 17]
[18 19 20 21 22 23]]
v1, v2 = np.vsplit(complet, 2)
print(v1)[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]]
print(v2)[[12 13 14 15 16 17]
[18 19 20 21 22 23]]