In [ ]:
import numpy as np
import numpy.random as npr
import matplotlib.pyplot as mpl
import matplotlib.ticker as mpt
import sys
In [ ]:
#==============================================================================
# Create Mock Data
#==============================================================================

def generate_correlated_random_xydata(npoints=2500):
    x = np.linspace(0,3,npoints)+npr.normal(size=npoints)
    y = x+npr.normal(size=npoints)
    return x,y
In [ ]:
#==============================================================================
#     Utility Functions
#==============================================================================

def find_level(histogram,level,nslices=1000):
    """
    This return the histogram value that contain level% of the data inside.
    Useful to construct isocontours for 2D data based on data counts.
    """
    HTOT = np.sum(histogram)
    HMAX = np.amax(histogram)
    HMIN = np.amin(histogram)
    slices = np.linspace(HMAX,HMIN,nslices)
    for i in range(nslices):
        if np.sum(histogram[histogram>slices[i]]) > level*HTOT:
            break
        else:
            continue
    return slices[i]
In [ ]:
#==============================================================================
#  Double Hist Figure
#==============================================================================


histCOLOR = 'GoldenRod'
histCMAP = 'YlOrRd'
contourCMAP = 'YlGnBu_r'
pointColor = 'LimeGreen'

fig = mpl.figure(figsize=(12.5,14)) ## Create an empty figure, figsize allows to control extra space for added axes
In [ ]:
## Add axes to the figure (where you will plot the data)
## [x_lowerCorner,y_lowerCorner,width,height]
## The full figure canvas goes from [0,1] both in x and y
## The use of sharex=axname will make the two axes linked in matplotlib interactive mode
## The same can be done with sharey=axname
axMain = fig.add_axes([0.1,0.1,0.65,0.65]) 
axHistX = fig.add_axes([0.1,0.75,0.65,0.20],sharex=axMain) 
axHistY = fig.add_axes([0.75,0.1,0.20,0.65],sharey=axMain) 

## Adjust label parameters for an axis to show (or not) the number labels
axHistX.tick_params(labelbottom='off')
axHistY.tick_params(labelleft='off')

## Generate random set of data
varx,vary = generate_correlated_random_xydata() 

## Create a 2D histogram from data
binx = np.linspace(-3,7,21)
biny= np.linspace(-3,7,21)
Histogram2D,Xedges,Yedges = np.histogram2d(varx,vary,bins=[binx,biny]) 
## Now we want to mask out all histogram bins where we have less than 10 data poin ts
masked_Histogram=np.ma.masked_where(Histogram2D<=10,Histogram2D,copy=False) 


## Compute levels to draw contours at 90,60,40 and 20% levels
levels = [0.90,0.60,0.40,0.20]
levels_contour = np.array([find_level(Histogram2D,l) for l in levels])

## Plot both histograms in the variables x and y
hx,ex,px=axHistX.hist(varx,bins=binx,color=histCOLOR,histtype='stepfilled')
hy,ey,py=axHistY.hist(vary,bins=biny,orientation='horizontal',color=histCOLOR,histtype='stepfilled')

## Adjust axis limits semi-automatically
axHistX.set_ylim(0,1.1*np.amax(hx))    
axHistY.set_xlim(0,1.1*np.amax(hy))
In [ ]:
## Plot data points, 2D histogram and contour levels on the main axes
## the picker keyword allows for added interactive actions to your plot by selecting
## closer point to your mouse click and have accessible data to play with.
## The zorder keyword allows you to control which elements of the plot apper on top.
## The higher the value of zorder the more upfront the element will be. 
## This is all done in relative terms.
z=0
axMain.plot(varx,vary,'.',alpha=0.5,color=pointColor,zorder=z,picker=2) 
axMain.imshow(masked_Histogram.T,cmap=histCMAP,extent=(min(Xedges),max(Xedges),min(Yedges),max(Yedges)),aspect='auto',zorder=z+1,origin="lower",interpolation="nearest")
axMain.contour(Histogram2D.T,levels=levels_contour,extent=(min(Xedges),max(Xedges),min(Yedges),max(Yedges)),cmap=contourCMAP,origin='lower',zorder=z+10)

## This limits the number of main ticks on each axis which helps in reducing
## the confusing in cluttered axes
axHistY.xaxis.set_major_locator(mpt.MaxNLocator(3))
axHistX.yaxis.set_major_locator(mpt.MaxNLocator(3))
axMain.xaxis.set_major_locator(mpt.MaxNLocator(5))

## This is not useful here, but can be useful in future scenarios since we use loglog plots often
## This will make your axis have the number labels in scalar format and not in power law format
axMain.xaxis.set_major_formatter(mpt.ScalarFormatter(useOffset=False))
axMain.yaxis.set_major_formatter(mpt.ScalarFormatter(useOffset=False))

## Turn on the minorticks in the plot to look more 'scientific'. Helpful to read values off plots in an easier way
for eixo in  [axMain,axHistX,axHistY]:
    eixo.minorticks_on()

    
## Set your plot labels using mathtext, same as you would do in LaTex
axMain.set_xlabel(r'$var_x$')
axMain.set_ylabel(r'$var_y$')

## Connect your figure with keyboard action. In this case, pressing a key while 
## the matplotlib window is active will execute the function exit_code
## This in turn wil either close the plot or exit the program if you click
## 'q' or 'escape' respectively
def exit_code(event):
    if event.key=='escape':
        sys.exit()
    if event.key=='q':
        mpl.close('all')
fig.canvas.mpl_connect('key_press_event',exit_code)

## Connect yout figure to a picking event. This will allow you to select the 
## closest element (data point, line, etc.) and allow you to access the data
## This is good for exploratory plots where you need specific information to
## add to that shown in the plot
def on_pick(event):
    thisline = event.artist
    xdata, ydata = thisline.get_data()
    ind = event.ind
    print('You clicked at x=%.2f, y=%.2f'%(xdata[ind], ydata[ind]))
cid = fig.canvas.mpl_connect('pick_event', on_pick)

## Make the Figure pop up
mpl.show()
In [ ]:
 
In [ ]: