Reputation: 43
I'm having trouble with matplotlib (version 3.1.3) : I would like to add custom ticks and tick labels on a log scale axis while preserving scientific notation.
To say it otherwise: I want to add custom ticks on a log scale axis and label them using the good old-fashioned '%1.1e' (or whatever numbers) format but, for instance, instead of having '2.5e-02', I would like to have '2.5 x 10^-2' (or '2.5 \times 10^{-2}' in latex).
So I start with a minimum working piece of code without custom ticks:
import matplotlib as mpl
import matplotlib.pyplot as plt
print('MATPLOTLIB VERSION : %s' % mpl.__version__)
plt.style.use("default")
# DATA
x = [0.1, 0.075, 0.05, 0.025, 0.01, 0.0075, 0.005, 0.0025, 0.001, 0.00075, 0.0005, 0.00025, 0.0001, 7.5e-05, 5e-05, 2.5e-05, 1e-05, 1e-06, 1e-07, 1e-08, 1e-09, 1e-10]
y = x
fig = plt.figure()
ax = plt.axes()
plt.loglog()
plt.minorticks_off()
path = ax.plot(x, y)
plt.savefig('test.png')
which gives:
Nice but, as I said, I would like to add custom ticks on the xaxis. More precisely, I would like to put limits to the axis and define equally log-spaced labels between those limits. Let's say I want 4 ticks; it gives the following piece of code:
import matplotlib as mpl
import matplotlib.pyplot as plt
print('MATPLOTLIB VERSION : %s' % mpl.__version__)
plt.style.use("default")
# DATA
x = [0.1, 0.075, 0.05, 0.025, 0.01, 0.0075, 0.005, 0.0025, 0.001, 0.00075, 0.0005, 0.00025, 0.0001, 7.5e-05, 5e-05, 2.5e-05, 1e-05, 1e-06, 1e-07, 1e-08, 1e-09, 1e-10]
y = x
xmin = min(x)
xmax = max(x)
ymin = min(y)
ymax = max(y)
# XTICKS
nbdiv = 4
xTicks = []
k = pow((xmin/xmax),1./(nbdiv-1.))
for i in range(0,nbdiv):
xTicks.append(xmax*pow(k,i))
# PLOT
fig = plt.figure()
ax = plt.axes()
plt.loglog()
plt.minorticks_off()
plt.axis([xmin,xmax,ymin,ymax])
plt.xticks(xTicks)
path = ax.plot(x, y)
plt.savefig('test_working_4.png')
which provides the following plot:
That's kind of the result I wanted to obtain. However, if the number of ticks (nbdiv) changes, for instance becomes equal to 5, I get:
And this time only the first and last ticks are labelled. It appears that only numbers which are equal (or at least close) to an integer power of ten (10^n) are labelled. I tried to change the default tick format with a matplot.ticker.ScalarFormatter but I didn't manage to tune it to solve this problem. I also tried LogFormatterMathText and LogFormatterSciNotation, it wasn't better.
The issue in itself doesn't seem so difficult to me, so I don't understand why I'm having so much trouble... What am I doing wrong? Does someone has keys to make me understand my errors?
In any case, I thank for reading and I thank you in advance for your response.
P.S.: Sorry for potential english mistakes, it's not my native language.
Upvotes: 1
Views: 3246
Reputation: 1018
Solved, based on your code above. This one is much simpler. You need to use xticklabels.
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt
from sympy import pretty_print as pp, latex
print('MATPLOTLIB VERSION : %s' % mpl.__version__)
plt.style.use("default")
# DATA
x = [0.1, 0.075, 0.05, 0.025, 0.01, 0.0075, 0.005, 0.0025, 0.001, 0.00075, 0.0005, 0.00025, 0.0001, 7.5e-05, 5e-05, 2.5e-05, 1e-05, 1e-06, 1e-07, 1e-08, 1e-09, 1e-10]
y = x
xmin = min(x)
xmax = max(x)
ymin = min(y)
ymax = max(y)
# XTICKS
nbdiv = 5
xTicks = []
xticklabels = []
k = pow((xmin/xmax),1./(nbdiv-1.))
for i in range(0,nbdiv):
xTicks.append(xmax*pow(k,i))
printstr = '{:.2e}'.format(xmax*pow(k,i))
ls = printstr.split('e')
xticklabels.append((ls[0]+' x $10^{' +ls[1] + '}$'))
# PLOT
fig = plt.figure()
ax = plt.axes()
plt.loglog()
plt.minorticks_off()
plt.axis([xmin,xmax,ymin,ymax])
plt.xticks(xTicks)
path = ax.plot(x, y)
plt.savefig('test_working_4.png')
ax.set_xticklabels(xticklabels)
Upvotes: 0
Reputation: 43
Sorry for double posting but I came with a solution which, even if unsatisfactory, may be useful for others.
I find my solution unsatisfactory because it involves converting the format "by hand" with the following function of mine (I am no expert in Python so I am sure it could be simplified/optimized):
def convert_e_format_to_latex(numberAsStr):
# CONVERTS THE STRING numberAsStr IN FORMAT '%X.Ye' TO A LATEX 'X \\times 10^{Y}'
baseStr = list(numberAsStr)
ind = 0
i = 0
flag = True
nStr = len(baseStr)
while (i < nStr and flag):
if (baseStr[i] == 'e' or baseStr[i] == 'E'): # NOT USING FIND BECAUSE 'e' CAN ALSO BE CAPITAL
ind = i
flag = False
i += 1
if (flag):
print('ERROR: badly formatted input number')
return ''
else:
expo = str(int(''.join(baseStr[ind+1:nStr]))) # GET RID OF POTENTIAL ZEROS
root = ''.join(baseStr[0:ind])
indP = root.find('.')
integerPart = int(root[0:indP]) #integer
decimalPart = root[indP+1:ind] #string
if (integerPart == 1): #DETECTING IF THE ROOT IS ONE (positive value)
x = ''.center(ind-(indP+1),'0')
if (decimalPart == x):
return '$10^{'+expo+'}$'
else:
return '$'+str(integerPart)+'.'+decimalPart+' \\times 10^{'+expo+'}$'
elif (integerPart== -1): #DETECTING IF THE ROOT IS ONE (positive value)
x = ''.center(ind-(indP+1),'0')
if (decimalPart == x):
return '$-10^{'+expo+'}$'
else:
return '$'+str(integerPart)+'.'+decimalPart+' \\times 10^{'+expo+'}$'
else:
return '$'+str(integerPart)+'.'+decimalPart+' \\times 10^{'+expo+'}$'
Then I add, in my previous piece of code:
for i in range(0,nbdiv):
xTicksStr.append(convert_e_format_to_latex('%1.1e'%xTicks[i]))
And I change the xticks instruction in the plot which becomes:
plt.xticks(xTicks,xTicksStr)
This gives the wanted output:
It works, but it is so complicated... I am pretty sure I missed a simpler functionality. What do you think?
Upvotes: 0