2

When I'm plotting my data, I want to be able to control the major and minor ticks of my subplots. However, whatever I try I seem to be unable modify the ticks of my second subplot. I've tried applying the advice from other stackoverflow questions, unfortunately to no avail. I think I'm doing something fundamentally wrong when constructing my boxplots.

As none of my colleagues have much experience with matplotlib, I'm turning to you stackoverflow! Any help would be greatly appreciated!

Currently, my figures look like this:

Example Plot

On the second boxplot, I also want to force a major tick on every 10^xth and show the default log minor ticks.

Currently, I generate my boxplots as follows:

def generateLogBoxPlot(title, plot_id, xlabels, data, initializion_time, fig):

    # Create an axes instance
    ax = fig.add_subplot(plot_id)

    # Set Log Scale
    ax.set_yscale('log')

    # Create the boxplot
    bp = ax.boxplot(data_of_plot)

    # Show Initialization Time (as a Line)
    line = plt.plot(...)

    # Custom rotated x-axis labels
    ax.set_xticklabels(xlabels)
    plt.xticks(rotation=15)

    #Set Labels
    plt.ylabel('Time (ms)')
    ax.set_title(title)

    #Show Grid
    ax.get_yaxis().grid()

And I call this function like this:

# Create a figure instance
fig = plt.figure(1, figsize=(9, 3))

# Generate first subplot
generateLogBoxPlot("No Context-Dependent A.C.\nBusiness Case", #title 
121, #plot_id
["Add User", "Add Role", "Add Demarcation", "Add Permission"], #labels
results["AddEntities"], #data
40000, #initializion_time
fig) #figure 

line = generateLogBoxPlot("Context-Dependent A.C.\nBusiness Case", 
122, #plot_id
["Add User", "Add Role", "Add Demarcation", "Add Permission"], #labels
results["AddEntities2"], #data
153000, #initialization_time
fig) #figure

#Show Legend
plt.legend(plt.plot([], [],linestyle="--", color="#A9A9A9", label="Initialization 
Time"),["Initialization Time"], loc='center left', bbox_to_anchor=(1, 0.5))

#Show
plt.tight_layout()
plt.show()

Whatever I try, I only seem to be able to modify the ticks of the fist subplot. How could I force/edit them on the second subbplot?

  • 1
    Unclear why the minor ticks would not appear in the second subplot with this code. However, it is not an MCVE, so the sample I ran might differ from yours. – Mr. T Jan 15 '21 at 16:28
  • 1
    This question is similar to [this one](https://stackoverflow.com/questions/44078409/matplotlib-semi-log-plot-minor-tick-marks-are-gone-when-range-is-large). – Patrick FitzGerald Jan 17 '21 at 16:29

1 Answers1

3

Matplotlib automatically shows or hides minor ticks of log scales depending on the range of values and to some extent the figure size as well. With regards to a y-axis base 10 log scale, here is what I have noticed from testing variations of the example shown further below (using matplotlib 3.3.2 with default settings):

  • For a figure height of 4 inches (default) or more: when the range of the y-axis covers 9 integer powers or more, the log scale switches from showing major ticks with labels at every power integer as well as all minor tick marks to showing major ticks every two (or more) power integers with no minor ticks (like in your plot on the right).
  • For a figure height of less than 4 inches (which seems to be your case): there is a more flexible adjustment of the ticks based on the range of the y-axis and the space available.

For your particular example, I would in any case start off by sharing the y-axis to make the plots more comparable. That then leaves two options: either leave the default tick formatting as it is and make do with no minor ticks or else force minor ticks for both plots.

Examples of matplotlib default log tick behavior and how to change it

First, here is an illustration of the matplotlib default behavior with log ticks:

import numpy as np                 # v 1.19.2
import matplotlib.pyplot as plt    # v 3.3.2
import matplotlib.ticker as ticker

# Create sample data with exponentially increasing values for x and
# the y functions and where the y functions are nearly identical
x = 10**np.linspace(0, 3, 30)
y1 = x**2
y2 = x**2.5

# Create figure and subplots containing semilogy plots
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 3))
fig.suptitle('Matplotlib defaults with figure height of 3 inches', y=1.25)

ax1.set_title('Minor ticks are shown\n\n', pad=10)
ax1.semilogy(x, y1, label='y1')
ax1.legend(loc='lower right')

ax2.set_title(f'Minor ticks are not shown:\nthe y range covers less than 9 \
integer\npowers, but the figure height is <4 inches', pad=10)
ax2.semilogy(x, y2, label='y2')
ax2.legend(loc='lower right')

plt.show()

mpl_logticks1


Now, what if the figure height is increased to make more space for the ticks?

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
fig.suptitle('Figure height of 4 inches', y=1.15)

ax1.set_title('Minor ticks are not shown:\nthe custom y-axis limits causes the\
\ny range to cover 9 integer powers', pad=10)
ax1.semilogy(x, y1, label='y1')
ax1.legend(loc='lower right')
ax1.set_ylim(1, 10**8) # the minor ticks disappear by changing the axis limits

ax2.set_title('Minor ticks are shown by default:\nthe y range covers less \
than 9 integer\npowers and the figure height is 4 inches', pad=10)
ax2.semilogy(x, y2, label='y2')
ax2.legend(loc='lower right')

plt.show()

mpl_logticks2

This particular example shows that increasing the figure size can solve the problem of minor ticks not showing, but this may often not be the case.

Here is how to force minor ticks to be shown whatever the range of y and the figure size, by using the LogLocator from the matplotlib.ticker module (this example also includes a shared y-axis):

# Add sharey=True
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 3), sharey=True)
fig.suptitle('Figure height of 3 inches with\ncustomized tick locator and shared \
y-axis', y=1.25)

ax1.set_title('Ticks shared by ax2\n', pad=10)
ax1.semilogy(x, y1, label='y1')
ax1.legend(loc='lower right')

ax2.set_title('Customized LogLocator:\nminor ticks are forced to be shown', pad=10)
ax2.semilogy(x, y2, label='y2')
ax2.legend(loc='lower right')

# Set ax2 major and minor tick locators with custom parameters to show
# all major and minor ticks despite the small figure height and the large
# range of y: the numticks argument must be an arbitrary number at least
# one unit above the number of integer powers covering the range of y
nticks = 9
maj_loc = ticker.LogLocator(numticks=nticks)
min_loc = ticker.LogLocator(subs='all', numticks=nticks)
ax2.yaxis.set_major_locator(maj_loc)
ax2.yaxis.set_minor_locator(min_loc)

# The tick labels are formatted as one would expect, so no need to use
# log tick formatters for this example.

plt.show()

mpl_logticks

If you want to create a plotting function that always shows minor ticks for any range of values, you need to set numticks at a high value.



References: answer by ImportanceOfBeingErnest, matplotlib Log Demo, matplotlib Tick Locators

Patrick FitzGerald
  • 3,280
  • 2
  • 18
  • 30
  • You're a hero! I'm now unstuck and I can format the graphs as they should be! Thanks a lot :) EDIT: The exact part which (finally!) got me unstuck was creating both the axes at once with "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 3))" instead of twice with "ax = fig.add_subplot(plot_id)" in the function. – Hans van der Laan Jan 18 '21 at 19:13
  • 1
    Great to hear that this solved your problem, happy to have been of help! – Patrick FitzGerald Jan 19 '21 at 11:03