'Proper automatic logarithmic axes when plot range less than a factor 10 (Matplotlib)
When the data range of logarithmic plots does not include a full factor of 10, the current (v3.5) Matplotlib code does not automatically produce publication-quality figures (see below). How can one correct this limitation?
Here is an example of a minimal code to produce a figure with a logarithmic axis and a range that does not include a full factor of 10 from the plotted major tick mark.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0.2, 8, 100)
plt.semilogx(x, np.sin(x))
The resulting figure below shows a single tick label and is hard to read and not publication quality
Using the suggestions from this related question, I could plot all minor tick marks and switch to non-scientific notation as follows
import matplotlib.pyplot as plt
from matplotlib import ticker
import numpy as np
fig, ax = plt.subplots()
x = np.linspace(0.2, 8, 100)
plt.semilogx(x, np.sin(x))
formatter = ticker.FuncFormatter(lambda y, _: f'{y:.3g}')
ax.xaxis.set_major_formatter(formatter)
ax.xaxis.set_minor_formatter(formatter)
This generates the following figure, which has too dense minor tick marks labels and is also not publication-quality
I am aware that I could edit the tick labels by hand, but I am looking for a way to produce proper tick labels automatically for many figures, with different axis ranges.
The tick marks labels I am looking for are like the ones that Mathematica nicely generates in the same situation as follows. Is it possible to achieve something similar in Matplotlib?
To clarify my question, as a practical test, could one design a function that automatically produces proper tick labels when running my above procedure with either of the following three vectors x:
x = np.linspace(0.2, 8, 100)
x = np.linspace(3, 60, 100)
x = np.linspace(0.06, 0.5, 100)
NB: editing the tick labels by typing different values for each different case is not a useful solution, as my function has to run in a general procedure to be used with unknown x range.
Solution 1:[1]
To reproduce something close to the mathematica's plot, you need to turn off the labeling of the minor ticks, i.e. the parts that are messed up.
Then you want to have a custom labeling of the major ticks. This can be done using FixedLocator.
import matplotlib.pyplot as plt
from matplotlib import ticker
import numpy as np
fig, ax = plt.subplots()
x = np.linspace(0.2, 8, 100)
plt.semilogx(x, np.sin(x))
formatter = ticker.FuncFormatter(lambda y, _: f'{y:.3g}')
ax.xaxis.set_major_formatter(formatter)
#ax.xaxis.set_minor_formatter(formatter) #comment this line out
ax.xaxis.set_major_locator(ticker.FixedLocator([0.2, 0.5, 1, 2, 5]))
Output:
** First update:
For automatic spacing, I would recommend just employing the matplotlib original major_formatter that gives x-label only at points of which the log10 is an integer, i.e.:
import matplotlib.pyplot as plt
from matplotlib import ticker
import numpy as np
fig, ax = plt.subplots()
x = np.linspace(0.2, 8, 100)
plt.semilogx(x, np.sin(x))
formatter = ticker.FuncFormatter(lambda y, _: f'{y:.3g}')
ax.xaxis.set_major_formatter(formatter)
This seems to give ideal labeling spacings for every possible range of x for plotting purpose, irrespective of figure size, number of zeros, etc.
For your case of x = np.linspace(0.2, 8, 100), it gives

On the other extreme, if x = np.linspace(0.000002, 800, 100):

After such plot generation, one can always use FixedLocator if more xlabels (like 0,2, 0.5, 2, 5, 20, 50, ..) are needed/modified.
** Second update:
For the three cases that only span two orders of magnitude, one can automatically output the xticks (major ticks/the xlabels locations) with a xticksfinder function, without worrying too much about the number of zeros and figure size (which will lead to xlabels overlapping). As a general rule of thumb, one would want to specify {1, 2, 5} times 10^n, where n is the order of magnitude. Then the following xticksfinder function would be helpful
def order(num):
return math.floor(math.log(num, 10))
def xticksfinder(x):
x = x.tolist()
xtickers = []
for i in range(order(x[0]), order(x[-1])+1):
xtickers.append(1*math.pow(10, i))
xtickers.append(2*math.pow(10, i))
xtickers.append(5*math.pow(10, i))
xticks = [x[0]] + [item for item in xtickers if x[0] < item <= x[-1]]
xticks = [int(k) if k >=1 else k for k in xticks]
return xticks
Then you can call it in FixedLocator:
import matplotlib.pyplot as plt
from matplotlib import ticker
import numpy as np
fig, ax = plt.subplots()
x = np.linspace(0.2, 8, 100)
plt.semilogx(x, np.sin(x))
formatter = ticker.FuncFormatter(lambda y, _: f'{y:.3g}')
ax.xaxis.set_major_formatter(formatter)
#ax.xaxis.set_minor_formatter(formatter) #comment this line out
ax.xaxis.set_major_locator(ticker.FixedLocator(xticksfinder(x)))
For x = np.linspace(0.2, 8, 100), the output is:

Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |






