2

I have looked around to no avail, but I'm having an issue that my python GridSpec multiplot keeps changing the axes. The main issue is that even though I explicitly set the extent and aspect of the 2dheatmap, it still changes the xaxis so that I have white space around my graph.

I have tried turning autoscale off but this causes the flanking histograms to go wrong, presumably because of the shared axis?

def hist2d_flanking1d(x, y, xlims, ylims, bins=50, 
    weights=None,xlabel="xlabel", ylabel="ylabel", cbarlabel='Testing'):

    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.ticker import NullFormatter, MaxNLocator
    from numpy import linspace
    import matplotlib.gridspec as gridspec
    from matplotlib import cm as cm
    import pdb
    from matplotlib import ticker
    from mpl_toolkits.axes_grid1 import make_axes_locatable


    plt.close('all')
    fig = plt.figure()
    gs = gridspec.GridSpec(2,2, width_ratios=[3,1], height_ratios=[1,3])
    axTemperature = plt.subplot(gs[1,0])

    # Find the min/max of the data
    xmin = np.float(xlims[0])
    xmax = np.float(xlims[1])
    ymin = np.float(ylims[0])
    ymax = np.float(ylims[1])

    xbins = linspace(start = xmin, stop = xmax, num = bins)
    ybins = linspace(start = ymin, stop = ymax, num = bins)
    H, xedges,yedges = np.histogram2d(y,x,bins=(ybins,xbins), weights=weights)
      extent=[xmin,xmax,ymin,ymax]
    cax = (axTemperature.imshow(H, extent=extent,
    interpolation='nearest', origin='lower',aspect=((xmax-xmin)/(ymax-ymin)),
    cmap=cm.cubehelix_r))

    #Set up the plot limits
    axTemperature.set_xlim(xmin,xmax)
    axTemperature.set_ylim(ymin,ymax)
    axTemperature.set_xlabel(xlabel, fontsize=22, labelpad=20)
    axTemperature.set_ylabel(ylabel, fontsize=22, labelpad=20)
    #Make the tickmarks pretty
    ticklabels = axTemperature.get_xticklabels()
    for label in ticklabels:
        label.set_fontsize(18)

    ticklabels = axTemperature.get_yticklabels()
    for label in ticklabels:
        label.set_fontsize(18)

    # Now setup the two flanking histograms
    axHistx = plt.subplot(gs[0,0], sharex=axTemperature)
    axHisty = plt.subplot(gs[1,1], sharey=axTemperature)

    # Remove the inner axes numbers of the histograms
    plt.setp(axHisty.get_yticklabels(), visible=False)
    plt.setp(axHistx.get_xticklabels(), visible=False)

    # Add labels
    axHistx.set_ylabel('N', fontsize=22, labelpad=20)
    axHisty.set_xlabel('N', fontsize=22, labelpad=20)

    #Plot the histograms
    axHistx.hist(x, bins=xbins, color = 'blue', histtype='step')
    axHisty.hist(y, bins=ybins, orientation='horizontal', color     ='red',histtype='step')

    # Make the tickmarks pretty
    ticklabels = axHistx.get_yticklabels()
    for label in ticklabels:
        label.set_fontsize(18)

    # Make the tickmarks pretty
    ticklabels = axHisty.get_xticklabels()
    for label in ticklabels:
        label.set_fontsize(18)

    #Cool trick that changes the number of tickmarks for the histogram axes
    axHisty.xaxis.set_major_locator(MaxNLocator(1))
    axHistx.yaxis.set_major_locator(MaxNLocator(1))

    # This should create an axes on the rightside of the vertical
    # histogram. Width is argument 2, padding argument 3, reduce
    # the number of ticks to make it less messy
    divider = make_axes_locatable(axHisty)
    extend = divider.append_axes("right", "20%", pad=0.2)
    cb = plt.colorbar(cax, cax=extend)
    tick_locator = ticker.MaxNLocator(nbins=5)
    cb.locator = tick_locator
    cb.update_ticks()

    # Have to draw first, then tightlayout then draw again, otherwise
    # the axes labels are cut off. If you do it before drawing it
    # complains that CGContextRef is NULL
    plt.draw()
    gs.tight_layout(fig)
    plt.draw()

    return axTemperature, axHistx, axHisty

I can't show you the outcome because I don't have the reputation to upload images.


As an aside, I'm also having issue with changing the tick numbers, I have set set_major_locator(MaxNLocator(1)) which should ( I think ) only have the maximum value tick mark, but this isn't consistent. The top histogram works no problem, but the side histogram only has 0 on the axis.


I have done further investigation and have found that it breaks down after:

axHistx = plt.subplot(gs[0,0], sharex=axTemperature)
axHisty = plt.subplot(gs[1,1], sharey=axTemperature)

Though I'm not sure why this suddenly breaks the axis size for the previous code.

Schorsch
  • 7,761
  • 6
  • 39
  • 65
ajclarke
  • 77
  • 1
  • 8

1 Answers1

2

The x-axis of the center plot is re-sized when you create the other parts of the multiplot. This overrides your aspect ratio definition in the call to imshow. Instead of:

aspect=((xmax-xmin)/(ymax-ymin))

use:

aspect='auto'

See this answer for a demonstration of the different aspect ratio settings for imshow.


The MaxNLocator(1) decides that the nicest tick to have is the first. To only get the largest/last tick, one can set all the previous ticks to empty strings. For this to work replace this block:

# Make the tickmarks pretty
ticklabels = axHistx.get_yticklabels()
for label in ticklabels:
    label.set_fontsize(18)

# Make the tickmarks pretty
ticklabels = axHisty.get_xticklabels()
for label in ticklabels:
    label.set_fontsize(18)

#Cool trick that changes the number of tickmarks for the histogram axes
axHisty.xaxis.set_major_locator(MaxNLocator(1))
axHistx.yaxis.set_major_locator(MaxNLocator(1))  

with:

yticklabels=axHistx.get_yticks().tolist()
yticklabels[:-1] = [' '] * len(yticklabels[:-1])
yticklabels[-1] = '{0:.0f}'.format(yticklabels[-1])

axHistx.set_yticklabels(yticklabels,fontsize=18)

xticklabels=axHisty.get_xticks().tolist()
xticklabels[:-1] = [' '] * len(xticklabels[:-1])
xticklabels[-1] = '{0:.0f}'.format(xticklabels[-1])

axHisty.set_xticklabels(xticklabels,fontsize=18)

Here, first the tick labels are retrieved (see this answer). Next, all but the last are set to empty strings and the last one is formatted to an integer representation. Finally, the labels are re-applied.


The plot you get with the following test data:

x = np.random.randn(100000)
y = np.random.randn(100000)+5

xlims = [0,1]
ylims = [0,5]

axTemperature, axHistx, axHisty = hist2d_flanking1d(x, y, 
                                                    xlims, ylims, 
                                                    bins=50, weights=None, 
                                                    xlabel="xlabel", 
                                                    ylabel="ylabel", 
                                                    cbarlabel='Testing')

looks like this:

final figure


As is explained in this answer, you can hold a defined aspect ratio with this command:

axTemperature.set(adjustable='box-forced')

However, that will not translate into the top plot's width being adjusted and the x-axis in the left column will not align. It is mentioned here for completeness.

Community
  • 1
  • 1
Schorsch
  • 7,761
  • 6
  • 39
  • 65