Chp J. Listes python VS tableaux numpy
Dans ce chapitre, on va comparer les listes python et les tableaux numpy.
- Dans une première partie on va revenir sur la notion de liste 1D en python que l'on complétera avec la notion de slice et on verra comment simuler des listes 2D.
- Dans une deuxième partie on abordera la notion de tableau numpy en envisageant également la 1D et la 2D (voire la multi-D !) en essayant de mettre en évidence les avantages et les inconvénients.
I. Liste python 1D
1. Rappels
On a déjà travaillé sur la notion de liste, faisons le point. On considère la liste
mots = ['sinus', 'cosinus', 'tangente', 'exponentielle', 'logarithme']
Trois façons pour parcourir une liste
- La méthode universelle commune à quasiment tous les langages. On accède aux éléments par leurs indices (inconvénient : un peu lourd...):
for i in range(len(mots)):
print(i,mots[i])
- La méthode naturelle spécifique à python. On accède directement aux éléments (inconvénient : on n'a pas accès à l'indice des éléments):
for m in mots :
print(m) # on n'a pas l'indice
- Une méthode mixte aussi spécifique à python, avec une boucle doublement indicée (on a accès aux indices et aux valeurs):
for i,m in enumerate(mots):
print(i,m)
Chacun des scripts précédents affiche les indices et les éléments de la liste mots
les uns après les autres.
2. Création de liste par compréhension
On peut aussi créer une liste par compréhension à partir d'une autre liste.
Schématiquement, si f
est une fonction python dépendant d'un paramètre, pour créer la liste des éléments f(x)
pour x
parcourant un certain ensemble sous certaines conditions on écrit :
Création de liste par compréhension
l = [ f(x) for x in liste if condition ]
Voici quelques exemples pour comprendre le principe :
lst1 = [ m[0] for m in mots ]
# lst1 contient : ['s','c','t','e','l']
lst2 = [ 2*x for x in range(5) ]
# lst2 contient : [0,2,4,6,8]
def f(x):
return x*x
lst3 = [ f(x) for x in range(5) ]
# lst3 contient : [0,1,4,9,16]
lst4 = [ f(x) for x in range(5) if f(x)>2 ]
# lst4 contient : [4,9,16]
lst5 = [ (i,j) for i in range(3) for j in range(3) ]
# lst5 contient : [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
3. Accès en lecture à une sous-liste d'une liste: slices
On précise ici en lecture, car on verra en fin de paragraphe qu'il est possible de faire de l'écriture mais que ce n'est pas recommandé si on n'est pas spécialiste !
On a déjà vu que les éléments d'une liste sont numérotés à partir de 0. On y accède avec la notation [...]. Par exemple, en reprenant la liste :
mots = ['sinus', 'cosinus', 'tangente', 'exponentielle', 'logarithme']
mots[0]
contient le motsinus
mots[1]
contient le motcosinus
- etc...
mots[-1]
contient le motlogarithme
. On notera que quand les indices sont négatifs, ils sont interprétés "modulo la longueur de la liste", en d'autres termes on compte à l'envers depuis le dernier élément.
Slicing
Il existe en python un moyen simple d'extraire en lecture une sous-liste d'une liste (on parle de slicing)
La commande mots[n:p]
va renvoyer une copie de la sous-liste de mots
commençant à l'indice n (inclus) et finissant à l'indice p (exclu).
Voyons cela sur des exemples en partant toujours de la liste :
mots = ['sinus', 'cosinus', 'tangente', 'exponentielle', 'logarithme']
mots[1:3]
renvoie la liste['cosinus','tangente']
mots[1:1]
renvoie la liste videmots[:2]
renvoie la liste['sinus','cosinus']
. Une abscence de nombre en premier paramètre est remplacé par 0.mots[4:]
renvoie la liste['logarithme']
. Une absence de nombre en deuxième paramètre est remplacé par la longueur de la liste.mots[:]
renvoie une copie de la liste mots.
Attention
La copie de liste ne se fait pas comme pour un entier ou une chaîne de caractère.
Voyons cela sur un exemple. Si l'on saisit les lignes suivantes :
# Attention : prendre garde pour copier une liste !
mots = ['sinus', 'cosinus', 'tangente', 'exponentielle', 'logarithme']
mots_bis = mots[:]
mots_ter = mots
print(mots)
print(mots_bis)
print(mots_ter)
On obtiendra comme prévu 3 fois la même liste affichée. Par contre, si l'on tente les modifications suivantes~:
mots.append('inverse')
print(mots)
print(mots_bis)
print(mots_ter)
Cette fois-ci on obtient l'affichage :
['sinus', 'cosinus', 'tangente', 'exponentielle', 'logarithme', 'inverse']
['sinus', 'cosinus', 'tangente', 'exponentielle', 'logarithme']
['sinus', 'cosinus', 'tangente', 'exponentielle', 'logarithme', 'inverse']
C'est à dire que la modification le liste originale a également modifié la liste mots_ter
qui n'est en réalité qu'un alias de la variable mots
. Seule la liste mots_bis
créée avec le slice est une copie de la liste initiale. Il faudra donc bien garder cela en tête lorsque l'on travaille sur des listes.
À noter
L'utilisation d'un slice renvoie toujours une liste, celle-ci étant éventuellement vide, ou réduite à un élément. Cette liste est une copie des éléments de la liste initiale.
Pour aller un peu plus loin avec les slices : il est possible d'ajouter un troisième paramètre dans les crochets, il représente le pas. Voyons un exemple :
lst = list(range(10))
# lst contient : ['0,1,2,3,4,5,6,7,8,9]
sl = lst[3::2] # récupère un élément sur 2 à partir de celui d'indice 3
# sl contient : [3, 5, 7, 9]
Remarque 1
Les slices offrent encore bien des possibilités, et on consultera la documentation sur le net en cas de besoin.
Remarque 2
Les slices sur les listes python peuvent aussi être utilisés en écriture (faire des affectations) mais le comportement est parfois surprenant, et on évitera leur utilisation. On abordera un peu ce sujet dans la dernière partie de ce thème...
4. Opérations usuelles sur les listes (méthodes)
Voyons sur un exemple les différentes méthodes que l'on peut appliquer sur une liste. Bien sûr, pour avoir davantage d'informations, on se référera à l'aide python sur internet !
On va voir dans cet exemple les méthodes append
, remove
, index
, reverse
et sort
que l'on peut appliquer à une liste. Il en existe d'autres, on pourra consulter Internet...
Voici quelques exemple d'opérations que le'on peut faire sur la liste mots
ci-dessous:
mots = ['sinus', 'cosinus', 'tangente', 'exponentielle', 'logarithme']
mots.append('inverse') # ajoute un élément
mots.remove('cosinus') # on enlève un élément connu
mots.index('tangente') # récupère l'index d'un élément connu
mots.reverse() # renverse l'ordre des éléments
mots.sort() # tri les éléments dans l'ordre alphabétique
m = mots.pop(2) # enlève et renvoie l'élément d'index donné
del mots[-1] # supprime l'élément d'index donné
del mots[1:] # même chose avec un slice
II. Listes 2D en python
En python il n'y a pas nativement la possibilité de gérer des tableaux à double entrée. Pour cela on aura recours à des listes de listes
Par exemple on peut écrire :
lst = [ [1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
Ainsi lst
contient trois éléments qui sont chacun des listes de 3 éléments (cela simule une matrice 3x3 !).
Par exemple on récupère la ligne 1 par l'instruction :
lig1 = lst[0]
# lig1 contient [1,2,3]
Du coup, l'élément 6 qui se situe en ligne d'indice 1 (on commence à 0) et en colonne d'indice 2 peut être atteint par l'instruction :
six = lst[1][2]
# six contient 6
Le slicing fonctionne encore:
- Par exemple
lst[:1]
contient la liste constituée de la liste située à l'indice 0, c'est à dire[[1,2,3]]
(remarquer les doubles crochets). lst[1][1:]
contient la liste[5,6]
III. TD sur les listes
Faire le TD J1 : travail sur les listes python (slices, 1D et 2D)
IV. Module numpy
On a étudié dans la partie précédente les listes python. On a apprécié leur souplesse (notamment pour ajouter ou enlever des éléments) mais on a vu aussi leurs limites notamment en 2D. Le module numpy
est là pour y remédier.
Première chose à remarquer : le vocabulaire !
Jusqu'à présent on parlait de listes python. Pour celles-ci, leur taille pouvait évoluer au cours du temps, et leurs éléments pouvaient être de type mélangés. Avec numpy
, on parle de tableaux. Leur taille est fixée dès le départ (et elle ne bouge plus) et les éléments sont tous du même type. Ces contraintes en terme de souplesse, sont compensées par des performances bien supérieures en terme de rapidité d'exécution...
Les bénéfices de numpy
(petit extrait du net ;-) ):
Here are the top four benefits that NumPy can bring to your code:
- More speed: NumPy uses algorithms written in C that complete in nanoseconds rather than seconds.
- Fewer loops: NumPy helps you to reduce loops and keep from getting tangled up in iteration indices.
- Clearer code: Without loops, your code will look more like the equations you’re trying to calculate.
- Better quality: There are thousands of contributors working to keep NumPy fast, friendly, and bug free.
1. Tableau 1D
Voyons comment utiliser un tableau numpy
et la facilité proposée pour les manipuler. On commence bien entendu par importer le module:
# Import de la libraire : alias usuel 'np'
import numpy as np
Plusieurs méthodes pour créer un tableau numpy 1D
- Par conversion d'une liste python avec la fonction
np.array(...)
:
tab1 = np.array([7,16,13,17,24,18])
- Avec
np.arange(...)
: cela s'utilise comme la fonctionrange
de python, avec point de départ, point d'arrivée et pas, mais l'avantage est que l'on n'est pas restreint à des entiers :
tab2 = np.arange(1.5 ,4 ,0.5)
# tab2 contient : array([1.5, 2. , 2.5, 3. , 3.5])
- Ou un tableau de zéros, que l'on pourra modifier par la suite :
tab3 = np.zeros(10) # crée un tableau de 10 zéros
tab3[1] = 5 # l'élément d'indice 1 devient 5
Accès aux éléments
Pareil que pour les listes python. Par exemple tab1[1]
donne 16.
Le slicing
L'extraction par slice fonctionne également de la même façon :
sl = tab1[:2]
# sl contient : array([ 7, 16])
Remarque très pratique
Les slices sur les tableaux numpy peuvent être utilisés en écriture (de façon plus naturelle que les listes), c'est à dire que l'on peut même faire des opérations d'affectation simultanées sur plusieurs zones du tableau. Par exemple :
tab4 = np.zeros(10) # crée un tableau de 10 zéros
tab4[2:6] = 10 # les éléments d'indices 2 à 5 inclus vaudront 10
tab4[::2] = 20 # tous les éléments d'indice pair vaudront 20
Opérations sur un tableau (mieux qu'avec les listes !)
Imaginons par exemple que le tableau tab1
représente les notes sur 30 obtenues par un groupe d'étudiants. On peut d'une simple ligne (et donc sans boucle) ramener ces notes sur 20 en multipliant par 2/3 :
tab1 = tab1*2/3
Et on peut ajouter un bonus ;-) de cette façon :
tab1 = tab1+1
Et si l'on veut arrondir les notes à 0.1 près on peut faire :
tab1 = np.round(tab1,1) # arrondir à une décimale
On peut aussi appliquer des fonctions mathématiques à tous les éléments d'un tableau. Par exemple :
tab5 = np.arange(0,2*np.pi,np.pi/4)
tab6 = np.sin(tab5)
# tab5 contient les angles de 0 à 2pi par pas de pi/4
# tab6 contient les images des ces angles
Plus généralement, on peut appliquer n'importe quelle fonction une fois qu'on l'a vectorisée :
# Une fonction personnelle :
def myfunc(x):
if x>0 :
return x*x
else :
return -x*x
# On la vectorise :
myfunc_vect = np.vectorize(myfunc)
# On crée un tableau
tab7 = np.arange(-3,3,0.5)
# on obtient un nouveau tableau en appliquant notre nouvelle fonction à toutes les valeurs du tableau
tab8 = myfunc_vect(tab7)
print(tab7)
print(tab8)
[-3. -2.5 -2. -1.5 -1. -0.5 0. 0.5 1. 1.5 2. 2.5]
[-9. -6.25 -4. -2.25 -1. -0.25 -0. 0.25 1. 2.25 4. 6.25]
Ainsi, ces deux listes permettent sans effort de faire la représentation graphique avec matplotlib de la fonction myfunc
(on testera en TD...)
Cette fonctionnalité est très intéressante et très performante. On peut donc l'utiliser pour tracer des représentations graphiques de fonction mais aussi, de façon très performante, dans la manipulation des images (thème abordé dans un prochain TD...)
2. Tableau 2D
Création d'un tableau à double entrée
-
On peut convertir une liste de listes python avec
np.array(...)
:tab2_1 = np.array([ [1, 2, 3, 4], [5, 6, 7, 8], ])
-
On peut aussi obtenir le même résultat directement en modifiant les dimensions d'une liste ou d'un tableau 1D avec la méthode
.reshape(lig,col)
:tab2_2 = np.arange(1,9) # entiers de 1 à 8 tab2_3 = tab2_2.reshape(2,4) # 2 lignes et 4 colonnes
Une commande qui nous servira pour le traitement de l'image est celle qui permet d'obtenir les dimensions (le shape) du tableau :
lig,col = tab2_3.shape # lig contient 2 et col contient 4
-
Pour créer un tableau de dimension donnée rempli de zéros (attention aux doubles parenthèses) :
tab2_4 = np.zeros((2,4))
Accès aux éléments (mieux que les listes python !)
Pour accéder à un élément la syntaxe est un peu plus agréable que pour les listes, car plus proche de celle que l'on utilise naturellement:
elt = tab2_3[1,2] # élément ligne indice 1, colonne indice 2
# elt contient 7
On retiendra que pour un tableau 2D, on prendra l'habitude de toujours saisir entre les crochets un couple séparé par une virgule, le premier élément correspondant aux lignes et le deuxième aux colonnes. En particuliers les slices fonctionnent sur chaque composante séparément. Voir les exemples suivants.
Le slicing
-
Grâce aux slices on peut extraire des parties du tableau. Par exemple :
# création d'une matrice en redimensionnant un tableau 1D tab2_5 = np.arange(1,16).reshape(3,5) # tab2_5 continent : #array([[ 1 2 3 4 5] # [ 6 7 8 9 10] # [11 12 13 14 15]]) # extraction d'une ligne : lig2 = tab2_5[1,:] # lig2 contient : array([ 6, 7, 8, 9, 10]) # extraction d'une colonne (que l'on ne pouvait pas faire directement avec les listes) col3 = tab2_5[:,2] # col3 contient array([ 3, 8, 13]) # extraction d'un bout du tableau : mini_tab = tab2_5[0:2,0:2] # mini_tab contient : array([[1, 2], # [6, 7]])
-
Les slices en écriture : on peut aussi utiliser les slices (comme pour les tableaux 1D, sur chacune des composantes) pour modifier des colonnes ou des lignes :
tab2_5[:,0:1] = 1 # On met des 1 dans la première colonne tab2_5[0:1,:] = 2 # On met des 2 dans la première ligne tab2_5[0,::2] = 0 # On met des 0 sur la première ligne aux colonnes paires
Opérations
Tout ce qui a été vu pour les opérations sur les tableaux 1D s'applique encore ici...
V. Affectations : tableaux numpy VS listes python
Attention !
On a vu que l'on pouvait utiliser le slicing pour extraire des informations depuis une liste ou un tableau, mais que l'on pouvait aussi s'en servir pour affecter des valeurs à une liste ou un tableau. Mais, le comportement pour les listes python et pour les tableaux numpy est bien différent.
Voyons sur des données 1D ces différences, mais cela sera la même choses en dimensions multiples.
On considère les variables suivantes :
import numpy as np
lst = [2,4,6,8,10,12] # liste python
tab = np.array(lst) # tableau numpy
On peut faire :
-
lst[n:p]=[...]
: remplace la sous-liste (n-p) de lst par le membre de droite, qui doit être une liste. Cette liste peut ne pas avoir le même nombre d'éléments que la sous-liste. Essayer les exemples suivants, prévoir puis analyser les résultats :lst[2:4]=[1] print(lst) lst[3:4]=[10,11,12,13] print(lst) lst[3:4]=[] print(lst) lst[3:3]=[0] print(lst) lst2 = [1]*5 print(lst2) lst3=lst+lst2 print(lst3)
-
tab[n:p]=[...]
: remplace terme à terme les éléments du sous-tableau par ceux de la liste du second membre, qui par conséquent doit avoir le même nombre d'éléments. Mais, il y a un cas particulier très utile : si le membre de droite n'est pas une liste, mais simplement une valeur, alors numpy va broadcaster cette valeur pour la transformer en une liste de bonne dimension. En d'autre termes, tous les éléments du sous-tableau seront remplacés par cette valeur. Essayer les exemples suivants, prévoir et analyser les résultas :# les commandes suivantes renvoient des erreurs (probèmes de tailles) tab[2:4]=[1] tab[3:4]=[10,11,12,13] tab[3:4]=[] tab[3:3]=[0] # Analyser les résultats : tab[2:4] = [10,11] print(tab) tab[:3]=5 print(tab) tab[::2]=0 print(tab) tab2 = np.array([1])*5 print(tab2) tab3 = tab+tab2 # erreur si dimensions différentes tab4 = tab+tab
On testera ces manipulations en TD.
On retiendra :
l'utilisation des slices sur les listes python ou les tableaux numpy
est extrêmement pratique et très performante. On peut les utiliser à la fois en lecture et en écriture, mais on gardera à l'esprit que lorsque l'on s'en sert en écriture, les comportements sont bien différents entre les listes python et les tableaux numpy
. Ces derniers sont a priori plus "naturels". A moins que l'on sache bien ce que l'on fait on évitera d'utiliser les slices en écriture sur les listes python.
VI. TD sur numpy
-
Le TD J2 : bases des manipulations de tableaux numpy
-
Le TD J3 : à propos de labyrinthe