This question already has answers here:
Matplotlib log scale tick label number formatting
(6 answers)
Closed 2 years ago.
I'd like the y axis to show only the number 100, 200, and 300, and not in scientific notation. Any thoughts?
Current plot
Simplified code:
from matplotlib import pyplot as plt
import numpy as np
x = np.logspace(2, 6, 20)
y = np.logspace(np.log10(60), np.log10(300), 20)
plt.scatter(x, y[::-1])
plt.xscale('log')
plt.yscale('log')
plt.show()
The major and minor locators determine the positions of the ticks. The standard positions are set via the AutoLocator. The NullLocator removes them. A MultipleLocator(x) shows ticks every multiple x.
For the y axis, setting standard tick positions shows the ticks at the top closer to each other, as determines by the log scale. Doing the same for the x axis, however, due to the large range, would put them too close together. So, for the x axis the positions determined by the LogLocator can stay in place.
The formatters control how the ticks should be displayed. The ScalarFormatter sets the default way. There is an option scilimits that determines for which ranges of values a scientific notation should be used. As 1.000.000 usually gets displayed as 1e6, setting scilimits=(-6,9) avoids this.
from matplotlib import pyplot as plt
from matplotlib import ticker
import numpy as np
x = np.logspace(2, 6, 20)
y = np.logspace(np.log10(60), np.log10(300), 20)
plt.scatter(x, y[::-1])
plt.xscale('log')
plt.yscale('log')
ax = plt.gca()
# ax.xaxis.set_major_locator(ticker.AutoLocator())
ax.xaxis.set_minor_locator(ticker.NullLocator()) # no minor ticks
ax.xaxis.set_major_formatter(ticker.ScalarFormatter()) # set regular formatting
# ax.yaxis.set_major_locator(ticker.AutoLocator()) # major y tick positions in a regular way
ax.yaxis.set_major_locator(ticker.MultipleLocator(100)) # major y tick positions every 100
ax.yaxis.set_minor_locator(ticker.NullLocator()) # no minor ticks
ax.yaxis.set_major_formatter(ticker.ScalarFormatter()) # set regular formatting
ax.ticklabel_format(style='sci', scilimits=(-6, 9)) # disable scientific notation
plt.show()
Related
I have a bar graph that is log scaled that I am trying to change the y ticks from 10^1, 10^2, etc., to whole numbers. I have tried setting the tick values manually, and tried setting the values from the data, and also setting the format to scalar. One thing I notice in all of the questions I am looking at is that my construction of the graph doesn't include subplot.
def confirmed_cases():
x = df['Date']
y = df['Confirmed']
plt.figure(figsize=(20, 10))
plt.bar(x, y)
plt.yscale('log')
# plt.yticks([0, 100000, 250000, 500000, 750000, 1000000, 1250000])
# plt.get_yaxis().set_major_formatter(matplotlib.ticker.ScalarFormatter())
plt.title('US Corona Cases By Date')
plt.xlabel('Date')
plt.ylabel('Confirmed Cases')
plt.xticks(rotation=90)
There a few issues:
The formatter needs to be placed at the yaxis of the ax. Useplt.gca() to get the current ax. Note that there is no function plt.get_yaxis().
The scalar formatter starts using exponential notation for large numbers. To prevent that, set_powerlimits((m,n)) makes sure the powers are only shown for values outside the range 10**m and 10**n.
In a log scale, major ticks are used for values 10**n for integer n. The other ticks or minor ticks, at positions k*10**n for k from 2 to 9. If there are only a few major ticks visible, the minor ticks can also get a tick label. To suppress both the minor tick marks and their optional labels, a NullFormatter can be used.
Avoid using a tick at zero for a log-scale axis. Log(0) is minus infinity.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib
plt.figure(figsize=(20, 10))
plt.bar(np.arange(100), np.random.geometric(1/500000, 100))
plt.yscale('log')
formatter = matplotlib.ticker.ScalarFormatter()
formatter.set_powerlimits((-6,9))
plt.gca().yaxis.set_major_formatter(formatter)
plt.gca().yaxis.set_minor_locator(matplotlib.ticker.NullLocator())
plt.yticks([100000, 250000, 500000, 750000, 1000000, 1250000])
plt.show()
I can't get any tick marks to appear when I have a narrow range of data and log formatting. I found a similar problem that talked about forcing a minimum number of ticks and tried that solution, but it did not seem to help.
What I want to do is have the Y range be automatically expanded until at least two ticks can be included, including one major tick (so it gets a label). I can't do anything to manual or custom because a lot of different data goes through this routine and it is only rarely that the range is so tight that no labels appear.
Here is an example that preserves as much of my local environment as possible:
import matplotlib
import numpy as np
import pylab as plt
fig=plt.figure(figsize=(15, 20))
locmin = matplotlib.ticker.LogLocator(base=10.0,subs=(.1,.2,.3,.4,.5,.6,.7,.8,.9),numticks=15)
ax6 = plt.subplot(616)
plt.plot(np.random.random(1000)*4+14, 'b')
plt.plot(np.random.random(1000)*4+14, 'r')
plt.minorticks_on()
plt.ylabel('Y')
plt.yscale('log')
ax6.yaxis.set_minor_locator(locmin)
ax6.yaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
plt.show()
The result is this plot here, which has no Y labels...
You can get the array of major_ticks and minor_ticklocs. Then find the bounds for the given scaled y limits. Then you can explicitly set the ylim of the plot. Since the values in the example scales between 10 and 20, the 10 from major_ticks and 20 from minor_ticks are shown. Consider below code:
import matplotlib
import numpy as np
import pylab as plt
fig=plt.figure(figsize=(15, 20))
locmin = matplotlib.ticker.LogLocator(base=10.0,subs=(.1,.2,.3,.4,.5,.6,.7,.8,.9),numticks=15)
ax6 = plt.subplot(616)
plt.plot(np.random.random(1000)*4+14, 'b')
plt.plot(np.random.random(1000)*4+14, 'r')
plt.minorticks_on()
plt.ylabel('Y')
plt.yscale('log')
ax6.yaxis.set_minor_locator(locmin)
ax6.yaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
plt.tick_params(axis='y', which='minor')
ax6.yaxis.set_minor_formatter(matplotlib.ticker.FormatStrFormatter("%.1f"))
tickArr = np.concatenate((plt.yticks()[0], ax6.yaxis.get_minorticklocs()))
ylim_min = tickArr[tickArr < plt.ylim()[0]].max()
ylim_max = tickArr[tickArr > plt.ylim()[1]].min()
plt.ylim([ylim_min, ylim_max])
plt.show()
This question already has answers here:
Changing the tick frequency on the x or y axis
(13 answers)
Closed 4 years ago.
When generating a Matplotlib line or scatter plot, what axis attribute specifies the spacing between ticks? I do not want to explicitly specify where each tick should be as prompted by this related question
ax.ticks(np.arange(-100, 100, 5))
What is the matplotlib axis attribute that controls the tick spacing? It should behave something like the following.
ax.set_x_tick_spacing(5)
This would use the same default xlim and origin point (usually 0) as the default settings.
A more recent answer to the related question illustrates using the matplotlib.ticker.MultipleLocator object. The axis ticks are this type of matplotlib object. Here is an example of it's use.
ax.xaxis.set_major_locator(matplotlib.ticker.MultipleLocator(5))
will place ticks 5 units apart on the x-axis, and
ax.xaxis.set_minor_locator(matplotlib.ticker.MultipleLocator(1))
will place minor ticks 1 unit apart on the x-axis.
Here is an example from the matplotlib Plotting Cookbook
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
X = np.linspace(-15, 15, 1024)
Y = np.sinc(X)
ax = plt.axes()
ax.xaxis.set_major_locator(matplotlib.ticker.MultipleLocator(5))
ax.xaxis.set_minor_locator(matplotlib.ticker.MultipleLocator(1))
plt.plot(X, Y, c = 'k')
plt.show()
When making a semi-log plot (y is log), the minor tick marks (8 in a decade) on the y axis appear automatically, but it seems that when the axis range exceeds 10**10, they disappear. I tried many ways to force them back in, but to no avail. It might be that they go away for large ranges to avoid overcrowding, but one should have a choice?
solution for matplotlib >= 2.0.2
Let's consider the following example
which is produced by this code:
import matplotlib.pyplot as plt
import matplotlib.ticker
import numpy as np
y = np.arange(12)
x = 10.0**y
fig, ax=plt.subplots()
ax.plot(x,y)
ax.set_xscale("log")
plt.show()
The minor ticklabels are indeed gone and usual ways to show them (like plt.tick_params(axis='x', which='minor')) fail.
The first step would then be to show all powers of 10 on the axis,
locmaj = matplotlib.ticker.LogLocator(base=10,numticks=12)
ax.xaxis.set_major_locator(locmaj)
where the trick is to set numticks to a number equal or larger the number of ticks (i.e. 12 or higher in this case).
Then, we can add minor ticklabels as
locmin = matplotlib.ticker.LogLocator(base=10.0,subs=(0.2,0.4,0.6,0.8),numticks=12)
ax.xaxis.set_minor_locator(locmin)
ax.xaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
Note that I restricted this to include 4 minor ticks per decade (using 8 is equally possible but in this example would overcrowd the axes). Also note that numticks is again (quite unintuitively) 12 or larger.
Finally we need to use a NullFormatter() for the minor ticks, in order not to have any ticklabels appear for them.
solution for matplotlib 2.0.0
The following works in matplotlib 2.0.0 or below, but it does not work in matplotlib 2.0.2.
Let's consider the following example
which is produced by this code:
import matplotlib.pyplot as plt
import matplotlib.ticker
import numpy as np
y = np.arange(12)
x = 10.0**y
fig, ax=plt.subplots()
ax.plot(x,y)
ax.set_xscale("log")
plt.show()
The minor ticklabels are indeed gone and usual ways to show them (like plt.tick_params(axis='x', which='minor')) fail.
The first step would then be to show all powers of 10 on the axis,
locmaj = matplotlib.ticker.LogLocator(base=10.0, subs=(0.1,1.0, ))
ax.xaxis.set_major_locator(locmaj)
Then, we can add minor ticklabels as
locmin = matplotlib.ticker.LogLocator(base=10.0, subs=(0.1,0.2,0.4,0.6,0.8,1,2,4,6,8,10 ))
ax.xaxis.set_minor_locator(locmin)
ax.xaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
Note that I restricted this to include 4 minor ticks per decade (using 8 is equally possible but in this example would overcrowd the axes). Also note - and that may be the key here - that the subs argument, which gives the multiples of integer powers of the base at which to place ticks (see documentation), is given a list ranging over two decades instead of one.
Finally we need to use a NullFormatter() for the minor ticks, in order not to have any ticklabels appear for them.
From what I can tell, as of Matplotlib 3.5.2:
With 8 or fewer major tick marks, the minor ticks show
with 9 to 11 major tick marks, subs="auto" will show the minor tick marks
with 12 or more, you need to set subs manually.
Using subs="auto"
from matplotlib import pyplot as plt, ticker as mticker
fig, ax = plt.subplots()
y = np.arange(11)
x = 10.0**y
ax.semilogx(x, y)
ax.xaxis.set_major_locator(mticker.LogLocator(numticks=999))
ax.xaxis.set_minor_locator(mticker.LogLocator(numticks=999, subs="auto"))
Setting subs manually
from matplotlib import pyplot as plt, ticker as mticker
fig, ax = plt.subplots()
y = np.arange(12)
x = 10.0**y
ax.semilogx(x, y)
ax.xaxis.set_major_locator(mticker.LogLocator(numticks=999))
ax.xaxis.set_minor_locator(mticker.LogLocator(numticks=999, subs=(.2, .4, .6, .8)))
Major ticks with empty labels will generate ticks but no labels.
ax.set_yticks([1.E-6,1.E-5,1.E-4,1.E-3,1.E-2,1.E-1,1.E0,1.E1,1.E2,1.E3,1.E4,1.E5,])
ax.set_yticklabels(['$10^{-6}$','','','$10^{-3}$','','','$1$','','','$10^{3}$','',''])
Wrapping the excellent answer from importanceofbeingernest for matplotlib >= 2.0.2 into a function:
import matplotlib.pyplot as plt
from typing import Optional
def restore_minor_ticks_log_plot(
ax: Optional[plt.Axes] = None, n_subticks=9
) -> None:
"""For axes with a logrithmic scale where the span (max-min) exceeds
10 orders of magnitude, matplotlib will not set logarithmic minor ticks.
If you don't like this, call this function to restore minor ticks.
Args:
ax:
n_subticks: Number of Should be either 4 or 9.
Returns:
None
"""
if ax is None:
ax = plt.gca()
# Method from SO user importanceofbeingernest at
# https://stackoverflow.com/a/44079725/5972175
locmaj = mpl.ticker.LogLocator(base=10, numticks=1000)
ax.xaxis.set_major_locator(locmaj)
locmin = mpl.ticker.LogLocator(
base=10.0, subs=np.linspace(0, 1.0, n_subticks + 2)[1:-1], numticks=1000
)
ax.xaxis.set_minor_locator(locmin)
ax.xaxis.set_minor_formatter(mpl.ticker.NullFormatter())
This function can then be called as
plt.plot(x,y)
plt.xscale("log")
restore_minor_ticks_log_plot()
or more explicitly
_, ax = plt.subplots()
ax.plot(x, y)
ax.set_xscale("log")
restore_minor_ticks_log_plot(ax)
The answers here ignore the convenient fact that the log-scaled axis already has the requisite locators. At least as of Matplotlib 3.6, it is enough to use set_params() with values that force minor ticks:
import matplotlib.pyplot as plt
import numpy as np
y = np.arange(12)
x = 10.0**y
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_xscale('log')
ax.xaxis.get_major_locator().set_params(numticks=99)
ax.xaxis.get_minor_locator().set_params(numticks=99, subs=[.2, .4, .6, .8])
plt.show()
This question already has an answer here:
Matplotlib: setting x-limits also forces tick labels?
(1 answer)
Closed 5 years ago.
I am making a figure where the x-axis should be logarithmically spaced, but I want to manually set the tick labels, and I want the tick labels to appear in ordinary '%.2f' notation, not exponential notation. The following solution based on Matplotlib - logarithmic scale, but require non-logarithmic labels suggests to use ScalarFormatter, but does not work with matplotlib 2.0:
x = np.logspace(2, 3, 100)
y = x
fig, ax = plt.subplots(1, 1)
xscale = ax.set_xscale('log')
ax.set_xticks((100, 200, 300, 500))
xlim = ax.set_xlim(100, 1000)
from matplotlib.ticker import ScalarFormatter
ax.get_xaxis().set_major_formatter(ScalarFormatter())
__=ax.plot(x, y)
The use of a ScalarFormatter is sure possible. You would then need to make sure that no minor ticklabels are shown as seen in this question: Matplotlib: setting x-limits also forces tick labels?
In your case the code would then look like:
import matplotlib.pyplot as plt
import numpy as np
x = np.logspace(2, 3, 100)
y = x
fig, ax = plt.subplots(1, 1)
xscale = ax.set_xscale('log')
ax.set_xticks((100, 200, 300, 500))
xlim = ax.set_xlim(100, 1000)
import matplotlib.ticker
ax.get_xaxis().set_major_formatter(matplotlib.ticker.ScalarFormatter())
ax.get_xaxis().set_minor_formatter(matplotlib.ticker.NullFormatter())
__=ax.plot(x, y)
plt.show()
Because you are hard-coding the min and max for the axis, it looks like you are trying to create the graph one-off rather than programatically for more general data. In this case, and especially because you are already getting a reference to the x-xais, you could place the tick label strings in a list and use the axis method set_ticklabels. In general, see the API for axis and tick objects.