import numpy as np
from matplotlib import pyplot as pltcontenu de ce notebook (sauter si déjà acquis)¶
les manières d’accéder à des éléments et de slicer un tableau
numpyles slices sont des vues et non des copies
la notion de
numpy.ndarray.basevoir les
exercices avancés pour les rapides
accès aux éléments d’un tableau¶
accès à un tableau de dimension 1¶
# le code
tab = np.arange(12)
tab[0] = np.pi
tab[0].dtype, tab[0](dtype('int64'), np.int64(3))# le code
tab1 = tab.astype(np.float64)
tab1[0] = np.pi
tab1[0].dtype, tab1[0](dtype('float64'), np.float64(3.141592653589793))accès à un tableau de dimension > à 1¶
# le code en dimension 2
tab = np.arange(12).reshape((2, 6))
# première ligne, deuxième colonne
line, col = 0, 1
tab[line, col] = 1000
tabarray([[ 0, 1000, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11]])# le code en dimension 3
tab.resize((2, 3, 2))
# deuxième matrice, troisième ligne, première colonne
mat, line, col = 1, 2, 0
tab[mat, line, col] = 2000
tabarray([[[ 0, 1000],
[ 2, 3],
[ 4, 5]],
[[ 6, 7],
[ 8, 9],
[2000, 11]]])[tab.shape[i] for i in range(tab.ndim)][2, 3, 2]tab.shape(2, 3, 2)exercices¶
accès à un élément
créez un tableau des 30 valeurs paires à partir de 2 (utilisez
numpypasPython)donnez lui la forme de 2 matrices de 5 lignes et 3 colonnes
accédez à l’élément qui est à la 3ème colonne de la 2ème ligne de la 1ère matrice
obtenez-vous 12 ?
# votre codeexercice
faites un
np.ndarrayde forme(3, 2, 5, 4)
avec des nombre aéatoires entiers entre 0 et 100affichez-le et vous voyez trois groupes et 2 matrices de 5 lignes et 4 colonnes
affichez le nombre des éléments des deux dernières dimensions
indice
voyez
np.random.randintpour créer un tableau aléatoiretapez
np.random.randint?pour avoir de l’aide en ligne
# votre code iciaccéder à un sous-tableau (slicing)¶
différence slicing python et numpy¶
le slicing numpy est syntaxiquement équivalent à celui des listes Python
la grande différence est que
quand vous slicez un tableau
numpyvous obtenez une vue sur le tableau initial
(avec une nouvelle indexation)quand vous slicez une liste
pythonvous obtenez une copie de la liste initiale
le slicing numpy va
regrouper des éléments du tableau initial
dans un sous-tableau
numpy.ndarrayavec l’indexation adéquatela mémoire sous-jacente reste la même
la seule structure informatique qui sera créée est l’indexation
vous pourrez ensuite, par exemple, modifier ces éléments
et donc ils seront modifiés dans le tableau initial
rappel du slicing Python¶
rappel du slicing Python
l[from:to-excluded:step]paramètres tous optionnels
par défaut:from = 0to-excluded = len(l)etstep=1indices négatifs ok
-1est le dernier élément,-2l’avant dernier ...
la liste python des 10 premiers entiers
l = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# un élément sur 2 en partant du début de la liste (copie)
l[::2]
# un élément sur 3 en partant du premier élément de la liste (copie)
l[1::3]
# la liste en reverse (copie)
l[::-1]
# la liste entière (copie)
l[::]
# ou
l[:]# le code
l = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
print(l[::2])
print(l[1::3])
print(l[::-1])
print(l[:])[0, 2, 4, 6, 8]
[1, 4, 7]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
slicing en dimension 1¶
on crée un numpy.ndarray de dimension 1 de taille 10
on prend un élément sur 2 en partant du début de la liste
on modifie les éléments du sous-tableau obtenu
le tableau initial est modifié
vec = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
print(vec[::2]) # [0 2 4 6 8]
vec[::2] = 100
print(vec) # [100, 1, 100, 3, 100, 5, 100, 7, 100, 9]# le code
vec = np.arange(10)
print(vec[::2])
vec[::2] = 100
vec[0 2 4 6 8]
array([100, 1, 100, 3, 100, 5, 100, 7, 100, 9])slicing en dimension > à 1 (a)¶
on crée un numpy.ndarray en dimension 4, de forme (2, 3, 4, 5)
on l’initialise avec les 120 premiers entiers
tab = np.arange(120).reshape(2, 3, 4, 5)on a 2 groupes de 3 matrices de 4 lignes et 5 colonnes
on accède au premier groupe de matrices
tab[0]on accède à la deuxième matrice du premier groupe de matrices
tab[0, 1]on accède à la troisième ligne de la deuxième matrice du premier groupe de matrices
tab[0, 1, 2]on accède à la quatrième colonne de la deuxième matrice du premier groupe de matrices
tab[0, 1, :, 3] # remarquez le ':' pour indiquer toutes les lignes# le code
tab = np.arange(120).reshape(2, 3, 4, 5)
print( tab )
print( tab[0] )
print( tab[0, 1] )
print( tab[0, 1, 2] )
print( tab[0, 1, :, 3] )[[[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[ 10 11 12 13 14]
[ 15 16 17 18 19]]
[[ 20 21 22 23 24]
[ 25 26 27 28 29]
[ 30 31 32 33 34]
[ 35 36 37 38 39]]
[[ 40 41 42 43 44]
[ 45 46 47 48 49]
[ 50 51 52 53 54]
[ 55 56 57 58 59]]]
[[[ 60 61 62 63 64]
[ 65 66 67 68 69]
[ 70 71 72 73 74]
[ 75 76 77 78 79]]
[[ 80 81 82 83 84]
[ 85 86 87 88 89]
[ 90 91 92 93 94]
[ 95 96 97 98 99]]
[[100 101 102 103 104]
[105 106 107 108 109]
[110 111 112 113 114]
[115 116 117 118 119]]]]
[[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]
[15 16 17 18 19]]
[[20 21 22 23 24]
[25 26 27 28 29]
[30 31 32 33 34]
[35 36 37 38 39]]
[[40 41 42 43 44]
[45 46 47 48 49]
[50 51 52 53 54]
[55 56 57 58 59]]]
[[20 21 22 23 24]
[25 26 27 28 29]
[30 31 32 33 34]
[35 36 37 38 39]]
[30 31 32 33 34]
[23 28 33 38]
slicing en dimension > à 1 (b)¶
on crée un numpy.ndarray en dimension 4, de forme (2, 3, 4, 5)
on l’initialise avec les 120 premiers entiers
tab = np.arange(120).reshape(2, 3, 4, 5)on peut combiner les slicing des 4 dimensions, icitab[from:to:step, from:to:step, from:to:step, from:to_step]
de l’indice from à l’indice to (exclus) avec un pas step
à savoir
quand vous voulez la valeur par défaut de
from,toetstepvous ne mettez rienquand les valeurs par défaut sont en fin d’expression, elles sont optionnelles
du coup pour prendre tous les éléments dans une dimension
on peut mettre simplement la slice universelle::, que généralement on abrège encore en juste:
exemples
la première matrice de tous les groupes de matrice, c’est-à-dire:
tous les groupes
la première matrice
toutes les lignes
toutes les colonnes
# en version longue où on épelle bien tout
tab[::, 0, ::, ::]
# en version courte on abrège et ça donne simplement
tab[:, 0] tab = np.arange(120).reshape(2, 3, 4, 5)# en version longue
tab[::, 0, ::, ::]array([[[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]],
[[60, 61, 62, 63, 64],
[65, 66, 67, 68, 69],
[70, 71, 72, 73, 74],
[75, 76, 77, 78, 79]]])# en version courte
tab[:, 0]array([[[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]],
[[60, 61, 62, 63, 64],
[65, 66, 67, 68, 69],
[70, 71, 72, 73, 74],
[75, 76, 77, 78, 79]]])exercices
extrayez du tableau
tabprécédent
tab = np.arange(120).reshape(2, 3, 4, 5)la sous-matrice au milieu des premières matrices de tous les groupes, ici ça doit donner:
indices
on a 2 groupes de 3 matrices de 4 lignes et 5 colonnes
donc
pour les 2 groupes de matrices
dans la première matrice
la sous-matrice du milieu (obtenue en enlevant une épaisseur de largeur 1 sur le pourtour)
donc
tous les groupes
:la première matrice (indice
0)de la première ligne (indice
1) à l’avant dernière ligne (indice-1) step par défautidem pour les colonnes
# votre codeles sous-tableaux sont des vues, et non des copies¶
le slicing calcule une nouvelle indexation sur le segment mémoire du tableau existant
si à chaque slicing, numpy faisait une copie du tableau sous-jacent, les codes seraient inutilisables
parce que coûteux (pénalisés) en place mémoire
donc lors d’un slicing
un nouvel objet
np.ndarrayest bien crééson indexation est différente de celle de l’objet
np.ndarrayinitialmais ils partagent la mémoire (le segment unidimensionnel sous-jacent)
si un utilisateur veut une copie, il la fait avec la méthode copy
tab1 = tab[:, 0, 1:-1, 1:-1].copy()partage du segment sous-jacent ou non? - avancé¶
un tableau numpy.ndarray peut être
un tableau original (on vient de le créer éventuellement par copie)
une vue sur un tableau (il a été créé par slicing ou indexation)
il partage son segment de mémoire avec au moins un autre tableau
l’attribut numpy.ndarray.base vaut alors
Nonesi le tableau est un tableau original
tab = np.arange(10)
print(tab.base)
-> Nonetab1 = np.arange(10)
tab2 = tab1.copy()
print(tab2.base)
-> Nonele tableau original qui a servi à créer la vue
quand le tableau est une vue
tab1 = np.array([[1, 2, 3], [4, 5, 6]])
tab2 = tab1[0:2, 0:2] # une vue
tab2.base is tab1
-> Truetab1 = np.arange(120)
tab2 = tab1.reshape(2, 3, 4, 5) # une vue
tab2.base is tab1
-> Truefaites attention, dans l’exemple
tab1 = np.arange(10).reshape(2, 5)tab1.base est l’objet np.arange(10)
les numpy.ndarray ayant le même objet numpy.ndarray.base
partagent tous leur segment sous-jacent
sont différentes vues d’un même tableau original
(celui indiqué par leur attributbase)modifier les éléments de l’un modifiera les éléments des autres
(ils pointent tous sur le même segment de mémoire)
numpy essaie de créer le moins de mémoire possible
pour stocker les éléments de ses tableaux
# le code
tab1 = np.arange(10)
print(tab1.base)None
# le code
tab1 = np.arange(10)
tab2 = tab1.copy()
print(tab2.base)None
# le code
tab1 = np.array([[1, 2, 3], [4, 5, 6]])
tab2 = tab1[0:2, 0:2] # vue
tab2.base is tab1True# le code
tab1 = np.arange(120)
tab2 = tab1.reshape(2, 3, 4, 5) # une vue
tab2.base is tab1True# le code
tab1 = np.arange(10).reshape(2, 5)
tab1.basearray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])exercice
créez un nouveau tableau formé des deux matrices .
affichez sa
baseslicez le tableau pour obtenir
affichez la
basede la slicevérifiez que les deux
basesont le même objet
# votre code icimodification des sous-tableaux¶
pour modifier un sous-tableau, il faut simplement faire attention
au type des éléments
et à la forme du tableau
exercices avancés pour les rapides¶
avant d’aborder ces exercices, il existe un utilitaire très pratique (parmi les 2347 que nous n’avons pas eu le temps de couvrir ;); il s’agit de numpy.indices()
commençons par un exemple :
lignes, colonnes = np.indices((3, 5))lignesarray([[0, 0, 0, 0, 0],
[1, 1, 1, 1, 1],
[2, 2, 2, 2, 2]])colonnesarray([[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4]])vous remarquerez que dans le tableau qui s’appelle lignes, la valeur dans le tableau correspond au numéro de ligne; dit autrement :
lignes[i, j] == ipour tous les(i, j),
et dans l’autre sens bien sûr
colonnes[i, j] == j
lignes[1, 4]np.int64(1)colonnes[1, 4]np.int64(4)Pourquoi est-ce qu’on parle de ça me direz-vous ?
Eh bien en guise d’indice, cela vous renvoie à la notion de programmation vectorielle.
Ainsi par exemple si je veux créer une matrice de taille (3,5) dans laquelle M[i, j] == i + j, je ne vais surtout par écrire une boucle for, et au contraire je vais écrire simplement
I, J = np.indices((3, 5))
M = I + J
Marray([[0, 1, 2, 3, 4],
[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6]])les rayures¶
Écrivez une fonction zebre, qui prend en argument un entier n et qui fabrique un tableau carré de coté n, formé d’une alternance de colonnes de 0 et de colonnes de 1.
par exemple pour n=4 on s’attend à ceci
0 1 0 1
0 1 0 1
0 1 0 1
0 1 0 1le damier¶
Écrivez une fonction checkers, qui prend en argument la taille n du damier, et un paramètre optionnel qui indique la valeur de la case (0, 0), et qui crée un tableau numpy carré de coté n, et le remplit avec des 0 et 1 comme un damier.
vous devez obtenir par exemple
>>> checkers(4)
array([[1, 0, 1, 0],
[0, 1, 0, 1],
[1, 0, 1, 0],
[0, 1, 0, 1]])
>>> checkers(5, False)
array([[0, 1, 0, 1, 0],
[1, 0, 1, 0, 1],
[0, 1, 0, 1, 0],
[1, 0, 1, 0, 1],
[0, 1, 0, 1, 0]])# a vous de jouer
def checkers(n, up_left=True):
pass# pour tester
checkers(4)checkers(5, False)le super damier par blocs¶
Il y a beaucoup de méthodes pour faire cet exercice de damier; elles ne vont pas toutes se généraliser pour cette variante du super damier :
Variante écrivez une fonction block_checkers(n, k) qui crée et retourne
un damier de coté
k*n x k*ncomposé de blocs de
k x khomogènes (tous à 0 ou tous à 1)eux mêmes en damiers
on décide que le premier bloc (en 0,0) vaut 0
c’est-à-dire par exemple pour n=4 et k=3 cela donnerait ceci :
>>> block_checkers(4, 3)
array([[0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1],
[0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1],
[0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1],
[1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0],
[1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0],
[1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1],
[0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1],
[0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1],
[1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0],
[1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0],
[1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0]])# vous de jouer
def block_checkers(n, k):
passblock_checkers(3, 2)# doit vous donner la figure ci-dessus
# éventuellement avec des False/True au lieu de 0/1
block_checkers(4, 3)les escaliers¶
Écrivez une fonction escalier, qui prend en argument un entier n, qui crée un tableau de taille 2n+1, et qui le remplit de manière à ce que:
aux quatre coins du tableau on trouve la valeur 0
dans la case centrale on trouve la valeur 2n
et si vous partez de n’importe quelle case et que vous vous déplacez d’une case (horizontalement ou verticalement), en vous dirigeant vers une case plus proche du centre, la valeur augmente de 1
par exemple
>>> stairs(4)
array([[0, 1, 2, 3, 4, 3, 2, 1, 0],
[1, 2, 3, 4, 5, 4, 3, 2, 1],
[2, 3, 4, 5, 6, 5, 4, 3, 2],
[3, 4, 5, 6, 7, 6, 5, 4, 3],
[4, 5, 6, 7, 8, 7, 6, 5, 4],
[3, 4, 5, 6, 7, 6, 5, 4, 3],
[2, 3, 4, 5, 6, 5, 4, 3, 2],
[1, 2, 3, 4, 5, 4, 3, 2, 1],
[0, 1, 2, 3, 4, 3, 2, 1, 0]])# à vous de jouer
def stairs(n):
pass# pour vérifier
stairs(4)calculs imbriqués (avancé)¶
Regardez le code suivant :
# une fonction vectorisée
def pipeline(array):
array2a = np.sin(array)
array2b = np.cos(array)
array3 = np.exp(array2a + array2b)
array4 = np.log(array3+1)
return array4Les questions : j’ai un tableau X typé float64 et de forme (1000,)
j’appelle
pipeline(X), combien de mémoire est-ce quepipelineva devoir allouer pour faire son travail ?quel serait le minimum de mémoire dont on a besoin pour faire cette opération ?
voyez-vous un moyen d’optimiser
pipelinepour atteindre ce minimum ?
indice
l’exercice vous invite à réfléchir à l’utilisation du paramètre
out=qui est supporté dans les fonction vectorisées de numpydans ce cadre, sachez qu’on peut presque toujours remplacer l’usage d’un opérateur (comme ici
+) par une fonction vectorisée (icinp.add)