I am trying to customize the minor ticks in a matplotlib plot. Consider the following code:
import pylab as pl
from matplotlib.ticker import AutoMinorLocator
fig, ax = pl.subplots(figsize=(11., 7.4))
x = [1,2,3, 4]
y = [10, 45, 77, 55]
errorb = [20,66,58,11]
pl.xscale("log")
ax.xaxis.set_minor_locator(AutoMinorLocator(2))
ax.yaxis.set_minor_locator(AutoMinorLocator(2))
pl.tick_params(which='both', width=1)
pl.tick_params(which='minor', length=4, color='g')
pl.tick_params(axis ='both', which='major', length=8, labelsize =20, color='r' )
pl.errorbar(x, y, yerr=errorb)
#pl.plot(x, y)
pl.show()
As far as I understood, AutoMinorLocator(n) is supposed to insert n minor ticks between each major tick, and this is what happens on a linear scale but simply cannot figure out the logic behind the placement of the minor ticks on a logscale. On the top of that, there are much more minor ticks when using errorbar() then when using the simple plot().
AutoMinorLocator is only designed to work for linear scales:
From the ticker documentation:
AutoMinorLocator
locator for minor ticks when the axis is linear and the major ticks are uniformly spaced. It subdivides the major tick interval into a specified number of minor intervals, defaulting to 4 or 5 depending on the major interval.
And the AutoMinorLocator documentation:
Dynamically find minor tick positions based on the positions of major ticks. Assumes the scale is linear and major ticks are evenly spaced.
You probably want to use the LogLocator for your purposes.
For example, to put major ticks in base 10, and minor ticks at 2 and 5 on your plot (or every base*i*[2,5]), you could:
ax.xaxis.set_major_locator(LogLocator(base=10))
ax.xaxis.set_minor_locator(LogLocator(base=10,subs=[2.0,5.0]))
ax.yaxis.set_minor_locator(AutoMinorLocator(2))
Related
I'm generating a logplot using the following lines:
# Set log-los scale and axes
ax.set_xscale('log')
ax.set_yscale('log')
ax.set_xlabel(r'$x$')
ax.set_ylabel(r'$y$')
ax.set_aspect('equal')
ax.set_xlim([1e-7, 1e0])
ax.set_ylim([1e-7, 1e0])
but the result is not what I expected:
How can I make the x-axis look like the y-axis in terms of ticks? Thanks
While it's technically true that ax.set_adjustable('datalim') brings back the minor ticks, that's only because it negates the equal axes set in this line:
...
ax.set_aspect('equal')
...
The root cause: set_aspect('equal') shrinks the x axis to the point that matplotlib decides to auto-remove the minor ticks.
To have both an equal aspect ratio and minor x ticks, use the LogLocator from the ticker module:
from matplotlib.ticker import LogLocator
ax.xaxis.set_major_locator(LogLocator(numticks=10))
ax.xaxis.set_minor_locator(LogLocator(numticks=10, subs=np.arange(0.1, 1, 0.1)))
I have a barplot I am trying to plot without the x-axis ticks overlapping. I have settled on an angle of 45 degrees, and a max. number of ticks of 50, as this is about the max. of what can be shown without overlapping (IF the ticks are tilted at 45 degrees).
However, in my attempts I ran into the problem of Matplotlib not setting the x-axis to what I desire, whatever I try. I need to plot multiple datasets, for all of which the time runs from -15.8 through somewhere around 1200-1800.
I tried several solutions I found online, but all to no avail. The code below does not work, as it does not show the correct ticks. The range stops well before the last number in the timepoints list.
import numpy as np
from matplotlib import pyplot as plt
# Mock data
timepoints = list(np.arange(-15.8, 1276.2, 4))
patient_counts = np.random.randint(300, 600, len(timepoints))
x_tick_pos = [i + 0.5 for i in range(len(timepoints))]
# Plot barplot
fig, ax = plt.subplots(figsize=(16, 10))
ax.bar(x_tick_pos, patient_counts, align='center', width=1.0)
# Set x axis ticks
ax.set_xticklabels(timepoints, rotation=45)
ax.locator_params(axis='x', nbins=20)
plt.show()
Clearly, the x-axis does not come close to the expected values.
EDIT
To expand, this question is a follow-up from this thread. The code based on the answer in that question is as follows
# Plot barplot
fig, ax = plt.subplots(figsize=(16, 10))
ax.bar(x_tick_pos, patient_counts, align='center', width=1.0)
# Set x axis ticks
ax.set_xticks(x_tick_pos)
ax.set_xticklabels(x_ticks, rotation=45)
This appears to set the right x-ticks, except they overlap a lot- hence why I want only a max of 50 ticks to show:
This might be a simple case of fixing the x_tick_pos list expression. In your mock example, if we print them out ...
x_tick_pos = [i + 0.5 for i in range(len(timepoints))]
print(x_tick_pos[:5], x_tick_pos[-5:])
... we get what your figure reflects:
[0.5, 1.5, 2.5, 3.5, 4.5] [318.5, 319.5, 320.5, 321.5, 322.5]
Changing the assignment to
x_tick_pos = [i + 0.5 for i timepoints]
would appear to give the expected ticks.
The issue is that the positioning of the ticks is written so that they line up with another graph above this one, as per this post.
There are two solutions:
forget about positioning the ticks relative to another graph, in case this bar plot is plotted in a standalone fashion
resetting the ticks after plotting the bar plot to give them correct labels:
# Plot barplot
fig, ax = plt.subplots(figsize=(16, 10))
ax.bar(x_tick_pos, patient_counts, align='center', width=1.0)
# Set x axis ticks
ticks_step = int(len(missings_df.index) / 50) # 50 here is the max. nr of ticks
x_ticks = [missings_df.index[i] for i in range(0, len(missings_df.index), int(len(missings_df)/50))]
x_tick_pos = [i + 0.5 for i in range(0, len(missings_df.index), int(len(missings_df)/50))]
ax.set_xticks(x_tick_pos)
ax.set_xticklabels(x_ticks, rotation=45)
This correctly plots the x-axis:
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:
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?
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()
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()
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()
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
I want to suppress pyplot's automatically generated tick labels in favor of my own labels. When I suppress the y-tick labels using pyplot.yticks([]) in the following Python script,
from matplotlib import pyplot as plt
num_points = 10
data = [i for i in range(num_points)]
fig = plt.figure()
ax = plt.subplot(1,1,1)
ax.plot(data)
ax.set_yscale('log')
plt.yticks([])
plt.text(1, 7, '10 data points')
plt.show()
pyplot suppresses y-tick labels, as desired:
But when the num_points is less than ten, pyplot ignores pyplot.yticks([]), inserts its automatically generated tick labels, and produces
When I supply my own tick labels by supplying a list of value and a list of labels, via plt.yticks(values_list, labels_list), pyplot.yticks() accepts my labels, but it still draws its automatically generated tick labels, overwriting my labels.
If I change the nine-point log plot to a linear plot by omitting the ax.set_yscale('log') statement, pyplot does not draw automatically generate tick labels:
The problem appears to be related to log plots with fewer than ten points. How do I suppress the automatically generated tick labels in log plots having fewer than ten points?
When working with a log axis, not only the major ticks but also the minor ticks are shown by default. You can turn them off separately. Note that when there are only very few major ticks, the minor ticks also can get a label.
Also note it usually isn't a good idea to have zero values on a log scale. As log(0) is minus infinity, matplotlib has to do some fragile guesswork about the desired tick distances.
from matplotlib import pyplot as plt
from matplotlib import ticker
num_points = 10
data = [i+2 for i in range(num_points)]
fig = plt.figure()
ax = plt.subplot(1,1,1)
ax.plot(data)
ax.set_yscale('log')
ax.text(1, 7, '10 data points')
ax.yaxis.set_major_locator(ticker.NullLocator())
ax.yaxis.set_minor_locator(ticker.NullLocator())
plt.show()
The image shows the major ticks in blue and the minor ticks in red. As in this example there is only one major tick, some of the minor ticks also got a label.
I'm having some trouble setting up a pcolormesh plot with a colorbar that includes logarithmically spaced minor tick marks on the colorbar.
The closest I've come is something like this:
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
xbins = np.linspace(0, 1, 50)
ybins = np.linspace(0, 1, 50)
data = np.random.random((49,49))
fig, ax = plt.subplots()
im = ax.pcolormesh(xbins, ybins, data, norm=matplotlib.colors.LogNorm())
cb = fig.colorbar(im)
cb.ax.minorticks_on()
plt.savefig('test.png')
The trouble with this solution is that the minor ticks are spaced evenly in log space:
I'd like to set up the plot so I have evenly spaced minor ticks in linear space, which should show up unevenly spaced on this plot.
I know that I can manually set the minor tick labels using a FixedFormatter, but I'd prefer not to do that if possible since I will be making a large number of plots automatically.
I think the best way to custom colorbars' ticks is to use the "ticks" argument of the fig.colorbar method and not trying to modify the attributes of the axe that contains the colorbar.
from matplotlib.ticker import LogLocator
"..."
cb = fig.colorbar(im, ticks = LogLocator(subs=range(10)))
Added for posterity:
From this answer: #JoeKington https://stackoverflow.com/a/20079644/230468:
minorticks = p.norm(np.arange(1, 10, 2))
cb.ax.xaxis.set_ticks(minorticks, minor=True)
This is annoying that you have to create the tick locations manually, but it seems to work.