Plotting

È naturale, di fronte ad una grossa mole di dati, cercare di visualizzarli per averne un'idea, almeno sommaria. I principali tool messi a disposizione da python per il 'plotting' dei dati, ovvero la creazione di grafici, sono due: matplotlib, integrato in sci-py, è la libreria standard per fare grafici, con un utilizzo sostanzialmente identico al plot di matlab; un'altra libreria, più orientata alla statistica, è seaborn, che ha al suo interno diversi template per mostrare i dati, e si integra alla perfezione con le pandas. Ovviamente ne esistono anche altre, ma queste due sono le più diffuse.

MatPlotLib

Matplotlib è la libreria standard per i plot in python. È naturalmente orinetata al plotting di funzioni, quindi corrispondenze tra delle X e delle Y. Vediamo intanto i comandi base.

In [6]:
import matplotlib.pyplot as plt
plt.plot([1, 2, 3, 4], [1, 4, 2, 3])
plt.show()

Come vedete, la funzione plot prende in input due parametri (generalmente due liste), una per le X e una per le Y. In questo caso, le due liste devono avere la stessa dimensione, altrimenti avremo un errore.

ATTENZIONE: su spyder, a seconda di come eseguite un programma, spesso il plot non avverà in automatico ma sarà necessario chiamare, alla fine, il comando plt.show().

Possiamo anche plottare diverse serie di dati contemporaneamente.

In [5]:
import numpy as np
x = np.linspace(0, 2, 100) # 100 punti equispaziati nell'intervallo [0,2] sono le nostre x
y1 = [np.exp(i) for i in x] # applico e^x ai punti
y2 = [(1+i) for i in x] # applico x+1
plt.plot(x,y1)
plt.plot(x,y2)
Out[5]:
[<matplotlib.lines.Line2D at 0x7f83d599b400>]

Vediamo ora come rendere più chiaro il nostro grafico:

In [4]:
plt.plot(x,y1, label='e^x')
plt.plot(x,y2, label='x+1')
plt.legend()
plt.title('Che bella giornata')
plt.xlabel('Asse x')
plt.ylabel('Asse y')
Out[4]:
Text(0, 0.5, 'Asse y')

La funzione legend aggiunge una legenda basandosi sui parametri label dei singoli plot. Le personalizzazioni possibili sono molte, dai colori alla forma degli indicatori dei singoli punti (qua i punti non sono indicati). Trovate tutto nella documentazione sul sito ufficiale.

Vediamo ora altre opzioni: possiamo ad esempio creare diversi grafici in una sola immagine con il comando subplot.

In [7]:
p1 = plt.subplot(1, 2, 1)
p1.plot(x,y1)
p2 = plt.subplot(1, 2, 2)
p2.plot(x,y2)
Out[7]:
[<matplotlib.lines.Line2D at 0x7f83d58b6048>]

All'interno di subplot indichiamo il numero di righe e di colonne della nostra tabella di plot, e infine l'effettiva posizione del nostro plot nella tabella.

Questo è il caso più semplice, il cosiddetto line plot. Una prima estensione del line plot è il plot tridimensionale: in questo caso avremo dei punti in un piano XxY, e una funzione con un valore Z per ogni punto del piano.

In [17]:
from mpl_toolkits.mplot3d import Axes3D

X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y) # comandi per approssimare il piano XxY
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R) # la funzione è sin(sqrt(x^2 + y^2)), non è importante ma è carina

fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X, Y, Z, cmap=plt.cm.coolwarm)

L'opzione cmap indica solo come colorare la nostra superficie. Anche qui ovviamente potremmo sbizzarrirci: possiamo ad esempio plottare una curva nello spazio, triangolare una superficie (non so come ma si può) o altre cose che trovate sempre al link di prima.

In [20]:
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X, Y, Z, cmap=plt.cm.jet)
plt.show()

In certi casi è preferibile 'schiacciare' un plot 3d in uno 2d, mostrando il piano con colori diversi (ad esempio le heatmap, mappe del calore). Possiamo farlo con il comando pcolor:

In [23]:
plt.pcolor(X,Y,Z)
Out[23]:
<matplotlib.collections.PolyCollection at 0x7f83d5a54198>

Non essendo in grado di visualizzare il valore della coordinata può essere utile visualizzare il valore dei vari colori.

In [25]:
plt.pcolor(X,Y,Z,cmap=plt.cm.jet)
plt.colorbar()
Out[25]:
<matplotlib.colorbar.Colorbar at 0x7f83d574e7b8>

Spesso capita di avere a che fare con dati sparsi, che magari sono rappresentati da due variabili (altezza, peso) ma in cui non è nota a priori o è del tutto assente una relazione tra i due. In questo caso possiamo semplicemente rappresentare i punti sul piano, senza collegarli come con il line plot, usando la funzione scatter:

In [26]:
x = np.random.normal(1, 0.1, 300)
y = np.random.normal(1, 0.1, 300) # valori casuali
plt.scatter(x,y)
Out[26]:
<matplotlib.collections.PathCollection at 0x7f83d56deba8>

Oppure mostrarli in un istogramma:

In [27]:
plt.hist(x)
Out[27]:
(array([ 2.,  9., 20., 45., 53., 66., 53., 27., 22.,  3.]),
 array([0.68890823, 0.74612281, 0.80333739, 0.86055197, 0.91776655,
        0.97498112, 1.0321957 , 1.08941028, 1.14662486, 1.20383944,
        1.26105401]),
 <a list of 10 Patch objects>)

In la funzione hist accetta anche un secondo parametro, una lista che indichi l'ampiezza delle basi. Se non viene fornito (come in questo caso) viene calcolata automaticamente. Ovviamente è possibile fornire molte altre rappresentazioni dei dati tramite questa libreria (a questa pagina trovate diversi esempi), ma è proprio per questo tipo di situazioni, con dati sparsi e non necessariamente correlati, che ci viene in aiuto la libreria seaborn.

Un altro strumento interessante è l'istruzione bar, che ci permette di fare un grafico a barre leggeremente diverso da un istogramma.

In [29]:
plt.bar([1,4,6],[10,20,60])
Out[29]:
<BarContainer object of 3 artists>

Seaborn

Seaborn è una libreria, basata su matplotlib, ma orientata alla statistica. Per questo motivo contiene diversi strumenti dedicati, e presta anche più attenzione alla veste grafica: da questo punto di vista ci sono molte più opzioni facilmente accessibili per i nostri plot. La documentazione ufficiale la trovate a questo link.

Iniziamo a vedere come fare lo scatter in seaborn:

In [1]:
import seaborn as sns
sns.set(style="darkgrid") # opzione grafica
tips = sns.load_dataset("tips") # dataset di esempio
sns.relplot(x="total_bill", y="tip", data=tips);

relplot è una funzione che ha diverse opzioni, ma di default ci restituisce lo scatter.

Seaborn, come molte librerie di questo genere, fornisce al suo interno alcuni dataset di esempio, per poter provare rapidamente le funzioni della libreria. Vediamo cosa contiene il nostro dataset tips:

In [2]:
tips
Out[2]:
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4
... ... ... ... ... ... ... ...
239 29.03 5.92 Male No Sat Dinner 3
240 27.18 2.00 Female Yes Sat Dinner 2
241 22.67 2.00 Male Yes Sat Dinner 2
242 17.82 1.75 Male No Sat Dinner 2
243 18.78 3.00 Female No Thur Dinner 2

244 rows × 7 columns

Sono diversi valori di mancia (tip) rapportati al conto totale pagato (total_bill), più una serie di altri fattori. In questo caso abbiamo indicato come x la colonna total_bill, e come y tip. È evidente la semplicità con cui questa libreria mostra il contenuto dei dataset.

Supponiamo ora di voler verificare, ad esempio, se gli uomini sono più o meno generosi delle donne. Possiamo aggiungere al grafico un terzo parametro, con l'opzione hue, che cambierà il colore del nostro plot:

In [3]:
sns.relplot(x="total_bill", y="tip", hue="sex", data=tips);

Così possiamo facilmente tenere sott'occhio tre variabili contemporaneamente.

Recuperiamo ora il dataset dei film che avevamo importato con le pandas, per vedere come utilizzare efficacemente la funzione relplot.

In [4]:
import pandas as pd
movies_df = pd.read_csv("IMDB-Movie-Data.csv", index_col="Title")
sns.relplot(x='Rating',y='Revenue (Millions)', data=movies_df)
Out[4]:
<seaborn.axisgrid.FacetGrid at 0x7efe8640d850>

In questo modo abbiamo di fronte, con pochissime righe di codice, un'immagine che ci dia un'idea di una possibile correlazione.

Con l'opzione type='line' otteniamo il nostro solito lineplot (anche in questo caso l'accesso ad un dataFrame di pandas è immediato):

In [6]:
import numpy as np
df = pd.DataFrame(dict(time=np.arange(500),value=np.random.randn(500).cumsum())) # dataFrame casuale
g = sns.relplot(x="time", y="value", kind="line", data=df)

Un fatto molto interessate si verifica quando abbiamo un dataset contenente diversi (ma simili) valori di Y per ogni X: ad esempio le particelle inquinanti ad un istante calcolate da diversi sensori vicini. In questo caso relplot mostra di default la media dei valori, e un'ombra contenente l'area con il 95% di confidenza. Per mostrarlo importiamo un altro dataset preinsallato:

In [8]:
fmri = sns.load_dataset("fmri")
fmri
Out[8]:
subject timepoint event region signal
0 s13 18 stim parietal -0.017552
1 s5 14 stim parietal -0.080883
2 s12 18 stim parietal -0.081033
3 s11 18 stim parietal -0.046134
4 s10 18 stim parietal -0.037970
... ... ... ... ... ...
1059 s0 8 cue frontal 0.018165
1060 s13 7 cue frontal -0.029130
1061 s12 7 cue frontal -0.004939
1062 s11 7 cue frontal -0.025367
1063 s0 0 cue parietal -0.006899

1064 rows × 5 columns

In [9]:
sns.relplot(x="timepoint", y="signal", kind="line", data=fmri);

Un'altro tipo di plot, molto interessante soprattutto nei problemi di classificazione, è il pairplot. In questo tipo di plot viene creata una griglia con tutte le colonne del dataset, sia sulle x che sulle y, che vengono poi confrontate a coppie per individuare possibili relazioni.

In [11]:
iris = sns.load_dataset("iris")
iris
Out[11]:
sepal_length sepal_width petal_length petal_width species
0 5.1 3.5 1.4 0.2 setosa
1 4.9 3.0 1.4 0.2 setosa
2 4.7 3.2 1.3 0.2 setosa
3 4.6 3.1 1.5 0.2 setosa
4 5.0 3.6 1.4 0.2 setosa
... ... ... ... ... ...
145 6.7 3.0 5.2 2.3 virginica
146 6.3 2.5 5.0 1.9 virginica
147 6.5 3.0 5.2 2.0 virginica
148 6.2 3.4 5.4 2.3 virginica
149 5.9 3.0 5.1 1.8 virginica

150 rows × 5 columns

In [10]:
sns.pairplot(iris, hue="species", height=2.5);
In [54]:
iris
Out[54]:
sepal_length sepal_width petal_length petal_width species
0 5.1 3.5 1.4 0.2 setosa
1 4.9 3.0 1.4 0.2 setosa
2 4.7 3.2 1.3 0.2 setosa
3 4.6 3.1 1.5 0.2 setosa
4 5.0 3.6 1.4 0.2 setosa
... ... ... ... ... ...
145 6.7 3.0 5.2 2.3 virginica
146 6.3 2.5 5.0 1.9 virginica
147 6.5 3.0 5.2 2.0 virginica
148 6.2 3.4 5.4 2.3 virginica
149 5.9 3.0 5.1 1.8 virginica

150 rows × 5 columns

Come vedete, abbiamo i dati di diversi fiori classificati per specie, oltre ad alcuni dati. Il pairplot ci permette immediatamente di vedere quali sono i dati caratteristici di ogni specie rapportata alle altre (in questo caso, ad esempio, la 'setosa' è chiaramente distinta dalle altre per molte caratteristiche). La diagonale centrale è di default occupata da grafici che indicano la distribuzione di un dato: plottare un dato contro se stesso infatti non ha troppo senso (perchè? dove sarebbero tutti i pallini?).

In realtà, pairplot è una versione standard della funzione PairGrid, più flessibile ma anche meno immediata da usare.

Concludiamo con la funzione distplot, che ci permette di visualizzare rapidamente la distribuzione dei dati mostrando un istogramma e un'approssimazione, detta KDE (Kernel Density Estimation ma ora non ci interessa cosa sia), di questa distribuzione.

In [12]:
x = np.random.normal(size=100)
sns.distplot(x);

Come vedete, le possibilità sono molte, e noi abbiamo provato solo le più elementari. Sui vari siti ufficiali delle librerie e sui forum trovate molti esempi, che si adattano alla maggior parte delle situazioni che vi potranno capitare.

Esercizi

  • Confrontare l'andamento delle funzioni x, x^2 e x^3 tra 0 e 5
  • Confrontare, nel dataset dei film, l'andamento degli incassi (Revenue (Millions)) negli anni (Years)
  • Confrontare, nello stesso dataset, la durata del film (Runtime (Minutes)) con il voto (Rating)