Le librerie python numpy e matplotlib

Numpy

La libreria numpy consente di lavorare con vettori e matrici in maniera più efficiente e veloce di quanto non si possa fare con le liste e le liste di liste (matrici).
Il costrutto di base è l'ndarray, che può avere dimensioni qualunque e tipi corrispondenti a quelli classici del C o del Fortran, che restano i linguaggi tuttora più utilizzati per il calcolo numerico.
Uno dei punti di forza di numpy è di poter lavorare sui vettori sfruttando le ottimizzazioni di calcolo vettoriale del processore della macchina. Ciò rende particolarmente efficiente i calcoli, rispetto alle liste.
Esempio: genera un vettore di 100 numeri casuali. Nota: la libreria numpy ha al suo interno un'altra libreria che contiene le funzioni per generare numeri random secondo diverse funzioni di distribuzione. Una di queste è la funzione "random_sample", che genera una sequenza di numeri tra zero e uno (escluso!) secondo una distribuzione piatta (cioé tutti i numeri sono equiprobabili!). Se chiamata con una tupla, genera una matrice di numeri random.

In [1]:
import numpy as np
vett=np.random.random_sample(100)
print(vett)
[ 0.96228399  0.03139621  0.19578969  0.26993071  0.92018016  0.16208287
  0.0040099   0.82571195  0.86796374  0.34062177  0.75651136  0.1575833
  0.17877441  0.57934359  0.42546442  0.30452725  0.61713464  0.72417378
  0.07530492  0.47891027  0.51107585  0.16746823  0.84737773  0.85375622
  0.18206534  0.28355896  0.96556426  0.45350271  0.11766705  0.00843031
  0.99499669  0.31427971  0.27397043  0.64377564  0.03704476  0.01057269
  0.34957699  0.28417194  0.51071693  0.14454131  0.00299339  0.60846012
  0.52632421  0.66928301  0.21127399  0.05466509  0.26632023  0.6687517
  0.90084082  0.49969179  0.3941352   0.7595248   0.49432655  0.01508642
  0.59176201  0.26636592  0.92083538  0.23404995  0.44768755  0.9447307
  0.65153745  0.03353806  0.90696816  0.59416394  0.34253737  0.53617159
  0.39720257  0.24232219  0.56676691  0.64868304  0.95898227  0.94152249
  0.31870322  0.55280255  0.98320652  0.65435545  0.36912929  0.22156457
  0.32472385  0.57130386  0.76026714  0.05367217  0.59259612  0.13726167
  0.97178712  0.92299704  0.09614643  0.82713076  0.98544573  0.50402718
  0.05118871  0.44708785  0.08639547  0.37824622  0.62703377  0.01248637
  0.51096113  0.74407436  0.59691485  0.74651743]

Lo stesso risultato si può ottenere, con una sintassi più semplice, come segue:

In [2]:
import numpy as np
from numpy import random as r
v=r.random_sample(10)
print(v)
w=r.random_sample((10,2))
print(w)
[ 0.42053131  0.75059598  0.79185145  0.3675981   0.79851675  0.7984137
  0.48144414  0.26799548  0.44733019  0.57473723]
[[ 0.62464664  0.08821883]
 [ 0.84080625  0.97847839]
 [ 0.31252662  0.66233158]
 [ 0.22896765  0.47992843]
 [ 0.59559521  0.92649603]
 [ 0.08023419  0.1249307 ]
 [ 0.27837347  0.40893471]
 [ 0.80346918  0.0755471 ]
 [ 0.47626741  0.8487704 ]
 [ 0.52154581  0.25150637]]

Ci sono un numero enorme di funzioni predefinite in numpy che calcolano automaticamente diverse quantità.
Ad esempio:
  • mean(): calcola la media di un vettore o matrice;
  • sum(): calcola la somma di un vettore o matrice;
  • std(): calcola la deviazione standard;
  • min(): trova il minimo nel vettore o matrice;
  • max(): trova il massimo;
  • ndim: dimensione del vettore o matrice;
  • shape: restituisce una tupla con la "forma" del vettore o matrice;
  • size: restituisce la dimensione totale del vettore (=ndim) o della matrice;
  • itemsize: scrive il numero di byte di ogni elemento (cioé lo spazio occupato in memoria da ciascun elemento;
  • dtype: scrive il tipo numpy del dato;
  • zeros(num): scrive un vettore di num elementi inizializzati a zero;
  • zeros((num1, ..., num_n)): come sopra, ma crea una matrice di dimensione num1 x ... x num_n;
  • arange(start,stop,step): genera un intervallo di valori (interi o reali, a seconda dei valori di start, ecc.) intervallati di step. Nota che i dati vengono generati nell'intervallo aperto [start,stop)!
  • linstep(start,stop,num): genera un intervallo di num valori interi o reali a partire da start fino a stop (incluso!);
  • astype(tipo): converte l'ndarray nel tipo specificato

Esempi:

In [1]:
import numpy as np
from numpy import random as r
v=r.random_sample(10)
w=np.round(50 * r.random_sample((3,3,4)) + 1)    # Genera una matrice 3x3x4 di numeri random tra 1 e 50!
print("v = ", v)
print("w = ", w)
('v = ', array([ 0.49903666,  0.78888167,  0.52288515,  0.71229   ,  0.53853104,
        0.99185594,  0.80640399,  0.49048668,  0.0834889 ,  0.36008571]))
('w = ', array([[[ 10.,  28.,  10.,  50.],
        [ 23.,   4.,  36.,  51.],
        [ 44.,  35.,  27.,  23.]],

       [[ 17.,  39.,  46.,   4.],
        [ 29.,  32.,   8.,  14.],
        [  2.,  33.,  33.,   7.]],

       [[ 30.,  19.,  44.,   8.],
        [ 11.,  18.,  41.,  47.],
        [ 27.,   8.,  32.,  28.]]]))

In [2]:
print(v.mean())
print(w.mean())
0.579394575201
25.5

In [3]:
print(v.sum())
print(w.sum())
5.79394575201
918.0

In [4]:
print(v.mean() * v.size)
print(w.mean() * w.size)
5.79394575201
918.0

In [5]:
print(v.std())
print(w.std())
0.244222779726
14.3672080331

In [6]:
print(v.min(), v.max())
print(w.min(), w.max())
(0.083488896437416193, 0.99185593989970799)
(2.0, 51.0)

In [7]:
print(v.ndim, v.shape, v.size, v.itemsize, v.dtype)
print(w.ndim, w.shape, w.size, w.itemsize, w.dtype)
ww=w.astype(int)
print(ww.ndim, ww.shape, ww.size, ww.itemsize, ww.dtype)
(1, (10,), 10, 8, dtype('float64'))
(3, (3, 3, 4), 36, 8, dtype('float64'))
(3, (3, 3, 4), 36, 8, dtype('int64'))

In [8]:
x=np.zeros((10,2))
y=np.arange(0.0,2.0*np.pi,0.1)
z=np.linspace(0.0,2.0*np.pi,11)
print(x)
print(y)
print(z)
[[ 0.  0.]
 [ 0.  0.]
 [ 0.  0.]
 [ 0.  0.]
 [ 0.  0.]
 [ 0.  0.]
 [ 0.  0.]
 [ 0.  0.]
 [ 0.  0.]
 [ 0.  0.]]
[ 0.   0.1  0.2  0.3  0.4  0.5  0.6  0.7  0.8  0.9  1.   1.1  1.2  1.3  1.4
  1.5  1.6  1.7  1.8  1.9  2.   2.1  2.2  2.3  2.4  2.5  2.6  2.7  2.8  2.9
  3.   3.1  3.2  3.3  3.4  3.5  3.6  3.7  3.8  3.9  4.   4.1  4.2  4.3  4.4
  4.5  4.6  4.7  4.8  4.9  5.   5.1  5.2  5.3  5.4  5.5  5.6  5.7  5.8  5.9
  6.   6.1  6.2]
[ 0.          0.62831853  1.25663706  1.88495559  2.51327412  3.14159265
  3.76991118  4.39822972  5.02654825  5.65486678  6.28318531]

La funzione array(lista) converte una lista python in un vettore numpy. Viceversa, la funzione tolist() converte l'array numpy in una lista!
Esempio:

In [79]:
xx=x.tolist()
type(xx)
Out[79]:
list
In [80]:
yy=np.array(xx)
type(yy)
Out[80]:
numpy.ndarray

Input e output di ndarrays

numpy include una serie di funzioni semplici per leggere/scrivere dati da/su file. Queste possono essere utilizzate per leggere e scrivere i vettori numpy da/su file in maniera semplice.
  • tofile(nome_file, dtype=float, sep=''): scrive una serie di dati contenuti in un ndarray in un file nome_file, col tipo specificato, usando sep come separatore tra i dati;
  • fromfile(nome_file,dtype=float,count=-1,sep=''): legge una serie di dati da file nome_file in un ndarray, del tipo specificato, per count valori, separati dalla stringa sep;
  • savetxt(nome_file, ... ): salva il vettore numpy specificato nel file nome_file;
  • loadtxt(nome_file, ... ): inserisce in un vettore numpy i dati dal file nome_file;
In [9]:
xx=np.ndarray((11,2),dtype=float)   # Crea una matrice di 11x2 elementi di tipo reale in singola precisione
xx[:,0]=np.linspace(0.0,1.0,11)
xx[:,1]=r.random_sample(11)
print("xx = ", xx)
xx.tofile("dati.dat", sep='\t')     # Scrive i due vettori come 2 colonne di dati in "dati.dat"
yy=np.fromfile("dati.dat", sep='\t')    # Legge le due colonne nel vettore yy
print("yy = ", yy)                      # Nota come ora yy e' un singolo vettore! Cosi' non va bene!
('xx = ', array([[ 0.        ,  0.4202031 ],
       [ 0.1       ,  0.98300377],
       [ 0.2       ,  0.24235446],
       [ 0.3       ,  0.33409948],
       [ 0.4       ,  0.61978779],
       [ 0.5       ,  0.29699894],
       [ 0.6       ,  0.32461147],
       [ 0.7       ,  0.56959388],
       [ 0.8       ,  0.12028092],
       [ 0.9       ,  0.43828402],
       [ 1.        ,  0.42666266]]))
('yy = ', array([ 0.        ,  0.4202031 ,  0.1       ,  0.98300377,  0.2       ,
        0.24235446,  0.3       ,  0.33409948,  0.4       ,  0.61978779,
        0.5       ,  0.29699894,  0.6       ,  0.32461147,  0.7       ,
        0.56959388,  0.8       ,  0.12028092,  0.9       ,  0.43828402,
        1.        ,  0.42666266]))

In [10]:
print("xx = ", xx)
np.savetxt("nuovo_dati.dat", xx)    # savetxt salva le due colonne come prima
yy=np.loadtxt("nuovo_dati.dat")     # ma loadtxt le legge correttamente in una matrice 11x2!
print("yy = ", yy)                  # Cosi' va molto meglio!
('xx = ', array([[ 0.        ,  0.4202031 ],
       [ 0.1       ,  0.98300377],
       [ 0.2       ,  0.24235446],
       [ 0.3       ,  0.33409948],
       [ 0.4       ,  0.61978779],
       [ 0.5       ,  0.29699894],
       [ 0.6       ,  0.32461147],
       [ 0.7       ,  0.56959388],
       [ 0.8       ,  0.12028092],
       [ 0.9       ,  0.43828402],
       [ 1.        ,  0.42666266]]))
('yy = ', array([[ 0.        ,  0.4202031 ],
       [ 0.1       ,  0.98300377],
       [ 0.2       ,  0.24235446],
       [ 0.3       ,  0.33409948],
       [ 0.4       ,  0.61978779],
       [ 0.5       ,  0.29699894],
       [ 0.6       ,  0.32461147],
       [ 0.7       ,  0.56959388],
       [ 0.8       ,  0.12028092],
       [ 0.9       ,  0.43828402],
       [ 1.        ,  0.42666266]]))

E' possibile anche inserire commenti nel file scritto, ad esempio per dare un'idea del contenuto del file, che poi verranno saltati in fase di lettura.
Esempio:

In [11]:
np.savetxt("altri_dati.dat", xx, header="Colonne scritte: x e numeri random", footer="Qui il file finisce...")
yy=np.loadtxt("altri_dati.dat")
print("Ora yy vale: ", yy)
('Ora yy vale: ', array([[ 0.        ,  0.4202031 ],
       [ 0.1       ,  0.98300377],
       [ 0.2       ,  0.24235446],
       [ 0.3       ,  0.33409948],
       [ 0.4       ,  0.61978779],
       [ 0.5       ,  0.29699894],
       [ 0.6       ,  0.32461147],
       [ 0.7       ,  0.56959388],
       [ 0.8       ,  0.12028092],
       [ 0.9       ,  0.43828402],
       [ 1.        ,  0.42666266]]))

Grafici con matplotlib

La libreria matplotlib consente di disegnare grafici 2D in vari modi. Le quantità da plottare sono ndarrays di numpy, ma anche una lista viene trasformata direttamente in un array quando viene passata come argomento del plot.
Esempio:

In [12]:
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np

plt.plot([0.0, 1.0, 4.0, 9.0, 16.0, 25.0])
plt.show()

Una lista unica viene interpretata come la funzione da plottare lungo \(y\), ma se ne passiamo due la prima viene interpretata come asse \(x\). Infine, una stringa come terzo parametro viene interpretata come un descrittore per il colore della linea, lo stile, ecc.
Esempio: disegno di dati contenuti in due liste con linea continua (parametro '-') e pallini rossi (parametro 'or')...

In [14]:
import matplotlib.pyplot as plt
import numpy as np

plt.plot([0.0, 1.0, 2.0, 3.0, 4.0, 5.0],[0.0, 1.0, 4.0, 9.0, 16.0, 25.0],'or-')
plt.show()

Oppure si possono definire due o più ndarrays, da passare al plot:

In [15]:
x=np.linspace(0.0,2.0*np.pi,101)
y=np.sin(x)
z=np.cos(x)
plt.plot(x,y,'o-')
plt.plot(x,z,'^-')
plt.show()

Si possono definire varie funzioni da plottare con un unico comando (nota il colore differente prima del simbolo!):

In [16]:
plt.plot(x,y,'g-',x,z,'b-')
plt.show()
La stringa (opzionale) che appare come terzo parametro e che, se non specificata, disegna una linea blu, descrive in maniera semplice il colore, un eventuale marker e lo stile della linea in un unico parametro.
E' possibile definire i colori comuni con le lettere seguenti:
  • b: blue
  • g: green
  • r: red
  • c: cyan
  • m: magenta
  • y: yellow
  • k: black
  • w: white

Tuttavia, è possibile indicare il colore anche con il nome completo (es.: green), con una sequenza RGB esadecimale (es: #FF0000 rappresenta il rosso!), con una tupla RGB o come toni di griglio tramite una stringa contenente un numero tra 0.0 e 1.0.
Esempio: (seno in verde, coseno in rosso)...

In [17]:
plt.plot(x,y,'g',x,z,'#A0A0A0')
plt.show()

(seno in giallo, coseno in grigio):

In [18]:
plt.plot(x,y,'yellow',x,z,'0.5')
plt.show()
Il parametro successivo, se presente, rappresenta il tipo di linea e l'eventuale punto (eventualmente entrambi, se presenti!).
I simboli corrispondenti sono:
  • -: solid line style
  • --: dashed line style
  • -.: dashed-dotted line style
  • :: dotted line style
  • .: point marker
  • ,: pixel marker
  • o: circle marker
  • v: triangle down marker
  • ^: triangle up marker
  • <b><: triangle left marker
  • >: triangle right marker
  • s: square marker
  • p: pentagon marker
  • *: star marker
  • h: hexagon marker
  • +: plus marker
  • x: x marker
  • D: diamond marker
  • |: vertical line marker
  • _: horizonthal line marker

Esempi:

In [19]:
plt.plot(x,y,'g-',x,z,'b--')
plt.show()

Mettere tutti i grafici in un unico comando di plot è semplice, ma separandoli si può avere una maggiore "flessibilità" nello specificare in maniera fine le caratteristiche della linea utilizzando delle keywords per indicare caratteristiche aggiuntive del grafico.
Esempio:

In [20]:
plt.plot(x,y,color='c',marker='x',linestyle=':')
plt.plot(x,z,color='#0000FF',marker='+',linestyle='--')
plt.show()
Alcuni dei possibili (ma ce ne sono tanti altri) parametri opzionali che è possibile passare alla funzione plot sono:
  • alpha: trasparenza della linea (float: 0.0 = trasparente, 1.0 = opaca)
  • color (o "c"): colore della linea
  • linestyle (o "ls"): stile della linea
  • linewidth (o "lw"): larghezza della linea (float)
  • marker: tipo di marker
  • markersize (o "ms"): dimensione del marker (float)
  • markevery: ogni quanti punti mettere un marker (ma si possono specificare anche markers singoli, ecc.)

Esempi:

In [21]:
plt.plot(x,y,linestyle='-',linewidth=2.0,color='b',marker="x")
plt.plot(x,z,ls='-',lw=1.0,marker='x',markersize=10.0,color='y',markevery=(10))
plt.show()

Un altro modo di specificare le caratteristiche di una linea è farsi restituire un oggetto "linea" dalla funzione plot e modificarne le proprietà tramite la funzione setp().

In [22]:
linea = plt.plot(x,y)
plt.setp(linea,linestyle="-.",color="green")
plt.show()

E' naturalmente possibile migliorare il grafico inserendo un titolo, delle labels sugli assi, una griglia, del testo in posizione arbitraria, ecc.

In [23]:
def esponenziale_smorzato(x):
    y=np.exp(-0.5*x)*np.cos(2.0*x)
    return y

pi=np.pi
x=np.linspace(0.0,2.0*np.pi,101)
y=esponenziale_smorzato(x)
plt.plot(x,y,'ko-')
plt.title("Un esponenziale smorzato")
plt.xlabel("x")
plt.ylabel("y")
plt.grid(True)
plt.text(pi,np.exp(-0.5*pi),"Max 1")
plt.text(0.5*pi,-np.exp(-0.25*pi),"Min 1")
plt.axis([0.0,2.0*pi,-1.2,1.2])             # Nota la lista che specifica min e max degli assi!
plt.show()
Le proprietà del testo si possono modificare attraverso parametri opzionali.
Una lista (incompleta) di tali parametri è la seguente:
  • fontsize: dimensione del font (in pixels)
  • horizonthalalignment: allineamento orizzontale del testo (left|center|right)
  • verticalalignment: allineamento verticale del testo (top|center|bottom)

Nota:
Una delle caratteristiche fondamentali di mathplotlib è quella di poter utilizzare il rendering in TeX o in LaTeX delle stringhe, facendole precedere da una "r" (che sta per "raw") per indicare a python di NON interpretare come sequenze di escape i codici TeX!
L'esempio di prima, un po' più carino:

In [24]:
plt.plot(x,y,'ko-')
plt.title(r"Un esponenziale smorzato: $e^{-\frac{x}{2}}\cos(2x)$")
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.grid(True)
plt.text(pi,1.4*np.exp(-0.5*pi),"Max 1",horizontalalignment="center",fontsize=18)
plt.text(0.5*pi,-1.1*np.exp(-0.25*pi),"Min 1",horizontalalignment="right",verticalalignment="top")
plt.xlim(0.0,2.0*pi)             # Nota che qui utilizziamo xlim e ylim per min e max degli assi invece di axis!
plt.ylim(-0.7,1.2)
plt.show()

E' possibile annotare un grafico con un testo e una freccia con la funzione "annotate()":

In [25]:
plt.plot(x,y,'ko-')
plt.title(r"Un esponenziale smorzato: $e^{-\frac{x}{2}}\cos(2x)$")
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.grid(False)                  # Nota che qui abbiamo rimosso la griglia!
xpos=pi
ypos=np.exp(-0.5*pi)
# Nota i parametri della freccia: shrink allontana la punta e la base della freccia dal punto!
plt.annotate("Max 1", xy=(xpos,ypos), xytext=(xpos+1.0,ypos+0.5), arrowprops={"facecolor":"blue","shrink":0.05})
plt.xlim(0.0,2.0*pi)             # Nota che qui utilizziamo xlim e ylim per min e max degli assi invece di axis!
plt.ylim(-0.7,1.2)
plt.show()
Si possono sovrapporre vari grafici nella stessa figura disponendoli su una griglia (ma anche in maniera arbitraria!) tramite il comando:
subplot(num_righe,num_cols,posizione)

Esempio:

In [26]:
plt.subplot(211)
plt.plot(x,y,'ko-')
plt.title("Un esponenziale smorzato")
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.subplot(212)
plt.plot(x,np.abs(y),'ko-')
plt.title("Valore assoluto di un esponenziale smorzato")
plt.xlabel(r"$x$")
plt.ylabel(r"$|y|$")
plt.show()
Si possono mettere gli assi in scala logaritmica tramite le funzioni:
xscale("log|linear")
yscale("log|linear")

Esempio:

In [27]:
plt.subplot(211)
plt.plot(x,y,'ko-')
plt.title("Un esponenziale smorzato in scala y lineare")
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.subplot(212)
plt.yscale("log")
plt.plot(x,np.abs(y),'ko-')
plt.title("Modulo di un esponenziale smorzato in scala y-log")
plt.xlabel(r"$x$")
plt.ylabel(r"$\log(|y|)$")
plt.show()
In [28]:
def power_law(x,n):
    y=np.power(x,n)
    return y

x=np.linspace(1.0,101.0,101)
y=power_law(x,-5.0/3.0)
plt.subplot(211)
plt.plot(x,y,'ko-')
plt.title("Una legge di potenza in scala lineare")
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.subplot(212)
plt.xscale("log")
plt.yscale("log")
plt.plot(x,y,'ko-')
plt.title("Legge di potenza in scala x-y log")
plt.xlabel(r"$\log(x)$")
plt.ylabel(r"$\log(y)$")
plt.show()

E' possibile disegnare grafici in coordinate polari, a torta, a barre, ecc., con varie funzioni.
Esempio in coordinate polari:

In [29]:
r = np.linspace(0.0,5.0,101)
theta = 2.0 * np.pi * r
plt.polar(r, theta)
plt.show()
In [30]:
dims=[10.0,30.0,40.0,10.0]
etichette=["dato1","dato2","dato3","dato4"]
colori=['b','r','y','magenta']
plt.pie(dims,labels=etichette,colors=colori)
plt.show()

E' infine possibile produrre contour plots.
Esempio:

In [31]:
x=np.zeros((101,51),dtype=float)
y=np.zeros((101,51),dtype=float)
xx=np.linspace(-2.0,2.0,101)
yy=np.linspace(-2.0,2.0,51)
for j in range(51):
    x[:,j]=xx
for i in range(101):
    y[i,:]=yy
z=np.exp(-(x*x + y*y))
plt.contour(x,y,z)
plt.show()