Les manipulations d'un tableau comprennent :
- la réorganisation du tableau (réindexation)
- l'aggrégation de 2 tableaux ou plus
- le découpage d'un tableau en 2 ou plus
Avant de regarder ces points, regardons comment Numpy présente les dimensions d'un tableau multidimensionnel avec la notion d'axes.
import numpy as np
# An array of marks of 3 exams for 4 students in two subjects
# (therefore 6 marks per students or 12 per subjects)
# stud.1 stud.2 stud.3 stud.4
marks = np.array([[[7,13,11], [7,7,13], [5,9,11], [7,17,15]], # subject 1
[[8,12,14], [8,12,12], [8,12,10], [12,16,12]]]) # subject 2
marks.shape
(2, 4, 3)
Les axes¶
Un tableau a des axes qui correspondent aux axes d'un repère dans l'espace. L'ordre des axes est celui de l'inclusion des crochets. En 2D un tableau de tableau est un tableau de lignes avec chaque ligne qui est un tableau 1D de valeurs. L'ordre est donc lignes puis colonnes (contrairement à l'axes $(x,y)$ dans l'espace). En 3D l'ordre est ligne, colonne, profondeur si on désire avoir une image, sinon c'est 0, 1 et 2.
De très nombreuses opérations sur les tableaux se font suivant un des axes du tableau aussi il est important de comprendre ce que sont les axes.
Regardons l'exemple des notes ci-dessus. Les axes sont
- les matières
- les étudiants
- les examens
Faire la moyenne des valeurs suivant l'axe 1 revient à prendre les données suivant l'axe 1 et effectuer les calculs dessus, donc ici en sortir une moyenne.
marks.mean(axis=1) # give means for each exam in each subject
array([[ 6.5, 11.5, 12.5], [ 9. , 13. , 12. ]])
Un autre facon de voir les axes est de les considérer comme des axes de projection. Si je projette un objet 3D suivant l'axe des $y$, le résultat est un objet 2D en $(x,z)$. On a ainsi une réduction de dimension.
Si je somme sur l'axe 0 un tableau de dimension (2,4,3) comme l'est notre tableau de notes, cela veut dire que je perds la dimension 0 et donc la dimension du résultat est (4,3).
marks.mean(axis=0).shape # mean along axis 0 (subjects) therefore this axis disapears
(4, 3)
Quelques fonctions qui supportent les axes¶
Toutes les fonctions qui s'appliquent à un ensemble de valeur pour produire un résultat doivent pouvoir utiliser de concept d'axe (je ne les ai pas toutes vérifiées mais n'hésitez pas à m'indiquer un contre-exemple). On a les fonctions mathématiques suivantes :
- arithmétiques :
sum
,prod
,cumsum
,cumprod
- statistiques :
min
,max
,argmin
,argmax
,mean
(moyenne),average
(moyenne pondérée),std
(écart type),var
,median
,percentile
,quantile
- autres :
gradiant
,diff
,fft
De plus il est possible de trier les valeurs d'un tableau suivant l'axe de son choix avec sort
.
Par contre on ne peut les mélanger, avec shuffle
, que suivant l'axe 0.
Appliquer une fonction suivant un axe¶
La fonction apply_along_axis
permet d'appliquer une fonction 1D de son choix à un tableau suivant un axe. C'est l'axe qui va disparaître dans le résultat :
def diff_min_max(a):
print('->', a, a.max() - a.min())
return a.max() - a.min()
np.apply_along_axis(diff_min_max, axis=-1, arr=marks) # -1 is the last axis, marks in our case
-> [ 7 13 11] 6 -> [ 7 7 13] 6 -> [ 5 9 11] 6 -> [ 7 17 15] 10 -> [ 8 12 14] 6 -> [ 8 12 12] 4 -> [ 8 12 10] 4 -> [12 16 12] 4
array([[ 6, 6, 6, 10], [ 6, 4, 4, 4]])
Question : c'est l'écart entre les notes de quoi ?
Appliquer une fonction suivant plusieurs axes¶
Certaines opérations peuvent prendre une liste d'axes et non un seul axe.
print('a.max \n', marks.max(axis=(1,2)), '\n')
print('a.max keepdim \n', marks.max(axis=(1,2), keepdims=True), '\n')
a.max [17 16] a.max keepdim [[[17]] [[16]]]
Question : à quoi correspondent les 2 valeurs sorties ?
Il est également peut utiliser la fonction apply_over_axes
pour lui indiquer quelle
fonction doit être appliquée suivant les axes donnés.
Attention la fonction donnée en argument recevra l'ensemble du tableau et l'axe sur lequel elle doit travailler, les axes étant donnés les uns après les autres et le tableau étant modifié à chaque étape.
def mymax(array, axis):
print('Apply over axis', axis)
print(array, '\n')
return array.max(axis)
np.apply_over_axes(mymax, marks, axes=(1,2))
Apply over axis 1 [[[ 7 13 11] [ 7 7 13] [ 5 9 11] [ 7 17 15]] [[ 8 12 14] [ 8 12 12] [ 8 12 10] [12 16 12]]] Apply over axis 2 [[[ 7 17 15]] [[12 16 14]]]
array([[[17]], [[16]]])
Réorganisation d'un tableau¶
On a déjà vu reshape
pour changer la forme d'un tableau, flatten
pour l'applatir en 1 dimension, regardons
d'autres fonctions de manipulation des tableaux.
Réordonner les axes¶
moveaxis
déplace un axe¶
Dans notre exemple de notes, les 3 axes sont les matières, les étudiants et les examens.
La fonction moveaxis
permet de déplacer un axe. Si ainsi je désire que les examens deviennent le premier axe
afin d'en faire ressortir les notes, je déplace l'axe 2 à la position 0 et les autres axes glissent pour faire de la place, l'axe 0 devient l'axe 1 et l'axe 1 devient l'axe 2 :
print('marks.shape = ',marks.shape, '\n')
b = np.moveaxis(marks, 2, 0)
print('b.shape = ', b.shape)
b
marks.shape = (2, 4, 3) b.shape = (3, 2, 4)
array([[[ 7, 7, 5, 7], [ 8, 8, 8, 12]], [[13, 7, 9, 17], [12, 12, 12, 16]], [[11, 13, 11, 15], [14, 12, 10, 12]]])
Il est plus simple de voir ainsi que le premier examen a été difficile.
swapaxes
échange 2 axes¶
Plutôt que d'insérer un axe à une nouvelle position et faire glisser les autres, on peut vouloir en échanger deux. Voici comme avoir les notes pour chaque matière et chaque examen :
marks.swapaxes(1,2)
array([[[ 7, 7, 5, 7], [13, 7, 9, 17], [11, 13, 11, 15]], [[ 8, 8, 8, 12], [12, 12, 12, 16], [14, 12, 10, 12]]])
transpose
pour tout faire¶
Enfin transpose
permet de réordonner tous les axes comme on veut, ainsi : transpose((2,0,1))
met
- l'axe 2 en place 0,
- l'axe 0 en place 1
- l'axe 1 en place 2.
Un apply over axis plus simple et plus rapide¶
Malheureusement la fonction apply_over_axis
n'est pas optimisée, aussi dans certaints il peut
être préférable de faire une boucle sur son tableau, ce qui veut dire mettre les axes qui vont rester au début et ceux sur lesquels on fait notre réduction à la fin :
print("Means per students", [m.mean() for m in marks.transpose((1,0,2))])
%timeit [m.mean() for m in marks.transpose((1,0,2))]
%timeit np.apply_over_axes(np.mean, marks, axes=(0,2))
Means per students [10.833333333333334, 9.833333333333334, 9.166666666666666, 13.166666666666666] 24 µs ± 2.01 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) 29.6 µs ± 1.23 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
a = np.arange(24).reshape([2,3,4])
np.flip(a,0)
array([[[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]], [[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]])
On peut faire glisser les valeurs suivant un axe avec roll
en spécifiant de combien on les fait glisser :
np.roll(a, 2, axis=1) # roll elements by 2 along axis 1
array([[[ 4, 5, 6, 7], [ 8, 9, 10, 11], [ 0, 1, 2, 3]], [[16, 17, 18, 19], [20, 21, 22, 23], [12, 13, 14, 15]]])
La transposée s'applique aussi quelque soit la dimension. Par défaut elle inverse l'ordre des axes mais on peut spécifier l'ordre voulu en sortie.
a.T
array([[[ 0, 12], [ 4, 16], [ 8, 20]], [[ 1, 13], [ 5, 17], [ 9, 21]], [[ 2, 14], [ 6, 18], [10, 22]], [[ 3, 15], [ 7, 19], [11, 23]]])
np.transpose(a, (0,2,1))
array([[[ 0, 4, 8], [ 1, 5, 9], [ 2, 6, 10], [ 3, 7, 11]], [[12, 16, 20], [13, 17, 21], [14, 18, 22], [15, 19, 23]]])
Agrégation¶
Concaténation¶
La fonction de base est concatenate
en indiquant l'axe choisi pour la concaténation. C'est à mon avis la méthode
la plus sûre et elle marche quelque soit la dimension.
Cela étant on peut utiliser pour des tableaux 2D ou 3D :
vstack
ourow_stack
pour la concaténation verticalhstack
oucolumn_stack
pour la concaténation horizontaldstack
pour la concaténation en profondeur (deep).
Toutes ces fonctions prennent une liste de tableaux à concaténer comme argument. Bien sûr les tailles des tableaux doivent être compatibles.
a = np.zeros((2,3))
b = np.ones((2,3))
print(np.concatenate((a,b), axis=0), '\n') # same than vstack
print(np.hstack((a,b))) # same than concatenate with axis=1
[[0. 0. 0.] [0. 0. 0.] [1. 1. 1.] [1. 1. 1.]] [[0. 0. 0. 1. 1. 1.] [0. 0. 0. 1. 1. 1.]]
c = np.stack((a,b)) # c[0] is a
c
array([[[0., 0., 0.], [0., 0., 0.]], [[1., 1., 1.], [1., 1., 1.]]])
Notez que stack
a une option axis
pour indiquer la direction dans laquelle on désire stocker les tableaux donnés.
Découpage¶
La fonction inverse de la concaténation est le découpage avec split
qui demande comme arguments :
- le tableau à découper
- en combien de morceaux ou à quels indices
- la direction (l'axe)
Pour retrouver nos deux tableaux qui ont généré le résultat de la cellule précédante on coupe en 2 suivant l'axe 0. On peut aussi couper suivant un autre axe.
e,f = np.split(c, 2, 1) # splits in 2 along axis 1
print("split part 1\n", e, '\n')
print("split part 2\n", f)
split part 1 [[[0. 0. 0.]] [[1. 1. 1.]]] split part 2 [[[0. 0. 0.]] [[1. 1. 1.]]]
Il existe aussi hsplit
, vsplit
et dsplit
pour découper suivant les axes 0, 1 et 2.
From Python to Numpy¶
Si vous désirez creuser et regarder de nombreux exemples, vous pouvez lire le livre de N. Rougier From Python to Numpy.
Pandas aussi¶
On retrouvera ces manipulations avec Pandas qui est le super tableur de Python. Il travaille aussi sur des structures en forme de tableau mais sans la contrainte que toutes les valeurs soient du même type.
{{ PreviousNext("np02 Filtres.ipynb", "np04 Xarray.ipynb")}}