Olivier Guibé - LMRS, fortement inspiré d'un document de Pierre Navaro (IRMAR).

Que fait Numpy ?

  • objet de type ndarray multi-dimensionnel
  • vecteurs et matrices, opérations vectorisées
  • des équivalents à Scilab et Matlab
  • initialement développé par Travis Oliphant, Numpy 1.0 Octobre, 2006.
  • Le site SciPy.org très riche
  • NumPy orienté objet (hors de mes compétences)
  • des routines pour
    • manipuler la forme/taille des tableaux
    • extraire, classer
    • entrée/sortie
    • FFT
    • algèbre linéaire de base (pas LU)
    • statistiques de base
    • des lois usuelles

Commencer avec NumPy

  • Ce qui n'est pas conseillé (mais fatigue moins les mains)
    from numpy import *
    
  • Il est plus facile de faire des import explicites
    • plt pour matplotlib.pyplot
    • np pour numpy
    • sp pour scipy
In [2]:
import numpy as np
print(np.__version__)
1.13.3

Il y a de l'aide en anglais

In [1]:
#np.nd<TAB>
In [4]:
print(np.eye.__doc__)
    Return a 2-D array with ones on the diagonal and zeros elsewhere.

    Parameters
    ----------
    N : int
      Number of rows in the output.
    M : int, optional
      Number of columns in the output. If None, defaults to `N`.
    k : int, optional
      Index of the diagonal: 0 (the default) refers to the main diagonal,
      a positive value refers to an upper diagonal, and a negative value
      to a lower diagonal.
    dtype : data-type, optional
      Data-type of the returned array.

    Returns
    -------
    I : ndarray of shape (N,M)
      An array where all elements are equal to zero, except for the `k`-th
      diagonal, whose values are equal to one.

    See Also
    --------
    identity : (almost) equivalent function
    diag : diagonal 2-D array from a 1-D array specified by the user.

    Examples
    --------
    >>> np.eye(2, dtype=int)
    array([[1, 0],
           [0, 1]])
    >>> np.eye(3, k=1)
    array([[ 0.,  1.,  0.],
           [ 0.,  0.,  1.],
           [ 0.,  0.,  0.]])

    

Tableaux Numpy: ndarray class.

  • Par rapport aux listes :
    • NumPy arrays : taille fixée à la création (un seul type dans un ndarray)
    • routines Numpy exécutées en code compilé donc rapide.
  • Numpy est très utilisé dès que cela concerne le calcul
In [5]:
a = np.array([0,1,2,3])  #  liste
b = np.array((4,5,6,7))  #  tuple
c = np.matrix('8 9 0 1') #  string (à la matlab)
In [6]:
print(a,b,c)
[0 1 2 3] [4 5 6 7] [[8 9 0 1]]

Par défaut les opérations sont termes à termes

In [7]:
a*b,a+b
Out[7]:
(array([ 0,  5, 12, 21]), array([ 4,  6,  8, 10]))
In [8]:
5*a, 5+a
Out[8]:
(array([ 0,  5, 10, 15]), array([5, 6, 7, 8]))
In [9]:
a@b, np.dot(a,b)  # produit scalaire
Out[9]:
(38, 38)

Propriétés des tableaux NumPy

In [16]:
a = np.array([1,2,3,4,5], dtype=float) # création avec type flottant
In [17]:
type(a) # le type
Out[17]:
numpy.ndarray
In [18]:
a.dtype # le type des éléments
Out[18]:
dtype('float64')
In [19]:
a.shape # retourne un tuple des dimensions selon les axes
Out[19]:
(5,)
In [20]:
np.size(a), a.size # le nombre d'éléments
Out[20]:
(5, 5)
In [21]:
a.ndim  # la dimension
Out[21]:
1
  • Toujours utiliser shape ousize pour les ndarray plutôt que len
  • len retourne le nombre d'éléments de la liste (donc en 2D le nombre de lignes)
In [24]:
b=np.array([[1, 2, 4],[6,7,8]], dtype=float)
print(b)
print("len ", len(b),"; size ", np.size(b),"; size(axis=1)", np.size(b,axis=1),"; size(axis=0)",
      np.size(b,axis=0), "; shape ", np.shape(b))
[[ 1.  2.  4.]
 [ 6.  7.  8.]]
len  2 ; size  6 ; size(axis=1) 3 ; size(axis=0) 2 ; shape  (2, 3)

Des fonctions pour créer les vecteurs/matrices

In [36]:
x = np.zeros((2,),dtype=float)
x
Out[36]:
array([ 0.,  0.])

les commandes empty, empty_like, ones, ones_like, zeros, zeros_like, full, full_like

In [37]:
n = 5
a = np.zeros(n*n,dtype=np.double).reshape(n,n) # 1D nxn reshaper en 2d n x n !
b = np.zeros((n,n),dtype=np.double)
a == b
Out[37]:
array([[ True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True]], dtype=bool)

Accès aux éléments

In [38]:
a = np.array([1,2,3,4,5])
print(a.dtype)
int64
In [40]:
a[0] = 10 
a, a.dtype
Out[40]:
(array([10,  2,  3,  4,  5]), dtype('int64'))
In [41]:
a.fill(0) # plus rapide que a[:] = 0
a
Out[41]:
array([0, 0, 0, 0, 0])

Slicing x[début:fin:step]

  • permet d'extraire une partie des tableaux
  • début est inclus mais fin n'est pas inclus (tout le monde fait l'erreur)
  • comme pour les listes, step vaut 1 par défaut et peut être négatif.
In [29]:
a = np.array([10,11,12,13,14])
In [30]:
a[:2], a[-5:-3], a[0:2], a[-2:] # avec des indices négatifs
Out[30]:
(array([10, 11]), array([10, 11]), array([10, 11]), array([13, 14]))
In [31]:
a[::2], a[::-1]
Out[31]:
(array([10, 12, 14]), array([14, 13, 12, 11, 10]))

Exercice:

  • Méthode des trapèzes pour approcher $\int_a^b f(x)dx$ $$ h \sum_{k=0}^{n-1} \frac{ f(a+kh)+ f(a+(k+1)h)}{2} $$ avec $h=(b-a)/n$. Sans boucle bien sûr !
In [25]:
def trap(f,a,b,n):
    xpt=np.linspace(a,b,n+1)
    return (b-a)/n*(np.sum(f(xpt[1:-1]))+.5*(f(a)+f(b)))
def f(x):
    return x
print(trap(f,0,1,10))
def f(x):
    return np.sin(x)
print(trap(f,0,np.pi,50))
                     
0.5
1.99934198308

Exercice : idem avec la méthode de Simpson

Tableau multi-dimensionel

In [26]:
a = np.arange(4*3).reshape(4,3) # NumPy array
l = [[0,1,2],[3,4,5],[6,7,8],[9,10,11]] # liste Python
In [27]:
print(a)
print(l)
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]
In [28]:
l[-1][-1] # [i][j] est fastidieux
Out[28]:
11
In [29]:
print(a[-1,-1])  # avec Numpy
print(a[0,0])    # le 1er élément
print(a[1,:])    # la seconde ligne
print(a[:,1])    # la seconde colonne
11
0
[3 4 5]
[ 1  4  7 10]
In [30]:
print(a[1]) # 2ème ligne avec 2d array
print(a[:,-1])  # dernière colonne
[3 4 5]
[ 2  5  8 11]

Les slices sont les références mémoires

  • Changer les valeurs dans un slice (b=a[2:5] puis b[0]=5) change le tableau original (donc a)
In [31]:
a = np.arange(10)
b = a[3:6]
b  # est une vue de `a`
Out[31]:
array([3, 4, 5])
In [32]:
b[0] = -1
a  # changer la vue change a
Out[32]:
array([ 0,  1,  2, -1,  4,  5,  6,  7,  8,  9])
  • Ce comportement évite de surcharger la mémoire.
  • Il est possible de copier mais en le demandant poliment
In [33]:
c = a[7:8].copy() # une copie explicite SVP, merci
c[0] = -1 
a
Out[33]:
array([ 0,  1,  2, -1,  4,  5,  6,  7,  8,  9])

En vrac on peut

  • redimensionner les tableaux (pourvu que le nombre d'élements soit cohérent)
  • ordonner
  • faire des sommes (sur les lignes, sur les colonnes)
  • extraire
  • jouer avec les booléens (a>1)
  • tranposer les tableaux
  • résoudre des systèmes linéaires

Numpy fournit un type matrix

C'est un type spécifique 2D qui possède certains opérateurs spécifiques comme $*$ (multiplication matricielle) et $**$ (élévation à la puissance). Personnellement je ne l'utilise pas...

In [62]:
m = np.matrix('1 2; 3 4') #à la Matlab
m
Out[62]:
matrix([[1, 2],
        [3, 4]])
In [63]:
a = np.matrix([[1, 2],[ 3, 4]]) #à la Python
a
Out[63]:
matrix([[1, 2],
        [3, 4]])
In [64]:
a = np.arange(1,4)
b = np.mat(a) # pas de copie !
b, np.may_share_memory(a,b)
Out[64]:
(matrix([[1, 2, 3]]), True)
In [65]:
a = np.matrix([[1, 2, 3],[ 3, 4, 5]])
a * b.T # produit matrice x vecteur 
Out[65]:
matrix([[14],
        [26]])
In [ ]:
m * a # multiplication matricielle
Out[ ]:
matrix([[ 7, 10, 13],
        [15, 22, 29]])

De l'aléatoire (enfin !)

Numpy propose évidemment les générateurs aléatoires (flottant, entier, vectorisé), des permutations aléatoires, les lois de distributions usuelles

Random

Des flottants aléatoires (uniforme dans $[0,1)$) tout simplement

In [4]:
import numpy.random as rd
print(rd.rand(1)) # un seul
print(rd.rand(4)) # un np.array 1D
print(rd.rand(4,5)) # un np.array 2D
[ 0.19864388]
[ 0.24828969  0.41575007  0.42803682  0.43093977]
[[ 0.79416312  0.88837971  0.42530676  0.47013251  0.25334582]
 [ 0.47566375  0.3812407   0.32629854  0.81334949  0.62524664]
 [ 0.07592473  0.9394389   0.657272    0.74258723  0.97345617]
 [ 0.01492761  0.51825439  0.61435279  0.79817972  0.27807372]]

Des entiers aléatoires dans $[m,M]$ (inclus)

In [36]:
print(rd.randint(-10,5))
print(rd.randint(-5,5,4)) #vecteur taille 4
print(20*np.eye(10)+rd.randint(-10,10,(10,10))) # une matrice à coefficients entiers aléatoires 
                                            #et à diagonale strictement dominante (normalement)
print(rd.randint(-10,10,(2,2,2)))
-7
[ 3  3  4 -3]
[[ 10.   6. -10.  -6.   0.   0.   8.  -4.  -7.   3.]
 [  0.  23.   0.   5.  -8.  -5.  -3.   8.  -2.   4.]
 [ -8.  -6.  17.  -7.   2.   0.   9.  -6.  -8.   6.]
 [ -4.  -7.  -7.  24.   7.   3. -10. -10.   0.   3.]
 [  1.  -1.   6.  -7.  16.   2. -10.   7.   5.  -8.]
 [ -1.  -6.   9.   9.   6.  27.  -7.   5.   8.  -4.]
 [ -3.  -1.   2.   2.  -2.   0.  29. -10.   5.  -3.]
 [ -2.  -8.   4.  -5.   5.   0.   8.  19.  -2.   9.]
 [  4.   1.   5.   3.   7.   4.   1.  -9.  18.   8.]
 [ -5.  -2.   1.   6.   9.   2.  -9.   2.  -7.  26.]]
[[[-4 -2]
  [ 9 -2]]

 [[ 7 -8]
  [ 9  5]]]

Pour générer suivant la loi normale $\mathcal{N}(3,8)$ (merci de ne pas poser de question):

In [17]:
8**.5 * rd.randn(2, 4) + 3
Out[17]:
array([[  3.40410065,  -0.13747735,  10.5533109 ,   3.74901395],
       [  5.12051563,   3.21453777,   4.19535599,   7.85135753]])

Exercice: Aire par une méthode de Monte Carlo,

Calculer l'aire de la réunion des disques $D_1$ (centre $(.5,.5)$ rayon $1/3$) et $D_2$ (centre $(.2,.5)$ rayon $.1$)

In [6]:
# méthode naïve
N , p = 10000, 0
A, B = np.array([.5,.5]), np.array([.2,.5])
for i in range(N):
    x= rd.rand(2)
    if (x-A)@(x-A)<=1/9 or (x-B)@(x-B)<=.01:
        p=p+1
print(p/N)
0.3608

Faire une version moins naïve, vectorisée...

In [23]:
# version vectorisée
# Bonus : tracé
import matplotlib.pyplot as plt
N = 100000
A, B = np.array([.5,.5]), np.array([.2,.5])
M = rd.rand(N,2)
MD1 = ((M[:,0]-A[0])**2+(M[:,1]-A[1])**2)<1/9
MD2 = ((M[:,0]-B[0])**2+(M[:,1]-B[1])**2)<.01
Munion = MD1 + MD2  # ici on vérifie qu'avec Numpy np.array([True])+np.array([True])=np.array([True])
                    # car en Python True + True = 2
print(sum(Munion)/N)
plt.rcParams['figure.figsize'] = (10.0, 10.0) # figure plus grande
plt.scatter(M[np.nonzero(Munion)][:,0],M[np.nonzero(Munion)][:,1])
plt.scatter(M[np.nonzero(Munion-1)][:,0],M[np.nonzero(Munion-1)][:,1])
plt.show()
0.35697

Distributions usuelles

Évidemment, Numpy sait générer les lois binomiales, exponentielles, Dirichlet, géométrique, multinomiales, de Poisson (merci de ne poser aucune question)

In [27]:
mu, sigma = 0, 0.1 
s = np.random.normal(mu, sigma, 1000)
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = (10.0, 6.0)
count, bins, ignored = plt.hist(s, 30, normed=True)
plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) * np.exp( - (bins - mu)**2 / (2 * sigma**2) ), linewidth=2, color='r')
plt.show();