Search code examples
pythonmatplotlibplotshareaxes

matplotlib subplots last plot disturbs log scale


I am making a matplotlib figure with a 2x2 dimension where x- and y-axis are shared, and then loop over the different axes to plot in them. I'm plotting variant data per sample, and it is possible that a sample doesn't have variant data, so then I want the plot to say "NA" in the middle of it.

import matplotlib.pyplot as plt

n_plots_per_fig = 4
nrows = 2
ncols = 2

fig, axs = plt.subplots(nrows, ncols, sharex="all", sharey="all", figsize=(8, 6))
axs = axs.ravel()

for i, ax in enumerate(axs):
    x = [1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]  # example values, but this list CAN be empty
    bins = 3  # example bins
    if x: 
        ax.hist(x, bins=bins)  # plot the hist
        ax.set_yscale("log")
        ax.set_title(str(i), fontsize="medium")
    else:
        ax.set_title(str(i), fontsize="medium")
        ax.text(0.5, 0.5, 'NA', ha='center', va='center', transform=ax.transAxes)

fig.show()

This works in almost every case; example of wanted output: wanted output

However, only if the last plot in the figure doesn't have any data, then this disturbs the log scale. Example code that triggers this:

import matplotlib.pyplot as plt

n_plots_per_fig = 4
nrows = 2
ncols = 2

fig, axs = plt.subplots(nrows, ncols, sharex="all", sharey="all", figsize=(8, 6))
axs = axs.ravel()

for i, ax in enumerate(axs):
    x = [1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
    bins = 3

    if i == n_plots_per_fig-1:  # this will distort the log scale
        ax.set_title(str(i), fontsize="medium")
        ax.text(0.5, 0.5, 'NA', ha='center', va='center', transform=ax.transAxes)
    elif x:
        ax.hist(x, bins=bins)  # plot the hist
        ax.set_yscale("log")
        ax.set_title(str(i), fontsize="medium")
    else:
        ax.set_title(str(i), fontsize="medium")
        ax.text(0.5, 0.5, 'NA', ha='center', va='center', transform=ax.transAxes)

fig.show()

unwanted_output

The log scale is now set to really low values, and this is not what I want. I've tried several things to fix this, like unsharing the y-axes for the plot that doesn't have any data [ax.get_shared_y_axes().remove(axis) for axis in axs] or hiding the plot ax.set_visible(False), but none of this works. The one thing that does work is removing the axes from the plot with ax.remove(), but since this is the bottom most sample, this also removes the values for the x ticks for that column: almost_corret_output

And besides that, I would still like the name of the sample that didn't have any data to be visible in the axes (and the "NA" text), and removing the axes doesn't allow this.

Any ideas on a fix?

Edit: I simplified my example.


Solution

  • You can set the limits manually with ax.set_xlim() / ax.set_ylim(). Note, that if you share the axes it does not matter on which subplot you call those functions. For example:

    axs[-1][-1].set_ylim(1e0, 1e2)
    

    If you do not know the limits before, you can infer it from the other plots:

    x = np.random.random(100)
    bins = 10
    if bins != 0:
        ...
        yy, xx = np.histogram(x, bins=bins)
        ylim = yy.min(), yy.max()
        xlim = xx.min(), xx.max()
    else:
       ax.set_xlim(xlim)
       ax.set_ylim(ylim)