#!/usr/bin/env python
# coding: utf-8
# # `numpy` (tableaux de données multi-dimensionnels) et `matplotlib` (visualisation en 2D et 3D pour Python)
#
# Joseph Salmon : joseph.salmon@umontpellier.fr
#
# Adapté du travail de
#
# - A. Gramfort (alexandre.gramfort@inria.fr) http://alexandre.gramfort.net/
# - J.R. Johansson (robert@riken.jp) http://dml.riken.jp/~rob/
# In[1]:
get_ipython().run_line_magic('matplotlib', 'inline')
# **Remarque**: la commande "magique" `%matplotlib inline` peut aussi avoir de l'intérêt (à essayer donc!)
#
# Voir aussi:
# - https://jakevdp.github.io/PythonDataScienceHandbook/01.03-magic-commands.html
# - https://ipython.org/ipython-doc/3/config/extensions/autoreload.html (pour `autoreload`)
# - https://ipython.readthedocs.io/en/stable/interactive/magics.html
# ## Introduction
#
#
# * `numpy` est un module utilisé dans presque tous les projets de calcul numérique sous `Python`
# * Il fournit des structures de données performantes pour la manipulation de vecteurs, matrices et tenseurs plus généraux
# * `numpy` est écrit en `C` et en `Fortran` d'où ses performances élevées lorsque les calculs sont vectorisés, c'est-à-dire formulés comme des opérations sur des vecteurs/matrices.
# * `matplotlib` est un module performant pour la génération de graphiques en 2D et 3D
# * syntaxe très proche de celle de Matlab
# * supporte texte et étiquettes en $\LaTeX$
# * sortie de qualité dans divers formats (.png, .pdf, .svg, .gif,etc.)
# * interface graphique intéractive pour explorer les figures
#
# * `guiqwt.pyplot` est une alternative intéressante qui reprend la syntaxe de `matplotlib` (plus récente, encore peu utilisée)
# Pour utiliser `numpy` et `matplotlib` il faut commencer par les importer :
# In[2]:
import numpy as np # raccourci usuel
import matplotlib.pyplot as plt # raccourci usuel
# ## *Arrays* en `numpy`
#
# Dans la terminologie `numpy`, vecteurs, matrices et autres tenseurs (tableaux de dimension supérieure à 3) sont appelés *arrays*.
#
# ## Création d'*arrays* `numpy`
#
# Plusieurs possibilités:
#
# * a partir de listes ou n-uplets `Python`
# * en utilisant des fonctions dédiées, telles que `arange`, `linspace`, etc.
# * par chargement à partir de fichiers
#
# ### A partir de listes
#
# Au moyen de la fonction `numpy.array` :
#
# In[3]:
# un vecteur: l'argument de la fonction est une liste Python
v = np.array([1, 3, 2, 4])
print(v)
print(type(v))
# On peut alors visualiser ces données avec `matplotlib`:
# In[4]:
v = np.array([1, 3, 2, 4])
x = np.array([0, 1, 2, 3])
fig = plt.figure()
plt.plot(x,v, 'rv--', label='v(x)')
plt.legend(loc='lower right')
plt.xlabel('x')
plt.ylabel('v')
plt.title('Mon titre')
plt.xlim([-1, 4])
plt.ylim([0, 5])
plt.show()
# fig.savefig('toto.svg') # décommenter pour sauvegarder
# **Remarque**: on peut omettre la commande `plt.show()` lorsque la méthode `ion()` (pour *Interaction ON*) a été appelée.
# C'est le cas dans `spyder` et `pylab` (qu'on lance par exemple avec la commande `ipython --pylab` dans un terminal Linux).
# Pour définir une matrice (= array de dimension 2 pour `numpy`):
#
# In[5]:
# Matrice: l'argument est une liste emboitée
M = np.array([[1, 2], [3, 4]])
print(M)
# In[6]:
M[0, 0]
# Les objets `v` et `M` sont tous deux du type `ndarray` (fournis par `numpy`)
# In[7]:
type(v), type(M)
# `v` et `M` ne diffèrent que par leur taille, que l'on peut obtenir via la propriété `shape` :
# In[8]:
v.shape # noter qu'ici un vecteur a une dimension vide pour sa deuxième
# In[9]:
M.shape
# Pour obtenir le nombre d'éléments d'un *array* :
# In[10]:
v.size
# In[11]:
M.size
# On peut aussi utiliser `numpy.shape` et `numpy.size`
# In[12]:
np.shape(M)
# Les *arrays* ont un type qu'on obtient via `dtype`:
# In[13]:
print(M)
print(M.dtype)
# Les types doivent être respectés lors d'assignations à des *arrays*
# In[14]:
M[0,0] = "hello"
# ### Attention !
# In[17]:
a = np.array([1,2,3])
a[0] = 3.2
print(a)
a.dtype
# In[18]:
a = np.array([1,2,3], dtype=np.int64)
b = np.array([2,2,3], dtype=np.int64)
b = b.astype(float)
print(a / b)
# On peut définir le type de manière explicite en utilisant le mot clé `dtype` en argument:
# In[19]:
M = np.array([[1, 2], [3, 4]], dtype=complex)
M
# * Autres types possibles avec `dtype` : `int`, `float`, `complex`, `bool`, `object`, etc.
#
# * On peut aussi spécifier la précision en bits: `int64`, `int16`, `float128`, `complex128`.
# ### Utilisation de fonction de génération d'*arrays*
# #### `arange`
# In[20]:
# Création d'un interval simple
x = np.arange(0, 10, 2) # arguments: start, stop, step
x
# In[21]:
x = np.arange(-1, 1, 0.1)
x
# #### `linspace` and `logspace`
# In[22]:
# Attention : la fin EST inclus avec linspace
np.linspace(0, 10, 25)
# In[23]:
np.linspace(0, 10, 11)
# In[24]:
xx = np.linspace(-10, 10, 100)
fig = plt.figure(figsize=(5, 5))
plt.plot(xx, np.sin(xx))
plt.show()
# In[25]:
print(np.logspace(0, 10, 10, base=np.e))
# #### `mgrid`
# In[26]:
x, y = np.mgrid[0:5, 0:5]
# In[27]:
x
# In[28]:
y
# In[29]:
xx, yy = np.mgrid[-50:50, -50:50]
plt.figure()
plt.figure(figsize=(3, 3))
plt.imshow(np.angle(xx + 1j * yy))
plt.axis('on')
plt.colorbar()
plt.figure(figsize=(3, 3))
plt.imshow(np.abs(xx + 1j * yy))
plt.axis('on')
plt.colorbar()
plt.show()
# In[30]:
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(5, 4))
ax = Axes3D(fig)
X = np.arange(-4, 4, 0.2)
Y = np.arange(-4, 4, 0.2)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='viridis')
#
# #### Génération de données aléatoires
# In[31]:
# tirage uniforme dans [0,1]
np.random.rand(5,5) # ou np.random.rand
# In[32]:
# tirage suivant une loi normale standard
np.random.randn(5,5)
# ### Graine:
# Il est utile dans certains contexte de fixer la 'graine' du générateur aléatoire.
# https://fr.wikipedia.org/wiki/Graine_al%C3%A9atoire
# In[33]:
np.random.rand(12)
# Maintenant le résultat est toujours le même pour une même graine (en: *seed*) si on relance la cellule plusieurs fois:
# In[34]:
np.random.seed(seed=33)
np.random.rand(12)
# Affichage de l'histogramme des tirages
# In[35]:
a = np.random.randn(10000)
plt.figure(figsize=(5,2))
plt.subplot(1, 2, 1)
plt.hist(a, bins=40, density=True)
plt.title('Histogramme (effectifs)')
plt.ylabel('Effectifs')
plt.subplot(1, 2, 2)
plt.hist(a + 10, bins=40, density=True)
plt.title('Histogramme (densité)')
plt.ylabel('Densité')
plt.tight_layout() # évite certains chevauchement de noms d'axes
# In[36]:
fig, axes = plt.subplots(2, 1, sharex='col')
axes[0].hist(a, bins=40, density=False)
axes[0].set_ylabel('Effectifs')
axes[0].set_title('Histogramme (effectifs)')
axes[1].hist(a + 10, bins=40, density=True)
axes[1].set_ylabel('Densité')
axes[1].set_title('Histogramme (densité)')
plt.tight_layout()
# #### `diag`
# In[37]:
# une matrice diagonale
A = np.diag([1,2,3])
A [1,2] = 17
A
np.diag(A)
# In[38]:
# une matrice diagonale avec décalage par rapport à la diagonale principale
np.diag([1,2,3], k=1)
# In[39]:
# np.diag?
my_diag = np.array([0,0,0])
print(my_diag.shape)
print(M.shape)
np.diag(my_diag)
np.fill_diagonal(M,my_diag)
M
# #### `diag`
#
# Cette fonction permet (aussi!) d'extraire la diagonale ou une sous-diagonale d'un *array* :
# In[40]:
print(A)
print(np.diag(A))
# In[41]:
np.diag(A, -1)
# #### `zeros`, `ones` et `full`
# In[42]:
np.zeros((3,), dtype=int) # attention zeros(3,3) est FAUX
# In[43]:
zero_mat_float = np.zeros((3,4,6))
print(zero_mat_float.dtype)
print(np.zeros((3,), dtype=int).dtype)
zero_mat_float.shape
# In[44]:
np.ones((3,)).shape
# In[45]:
print(np.zeros((3,), dtype=int))
print(np.zeros((1, 3), dtype=int))
print(np.zeros((3, 1), dtype=int))
# In[46]:
np.full((5,4), 9)
# ## Fichiers d'Entrées/Sorties (E/S)
# ### Fichiers séparés par des virgules (CSV)
#
# Un format fichier classique est le format CSV (Comma-Separated Values).
# Pour lire de tels fichiers on peut utiliser `numpy.genfromtxt`, mais on utilisera surtout le module `pandas` par la suite pour cela.
# A l'aide de `numpy.savetxt` on peut enregistrer un *array* `numpy` dans un fichier txt:
# In[50]:
M = np.random.rand(3,3)
print(M)
np.savetxt("random-matrix.txt", M) # regader dans votre dossier, un nouveau fichier est apparu
# In[51]:
MM = np.genfromtxt('random-matrix.txt') # on peut alors générer un array depuis un fichier texte
print(MM)
# ### Format de fichier `numpy` natif (`.npy`)
#
# Pour sauvegarder et recharger des *array* `numpy` : `numpy.save` et `numpy.load` :
# In[52]:
np.save("random-matrix.npy", M)
get_ipython().system('cat random-matrix.npy')
# In[53]:
N = np.load("random-matrix.npy")
N
# ## Autres propriétés des *arrays* `numpy`
# In[54]:
M
# In[55]:
M.dtype
# In[56]:
M.itemsize # octets par élément
# In[57]:
M.nbytes # nombre d'octets
np.save("random-matrix.npy", M)
# In[58]:
M.nbytes / M.size
# In[59]:
M.ndim # nombre de dimensions
# In[60]:
print(np.zeros((3,), dtype=int).ndim)
print(np.zeros((1, 3), dtype=int).ndim)
print(np.zeros((3, 1), dtype=int).ndim)
# ## Manipulation d'*arrays*
# ### Indexation
# In[61]:
# v est un vecteur, il n'a qu'une seule dimension -> un seul indice
v[3]
# In[62]:
# M est une matrice, ou un array à 2 dimensions -> deux indices
M[1,1]
# Contenu complet :
# In[63]:
M
# La deuxième ligne :
# In[64]:
M[1]
# On peut aussi utiliser `:`
# In[65]:
M[1,:].shape # 2 ème ligne (indice 1)
# In[66]:
M[:,1] # 2 ème colonne (indice 1)
# In[67]:
print(M.shape)
print(M[1,:].shape, M[:,1].shape)
# On peut assigner des nouvelles valeurs à certaines cellules :
# In[68]:
M[0,0] = 1
# In[69]:
M
# In[70]:
# on peut aussi assigner des lignes ou des colonnes
M[1,:] = -1
M
# In[71]:
M[1,:] = [1, 2, 3]
M
# ## *Slicing* ou accès par tranches
#
# Le *Slicing* fait référence à la syntaxe `M[start:stop:step]` pour extraire une partie d'un *array* :
# In[72]:
A = np.array([1,2,3,4,5])
A
# In[73]:
A[1:3]
# Les tranches sont modifiables :
# In[74]:
A[1:3] = [-2,-3]
A
# On peut omettre n'importe lequel des argument dans `M[start:stop:step]`:
# In[75]:
A[::] # indices de début, fin, et pas avec leurs valeurs par défaut
# In[76]:
A[::2] # pas = 2, indices de début et de fin par défaut
# In[77]:
A[:3] # les trois premiers éléments
# In[78]:
A[3:] # à partir de l'indice 3
# In[79]:
M = np.arange(12).reshape(4, 3)
print(M)
# On peut utiliser des indices négatifs :
# In[80]:
A = np.array([1,2,3,4,5])
# In[81]:
A[-1] # le dernier élément
# In[82]:
A[-3:] # les 3 derniers éléments
# Le *slicing* fonctionne de façon similaire pour les *array* multi-dimensionnels
# In[83]:
A = np.array([[n+m*10 for n in range(5)] for m in range(5)])
A
# In[84]:
A[1:4, 1:4] # sous-tableau
# In[85]:
# sauts de deux en deux:
A[::2, ::2]
# In[86]:
A
# In[87]:
print(A[[0, 1, 3]])
print(A[[0, 1, 3],:])
print(A[:,[0, 1, 3]])
# ### Indexation avancée (*fancy indexing*)
#
# Lorsque qu'on utilise des listes ou des *array* pour définir des tranches :
# In[88]:
row_indices = [1, 2, 3]
print(A)
print(A[row_indices])
print(A.shape)
# In[89]:
print(A[[1, 2], [3, 4]])
# In[90]:
A[np.ix_([1, 2], [3, 4])] = 0
print(A)
# On peut aussi utiliser des masques binaires :
# In[91]:
B = np.arange(5)
B
# In[92]:
row_mask = np.array([True, False, True, False, False])
print(B[row_mask])
# print(B[[0,2]])
# In[93]:
# ou de façon équivalente
row_mask = np.array([1,0,1,0,0], dtype=bool)
B[row_mask]
# In[94]:
# ou encore
a = np.array([1, 2, 3, 4, 5])
print(a < 3)
print(B[B > 3])
print(B)
# In[95]:
print(A)
print(A[:, a < 3])
# ## Extraction de données à partir d'*arrays* et création d'*arrays*
# #### `where`
#
# Un masque binaire peut être converti en indices de positions avec `where`
# In[96]:
x = np.arange(0, 10, 0.5)
print(x)
mask = (x > 5) * (x < 7.5)
print(mask)
indices = np.where(mask)
indices
# In[97]:
print(x[indices]) # équivalent à x[mask]
print(x[mask])
# ## Algèbre linéaire
#
# La performance des programmes écrit en `Python/numpy` dépend de la capacité à vectoriser les calculs (les écrire comme des opérations sur des vecteurs/matrices) en évitant au maximum les boucles `for/while`.
# ### Opérations scalaires
#
# On peut effectuer les opérations arithmétiques habituelles pour multiplier, additionner, soustraire et diviser des *arrays* avec/par des scalaires :
# In[98]:
v1 = np.arange(0, 5)
print(v1)
# In[99]:
v1 * 2
# In[100]:
v1 + 2
# In[101]:
plt.figure()
plt.subplot(1,2,1)
plt.plot(v1 ** 2,'g--', label='$y = x^2$')
plt.legend(loc=0)
plt.subplot(1,2,2)
plt.plot(np.sqrt(v1), 'r*-', label='$y = \sqrt{x}$')
plt.legend(loc=2)
plt.show()
# ### Visualiser des matrices
# In[102]:
C = np.random.rand(300,200)
plt.figure()
plt.imshow(C)
plt.colorbar()
plt.show()
# In[103]:
n_samples = 300
C = np.random.rand(n_samples,200)
plt.figure()
D = np.dot(C.T,C) # mulitplication de C^T avec C : = C^T C
D = C.T@C # idem mulitplication de C^T avec C : = C^T C
D = C.T.dot(C) # idem mulitplication de C^T avec C : = C^T C
plt.imshow(D)
plt.colorbar()
plt.show()
# ### Opérations terme-à-terme sur les *arrays*
#
# Les opérations par défaut sont des opérations **terme-à-terme** (contrairement à Matlab par exemple).
# In[104]:
A = np.array([[n+m*10 for n in range(5)] for m in range(5)])
print(A)
# In[105]:
A * A # multiplication terme-à-terme
# In[106]:
S1=(A + A.T) / 2 # la projection de A sur les matrices symétriques
# In[107]:
A1=(A - A.T) / 2 # la projection de A sur les matrices anti-symétriques
print(A1)
# In[108]:
print(A1+S1) # donne A
np.trace(S1.dot(A1)) # A1 et S1 sont orthogonaux pour le pdt scalaire
#tr(S1^T A1)
# In[109]:
print(v1)
print(v1 * v1)
# En multipliant des *arrays* de tailles compatibles, on obtient des multiplications terme-à-terme par ligne :
# In[110]:
A.shape, v1.size
# In[111]:
print(A)
print(v1)
print(A * v1)
# EXERCICE : Sans utiliser de boucles (`for/while`),
#
#
# * Créer une matrice (5x6) aléatoire
# * Remplacer une colonne sur deux par sa valeur moins le double de la colonne suivante
# * Remplacer les valeurs négatives par 0 en utilisant un masque binaire
#
# In[112]:
# XXX todo
# ### Algèbre matricielle
#
# Comment faire des multiplications de matrices ? Deux façons :
#
# * en utilisant les fonctions `dot`;
# * en utilisant le type `@/matmul` (pour les versions récentes de `numpy`).
#
# In[113]:
print(A.shape)
print(A)
print(type(A))
# In[114]:
print(np.dot(A, A)) # multiplication matrice
print(A.dot(A)) # multiplication matrice
print(A * A) # multiplication élément par élément
# In[115]:
A.dot(v1)
# In[116]:
np.dot(v1, v1)
# Voir également les fonctions : `inner`, `outer`, `cross`, `kron`, `tensordot`. Utiliser par exemple `help(kron)`.
# ### Transformations d'*arrays* ou de matrices
# * Plus haut `.T` a été utilisé pour transposer `v`
# * On peut aussi utiliser la fonction `transpose`
#
# **Autres transformations :**
# In[117]:
C = np.array([[1j, 2j], [3j, 4j]])
C
# In[118]:
np.conj(C) # conjuguée complexe
# Transposée conjuguée :
# In[119]:
C.conj().T
# Parties réelles et imaginaires :
# In[120]:
np.real(C) # équivalent à C.real
# In[121]:
np.imag(C) # équivalent à C.imag
# Argument et module :
# In[122]:
np.angle(C+1)
# In[123]:
np.abs(C)
# ### Caclul matriciel
# #### Analyse de données
#
# `numpy` propose des fonctions pour calculer certaines statistiques des données stockées dans des *arrays* :
# In[124]:
data = np.vander([1, 2, 3, 4], increasing=True) # Matrice de Vandermonde
print(data)
# #### `moyenne`
# In[125]:
print(np.mean(data))
print(np.mean(data, axis=0))
print(np.mean(data, axis=1))
# In[126]:
# la moyenne de la troisième colonne
np.mean(data[:,2])
# #### variance et écart type
# In[127]:
print(np.var(data[:, 2]), np.std(data[:, 2]))
# In[128]:
# ddof : Delta Degrees of Freedom
print(np.var(data[:, 2], ddof=1), np.std(data[:, 2], ddof=1))
# #### min et max
# In[129]:
data[:,2].min()
# In[130]:
data[:,2].max()
# In[131]:
data[:,2].sum()
# In[132]:
data[:,2].prod()
# #### `sum`, `prod`, et `trace`
# In[133]:
d = np.arange(0, 10)
d
# In[134]:
# somme des éléments
np.sum(d)
# ou encore :
# In[135]:
d.sum()
# In[136]:
# produit des éléments
np.prod(d+1)
# In[137]:
# somme cumulée
np.cumsum(d)
# In[138]:
# produit cumulé
np.cumprod(d+1)
# In[139]:
# équivalent à diag(A).sum()
np.trace(data)
# ### EXERCICE : Calculer une approximation de $\pi$ par la formule de Wallis sans boucle `for`, mais avec `numpy`
#
# \begin{align}
# \text{Formule de Wallis:}\quad \pi&= 2 \cdot \prod_{n=1}^{\infty }\left({\frac{4 n^{2}}{4 n^{2} - 1}}\right)
# \end{align}
# ### Calculs aves données multi-dimensionnelles
#
# Pour appliquer `min`, `max`, etc., par lignes ou colonnes :
# In[143]:
m = np.random.rand(3,4)
m
# In[144]:
# max global
m.max()
# In[145]:
# max dans chaque colonne
m.max(axis=0)
# In[146]:
# max dans chaque ligne
m.max(axis=1)
# Plusieurs autres méthodes des classes `array` et `matrix` acceptent l'argument (optional) `axis` keyword argument.
# ## Copy et "deep copy"
#
# Pour des raisons de performance `Python` ne copie pas automatiquement les objets (par exemple passage par référence des paramètres de fonctions).
# In[147]:
A = np.array([[0, 2],[ 3, 4]])
A
# In[148]:
B = A
# In[149]:
# ATTENTION: changer B affecte A
B[0,0] = 10
B
# In[150]:
A
# In[151]:
B = A
print(B is A)
# Pour éviter ce comportement, on peut demander une *copie profonde* (en: *deep copy*) de `A` dans `B`
# In[152]:
B = A.copy() # identique à B = np.copy(A)
# In[153]:
# maintenant en modifiant B, A n'est plus affecté
B[0,0] = -5
B
# In[154]:
A # A n'est pas modifié cette fois!
# ### EXERCICE : interpréter ce qui se passe dans l'exemple ci-dessous
#
# In[155]:
print(A - A[:,0]) # FAUX
print(A - A[:,0].reshape((2, 1))) # OK
# ## Changement de forme et de taille, et concaténation des *arrays*
#
#
# In[156]:
A
# In[157]:
n, m = A.shape
# In[158]:
B = A.reshape((1, n * m))
B
# In[159]:
B[0, 0:5] = 5 # modifier l'array
B
# In[160]:
A
# ### Attention !
#
# La variable originale est aussi modifiée ! B n'est qu'une nouvelle *vue* de A.
# Pour transformer un *array* multi-dimmensionel en un vecteur. Mais cette fois-ci, une copie des données est créée :
# In[161]:
A = np.array([[0, 2],[ 3, 4]])
B = A.flatten()
print(A,B)
# In[162]:
B[0:5] = 10
B
# In[163]:
A # A ne change pas car B est une copie de A
# ### Ajouter une nouvelle dimension avec `newaxis`
#
# Par exemple pour convertir un vecteur en une matrice ligne ou colonne :
# In[164]:
v = np.array([1,2,3])
# In[165]:
np.shape(v)
# In[166]:
# créer une matrice à une colonne à partir du vectuer v
v[:, np.newaxis]
# In[167]:
v[:,np.newaxis].shape
# In[168]:
# créer une matrice à une ligne à partir du vectuer v
v[np.newaxis,:].shape
# ### Concaténer, répéter des *arrays*
#
# En utilisant les fonctions `repeat`, `tile`, `vstack`, `hstack`, et `concatenate`, on peut créer des vecteurs/matrices plus grandes à partir de vecteurs/matrices plus petites :
#
# #### `repeat` et `tile`
# In[169]:
a = np.array([[1, 2], [3, 4]])
a
# In[170]:
# répéter chaque élément 3 fois
np.repeat(a, 3) # résultat 1-d
# In[171]:
# on peut spécifier l'argument axis
np.repeat(a, 3, axis=1)
# Pour répéter la matrice, il faut utiliser `tile`
# In[172]:
# répéter la matrice 3 fois
np.tile(a, 3)
# #### `concatenate`
# In[173]:
b = np.array([[5, 6]])
# In[174]:
np.concatenate((a, b), axis=0)
# In[175]:
np.concatenate((a, b.T), axis=1)
# #### `hstack` et `vstack`
# In[176]:
np.vstack((a,b))
# In[177]:
np.hstack((a,b.T))
# ## Itérer sur les éléments d'un *array*
#
# * Dans la mesure du possible, il faut éviter l'itération sur les éléments d'un *array* : c'est beaucoup plus lent que les opérations vectorisées
# * Mais il arrive que l'on n'ait pas le choix...
# In[178]:
v = np.array([1,2,3,4])
for element in v:
print(element)
# In[179]:
M = np.array([[1, 2], [3, 4]])
for row in M:
print("row", row)
for element in row:
print(element)
# Pour obtenir les indices des éléments sur lesquels on itère (par exemple, pour pouvoir les modifier en même temps) on peut utiliser `enumerate` :
# In[180]:
for row_idx, row in enumerate(M):
print("row_idx", row_idx, "row", row)
for col_idx, element in enumerate(row):
print("col_idx", col_idx, "element", element)
# update the matrix M: square each element
M[row_idx, col_idx] = element ** 2
# In[181]:
# chaque élément de M a maintenant été élevé au carré
M
# ## Utilisation d'*arrays* dans des conditions
#
# Losqu'on s'intéresse à des conditions sur tout on une partie d'un *array*, on peut utiliser `any` ou `all` :
# In[182]:
M
# In[183]:
if (M > 5).any():
print("Au moins un élément de M est plus grand que 5.")
else:
print("Aucun élément de M n'est plus grand que 5.")
# In[184]:
if (M > 5).all():
print("Tous les éléments de M sont plus grands que 5.")
else:
print("Tous les éléments de M sont plus petits que 5.")
# ## *Type casting*
#
# On peut créer une vue d'un autre type que l'original pour un *array*
# In[185]:
M = np.array([[-1,2], [0,4]])
M.dtype
# In[186]:
M2 = M.astype(float)
M2
# In[187]:
M2.dtype
# In[188]:
M3 = M.astype(bool)
M3
# ## Pour aller plus loin
#
# * http://numpy.scipy.org
# * http://scipy.org/Tentative_NumPy_Tutorial
# * http://scipy-lectures.org/ - une bible pour les sujets avancés (e.g. matrice sparse)
# * http://scipy.org/NumPy_for_Matlab_Users - Un guide pour les utilisateurs de MATLAB.