TUTORIAL PROFILE PLOT¶

In questo tutorial viene mostrato come costruire in modo rapido ed indolore un profile plot usando il pitone.

Il punto di partenza è l'esempio di Valerio da cui prenderò anche ispirazione per i dati da usare.

In [ ]:
import numpy as np
from matplotlib import pyplot as plt

Genero e plotto dei dati casuali. Utilizzo un differente binnaggio per poter seguire al meglio quello che succede, e non trovarmi a dover riflettere se lavorare sulla matrice hh[0] o sulla sua trasposta. Lungo la prima dimensione (ovvero muovendosi lungo le righe) viene riportata la suddivisione in x, che nel plot viene mostrata in orizzontale

In [ ]:
N = 100000
xVect = np.random.rand(N)*2*np.pi # flat distribution between 0 and 2pi
yVect = np.sin(xVect) + np.random.normal(0, 0.1, N) +  np.random.normal(0, 0.3, N)*xVect/5 # sin wave + some spread which increases with x 

# Numero di bin
Nx = 50
Ny = 60
In [ ]:
fig, ax = plt.subplots()
fig.set_size_inches(11, 6)

mat, binsx, binsy, hh = ax.hist2d(xVect, yVect, bins = (Nx, Ny), cmap='coolwarm')

fig.colorbar(hh, ax = ax)
plt.show()
No description has been provided for this image

Definisco una comoda funzione che crea il profile plot.

Ricordiamo che i for in python sono il male assoluto quando si itera sull'intero set di dati. Iterare sui bin dell'istogramma sfruttando la vettorializzazione, è in generale una cosa lecita e comoda (come testimoniano i tempi di esecuzione)

In [ ]:
def myProfile(xVect, yVect, nbin):
    h, bins = np.histogram(xVect, bins = nbin)
    binc = bins[:-1] + (bins[1]- bins[0])/2
    
    out_vect = np.array([np.mean(yVect[(xVect >= bins[i]) & (xVect < bins[i+1])]) for i in range(nbin)])
    out_err =  np.array([np.std (yVect[(xVect >= bins[i]) & (xVect < bins[i+1])]) for i in range(nbin)])
    
    return binc, out_vect, out_err
In [ ]:
%%time
fig, ax = plt.subplots()
fig.set_size_inches(11, 6)

mat, binsx, binsy, hh = ax.hist2d(xVect, yVect, bins = (Nx, Ny), cmap='coolwarm')


%time binc, out_vect, out_err = myProfile(xVect, yVect, Nx)
ax.errorbar(binc, out_vect, yerr = out_err, ls = ":", c = "k", marker = ".")

fig.colorbar(hh, ax = ax)
plt.show()
CPU times: total: 62.5 ms
Wall time: 101 ms
No description has been provided for this image
CPU times: total: 422 ms
Wall time: 620 ms

Cosa succede se l'istogramma 2D ha un range verticale diverso da quello dei dati?¶

Infatti, la funzione sopra definisce per ciascuna fetta verticale la media sui dati, senza avere memoria del range verticale dell'istogramma

In [ ]:
%%time
fig, ax = plt.subplots()
fig.set_size_inches(11, 6)

mat, binsx, binsy, hh = ax.hist2d(xVect, yVect, bins = (Nx, Ny), cmap='coolwarm', range = ((0, 2*np.pi),(-.5, .5)))


%time binc, out_vect, out_err = myProfile(xVect, yVect, Nx)
ax.errorbar(binc, out_vect, yerr = out_err, ls = ":", c = "k", marker = ".")

fig.colorbar(hh, ax = ax)
plt.show()
CPU times: total: 15.6 ms
Wall time: 15.4 ms
No description has been provided for this image
CPU times: total: 250 ms
Wall time: 290 ms

Definiamo quindi una funzione che tenga traccia anche del range verticale

In [ ]:
def myProfile(xVect, yVect, nbinx, rangex, rangey):
    h, bins = np.histogram(xVect, bins = nbinx, range = rangex)
    binc = bins[:-1] + (bins[1]- bins[0])/2
    
    log = (yVect >= rangey[0]) & (yVect <= rangey[1])
    xVect = xVect[log]
    yVect = yVect[log]
    
    out_vect = np.array([np.nanmean(yVect[(xVect >= bins[i]) & (xVect < bins[i+1])]) for i in range(nbinx)])
    out_err =  np.array([np.nanstd (yVect[(xVect >= bins[i]) & (xVect < bins[i+1])]) for i in range(nbinx)])
    
    return binc, out_vect, out_err

Indipendentemente dal significato che possa avere, questa funzione fa il profile plot SOLO DI QUELLO CHE SI VEDE NELLA FIGURA, che è il comportamento standard di paw

In [ ]:
%%time
rangex = (0, 2*np.pi)
rangey = (-.5, .5)

fig, ax = plt.subplots()
fig.set_size_inches(11, 6)

mat, binsx, binsy, hh = ax.hist2d(xVect, yVect, bins = (Nx, Ny), cmap='coolwarm', range = (rangex, rangey))


%time binc, out_vect, out_err = myProfile(xVect, yVect, Nx, rangex, rangey)
ax.errorbar(binc, out_vect, yerr = out_err, ls = ":", c = "k", marker = ".")

fig.colorbar(hh, ax = ax)
plt.show()
C:\Users\steca\AppData\Local\Temp\ipykernel_21000\3407349314.py:9: RuntimeWarning: Mean of empty slice
  out_vect = np.array([np.nanmean(yVect[(xVect >= bins[i]) & (xVect < bins[i+1])]) for i in range(nbinx)])
c:\Users\steca\anaconda3\envs\AnaTesiLM\Lib\site-packages\numpy\lib\nanfunctions.py:1879: RuntimeWarning: Degrees of freedom <= 0 for slice.
  var = nanvar(a, axis=axis, dtype=dtype, out=out, ddof=ddof,
CPU times: total: 31.2 ms
Wall time: 22.9 ms
No description has been provided for this image
CPU times: total: 281 ms
Wall time: 291 ms

Definiamo infine una funzione che faccia in un colpo solo istogramma 2D e profile plot¶

In [ ]:
def Histo2DProfile(xVect, yVect, nbinx, nbiny, rangex, rangey, **kwargs):
    
    fig, ax = plt.subplots()
    fig.set_size_inches(11, 6)

    # Hist 2d
    mat, binsx, binsy, hh = ax.hist2d(xVect, yVect, bins = (nbinx, nbiny), range = (rangex, rangey), **kwargs)
    
    
    # Plug here the old function
    h, bins = np.histogram(xVect, bins = nbinx, range = rangex)
    binc = bins[:-1] + (bins[1]- bins[0])/2
    
    log = (yVect >= rangey[0]) & (yVect <= rangey[1])
    xVect = xVect[log]
    yVect = yVect[log]
    
    out_vect = np.array([np.nanmean(yVect[(xVect >= bins[i]) & (xVect < bins[i+1])]) for i in range(nbinx)])
    out_err =  np.array([np.nanstd (yVect[(xVect >= bins[i]) & (xVect < bins[i+1])]) for i in range(nbinx)])
    
    # Plot profile plot
    ax.errorbar(binc, out_vect, yerr = out_err, ls = ":", c = "k", marker = ".")

    fig.colorbar(hh, ax = ax)
    plt.show()
In [ ]:
# Proviamo la funzione
Histo2DProfile(xVect, yVect, Nx, Ny, rangex, rangey, cmap='coolwarm')
C:\Users\steca\AppData\Local\Temp\ipykernel_21000\614021229.py:17: RuntimeWarning: Mean of empty slice
  out_vect = np.array([np.nanmean(yVect[(xVect >= bins[i]) & (xVect < bins[i+1])]) for i in range(nbinx)])
No description has been provided for this image