Python matplotlib colorbar scientific notation base - python

I am trying to customise a colorbar on my matpllotlib contourf plots. Whilst I am able to use scientific notation I am trying to change the base of the notation - essentially so that my ticks would be in the range of (-100,100) rather than (-10,10).
For example, this produces a simple plot...
import numpy as np
import matplotlib.pyplot as plt
z = (np.random.random((10,10)) - 0.5) * 0.2
fig, ax = plt.subplots()
plot = ax.contourf(z)
cbar = fig.colorbar(plot)
cbar.formatter.set_powerlimits((0, 0))
cbar.update_ticks()
plt.show()
like so:
However, I would like the label above the colorbar to be 1e-2 and the numbers to range from -10 to 10.
How would I go about this?

A possible solution can be to subclass the ScalarFormatter and fix the order of magnitude as in this question: Set scientific notation with fixed exponent and significant digits for multiple subplots
You would then call this formatter with the order of magnitude as the argument order, OOMFormatter(-2, mathText=False). mathText is set to false to obtain the notation from the question, i.e.
while setting it to True, would give .
You can then set the formatter to the colorbar via the colorbar's format argument.
import numpy as np; np.random.seed(0)
import matplotlib.pyplot as plt
import matplotlib.ticker
class OOMFormatter(matplotlib.ticker.ScalarFormatter):
def __init__(self, order=0, fformat="%1.1f", offset=True, mathText=True):
self.oom = order
self.fformat = fformat
matplotlib.ticker.ScalarFormatter.__init__(self,useOffset=offset,useMathText=mathText)
def _set_order_of_magnitude(self):
self.orderOfMagnitude = self.oom
def _set_format(self, vmin=None, vmax=None):
self.format = self.fformat
if self._useMathText:
self.format = r'$\mathdefault{%s}$' % self.format
z = (np.random.random((10,10)) - 0.5) * 0.2
fig, ax = plt.subplots()
plot = ax.contourf(z)
cbar = fig.colorbar(plot, format=OOMFormatter(-2, mathText=False))
plt.show()
For matplotlib versions < 3.1 the class needs to look like this:
class OOMFormatter(matplotlib.ticker.ScalarFormatter):
def __init__(self, order=0, fformat="%1.1f", offset=True, mathText=True):
self.oom = order
self.fformat = fformat
matplotlib.ticker.ScalarFormatter.__init__(self,useOffset=offset,useMathText=mathText)
def _set_orderOfMagnitude(self, nothing):
self.orderOfMagnitude = self.oom
def _set_format(self, vmin, vmax):
self.format = self.fformat
if self._useMathText:
self.format = '$%s$' % matplotlib.ticker._mathdefault(self.format)

Similar to what #ImportanceOfBeingErnes described, you could use a FuncFormatter (docs) to which you just pass a function to determine the tick labels. This removes the auto generation of the 1e-2 header for your colorbar, but I imagine you can manually add that back in (I had trouble doing it, though was able to add it on the side). Using a FuncFormatter, you can just generate string tick values which has the advantage of not having to accept the way python thinks a number should be displayed.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as tk
z = (np.random.random((10,10)) - 0.5) * 0.2
levels = list(np.linspace(-.1,.1,9))
fig, ax = plt.subplots()
plot = ax.contourf(z, levels=levels)
def my_func(x, pos):
label = levels[pos]
return str(label*100)
fmt1 = tk.FuncFormatter(my_func)
cbar = fig.colorbar(plot, format=fmt1)
cbar.set_label("1e-2")
plt.show()
This will generate a plot which looks like this.

Related

Set matplotlib tick locators, but specify spacing around them

For example if I have the following:
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
fig, ax = plt.subplots()
ax.set_xlim(left=0, right=11)
ax.xaxis.set_major_locator(ticker.MultipleLocator(1))
Which looks as:
Here the range is set to include 0 and 11 so that there's some spacing around the plotted values, but the data only contains values of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - so I'd like to not have 0 and 11 on the xaxis.
Looking at the documentation for ticker.MultipleLocator (here) it's not clear how this should be done (of if it can be with a locator). I tried to use the view_limits method but it just seems to return a tuple.
Ideally the values 0 and 11 would be gone, and the plot would look as:
edit 1
The following "works"
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
fig, ax = plt.subplots()
ax.set_xlim(left=1e-2, right=11 - 1e-2)
ax.xaxis.set_major_locator(ticker.MultipleLocator(1))
Though removing fractional amounts in order to not display something isn't a solution here as it's constrained to the base value of the MultipleLocator.
For example - what if I wanted the following:
ax.set_xlim(left=1e-2, right=11 - 1e-2)
ax.xaxis.set_major_locator(ticker.MultipleLocator(0.5))
Which looks as:
Then I have 10.5 and so on, whereas I might still like the spacing to be a particular value not limited to the MultipleLocator size.
Getting the lower limit is something that is a bit tricky and not by default included in any of the Matplotlib ticker. However, taking a look at the source code
(https://github.com/matplotlib/matplotlib/blob/v3.5.1/lib/matplotlib/ticker.py#L2734-L2751) gives a good hint how to implement this by deriving a new MyMultipleLocator class from MultipleLocator by yourself. Here is a piece of code that should work:
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator, _Edge_integer
import numpy as np
class MyMultipleLocator(MultipleLocator):
def __init__(self, base=1.0, offset=0.):
self._edge = _Edge_integer(base, 0)
self._offset = offset
def tick_values(self, vmin, vmax):
# we HAVE to re-implement this method as it is called by
# xaxis.set_major_locator(...)
vmin = self._edge.ge(vmin)
step = self._edge.step
n = (vmax - vmin + 0.001 * step) // step
locs = self._offset + vmin - step + np.arange(n + 3) * step
return self.raise_if_exceeds(locs)
fig, ax = plt.subplots()
x = np.arange(1, 11)
ax.plot(x, np.random.randint(-3, 3, size=x.size))
tick_spacing = 2
ax.xaxis.set_major_locator(MyMultipleLocator(base=tick_spacing, offset=0))
You can now additionally change the offset manually in the last line. You could even do this automatically by checking if the minimum (the nearest int, that is) is an odd or an even number, but if I understand the question correctly then this is not required here.
Output from the above code:
example 1
fig, ax = plt.subplots()
ax.xaxis.set_major_locator(MyMultipleLocator(base=1, offset=0))
ax.set_xlim(0.5, 10.5)
Which gives:
example 2
fig, ax = plt.subplots()
ax.xaxis.set_major_locator(MyMultipleLocator(base=2, offset=0.5))
ax.set_xlim(0., 11)
Which gives:

Number of decimals in a subplot in scientific notation pyplot

I am making a figure with subplots with different y-axes. The y-axis of the last plot needs to be represented in scientific notation, and I would like the numbers on the axis to only display 2 decimals, i.e., 2.15 instead 2.1565. I have been trying to do format the numbers for hours without any success. Any suggestion on how to do it?
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.ticker as mtick
from matplotlib.ticker import FormatStrFormatter
fig, axs = plt.subplots(2, 3)
y_pos = [0.2,0.4,0.6,0.8]
plt.setp(axs, xticks=[0.2,0.4,0.6,0.8], xticklabels=['A','B','C','D'],xlim=[0.1,0.9],facecolor='#F4F4F4')
#P-2
Pval = [3.3705E+00,3.4262E+00,3.5093E+00,3.4882E+00]
axs[0,0].set_ylim(3.3,3.55)
axs[0,0].set_yticks(np.arange(3.3,3.56, 0.05))
axs[0,0].grid(zorder=0)
axs[0,0].bar(y_pos,Pval,width=0.1,color='#ADDDCE')
axs[0,0].set_title(r'$Initial$')
axs[0,0].grid(True,linestyle='--',color='#D6D8CD')
#P-1
Pval = [1.6667E+00,1.6079E+00,1.6087E+00,1.6132E+00]
axs[0,1].set_ylim(1.5,1.7)
axs[0,1].set_yticks(np.arange(1.5,1.71, 0.05))
axs[0,1].grid(zorder=0)
axs[0,1].bar(y_pos,Pval,width=0.1,color='#FFC347')
axs[0,1].set_title(r'$Test 1$')
axs[0,1].grid(True,linestyle='--',color='#D6D8CD')
#P1
Pval = [9.9458E-01,9.4241E-01,9.4569E-01,9.4014E-01]
axs[0,2].set_ylim(0.8,1)
axs[0,2].set_yticks(np.arange(0.8,1.01, 0.05))
#
axs[0,2].grid(zorder=0)
axs[0,2].bar(y_pos,Pval,width=0.1,color='#FC746C')
#
axs[0,2].set_title(r'$Test 2$')
axs[0,2].grid(True,linestyle='--',color='#D6D8CD')
#P2
Pval = [2.4196E+00,2.3292E+00,2.3499E+00,2.3213E+00]
axs[1,0].set_ylim(1.8,2.5)
axs[1,0].set_yticks(np.arange(1.8,2.7, 0.2))
#
axs[1,0].grid(zorder=0)
axs[1,0].bar(y_pos,Pval,width=0.1,color='#70AE98')
#
axs[1,0].set_title(r'$ \omega $')
axs[1,0].grid(True,linestyle='--',color='#D6D8CD')
#P3
Pval = [1.5646E+01,1.5346E+01,1.5538E+01,1.5298E+01]
axs[1,1].set_ylim(15,15.7)
axs[1,1].set_yticks(np.arange(15.,15.9, 0.2))
#X
axs[1,1].grid(zorder=0)
axs[1,1].bar(y_pos,Pval,width=0.1,color='#F0A35E')
#
axs[1,1].set_title(r'$ \Pi $')
axs[1,1].grid(True,linestyle='--',color='#D6D8CD')
#P4
Pval = [2.1391E+02,2.1148E+02,2.1434E+02,2.1085E+02]
axs[1,2].set_ylim(2.09E+02,2.15E+02)
axs[1,2].set_yticks(np.arange(2.09E+02,2.16E+02,0.95))
#axs[1,2].ticklabel_format(style='sci',scilimits=(200,300),axis='y')
#
#axs[1,2].yaxis.set_major_formatter(MathTextSciFormatter('%.2e'))
#m = grabMaxExponent(2)
#axs[1,2].yaxis.set_major_formatter(OOMFormatter(2, "%1.1f"))
#axs[1,2].yaxis.set_major_formatter(mtick.FormatStrFormatter('%.2f'))
axs[1,2].ticklabel_format(axis='y', style='sci', scilimits=(2,2))
axs[1,2].yaxis.major.formatter._useMathText = True
#axs[1,2].yaxis.set_major_formatter(FormatStrFormatter('%.2f'))
#axs[1,2].yaxis.set_major_formatter(MathTextSciFormatter('%.2e'))
#axs[1,2].ticklabel_format(useOffset=False)
#X
axs[1,2].grid(zorder=0)
axs[1,2].bar(y_pos,Pval,width=0.1,color='#CA7E8D')
#
axs[1,2].set_title(r'$\gamma$')
axs[1,2].grid(True,linestyle='--',color='#D6D8CD')
fig.tight_layout()
plt.show()
ScalarFormatter doesn't support custom formatting. What you can try is overriding the method responsible for the formatting:
(NOTE: This works for Matplotlib 3.0.x - Python 3.x, NOT Matplotlib 2.0.x.)
from matplotlib.ticker import ScalarFormatter
...
# P4
# Extend ScalarFormatter
class MyScalarFormatter(ScalarFormatter):
# Override '_set_format' with your own
def _set_format(self):
self.format = '%.2f' # Show 2 decimals
# Your data as before
Pval = [2.1391E+02,2.1148E+02,2.1434E+02,2.1085E+02]
axs[1,2].set_ylim(2.09E+02,2.15E+02)
axs[1,2].set_yticks(np.arange(2.09E+02,2.16E+02,0.95))
# New code
custom_formatter = MyScalarFormatter(useMathText=True)
axs[1,2].yaxis.set_major_formatter(custom_formatter)
axs[1,2].yaxis.major.formatter.set_powerlimits((2,2))
#axs[1,2].yaxis.major.formatter.set_scientific(True) # Redundant: True is default
# Rest code as before
axs[1,2].grid(zorder=0)
axs[1,2].bar(y_pos,Pval,width=0.1,color='#CA7E8D')
axs[1,2].set_title(r'$\gamma$')
axs[1,2].grid(True,linestyle='--',color='#D6D8CD')
fig.tight_layout()
plt.show()
Output
It rounds your y-axis values, though. But you could work it out a bit this way to achieve the result you want. Take a look at ScalarFormatter source.

Add meaningful minor ticks to a modified axis?

This example is specifically relating to plotting data as a function of log(redshift+1) and having a reference redshift axis but can be easily generalised to any functional modification.
I've written a neat little function (with the help of some question/answers on here) that allows me to easily add a redshift axis to the top of a log(1+redshift) plot. I am really struggling to get meaningful minor ticks (and would rather not share my dismal efforts!).
Here is the code, including example plot:
In this case, I would like redshifts at every 0.1 increment not occupied by a major tick, with the flexibility of changing that 0.1 in the function call.
import matplotlib.pyplot as plt
import numpy as np
def add_zaxis(axis,denomination):
oldx = axis.get_xlim()
axis.set_xlim(0., None)
zspan = [(10**x)-1 for x in axis.get_xlim()]
denom = denomination
zmax = int(np.floor(zspan[1]/denom))*denom
zspan[1] = zmax
k = len(np.arange(zspan[0],zspan[1],denom))+1
zs = np.linspace(zspan[0],zspan[1],k)
z_ticks = [np.log10(1+x) for x in zs]
axz = axis.twiny()
axz.set_xticks(z_ticks)
axz.set_xticklabels(['{:g}'.format(y) for y in zs])
axz.set_xlim(oldx)
axis.set_xlim(oldx)
return axz
data = np.random.randn(500)
data = data[data>0.]
fig, ax = plt.subplots(1)
plt.hist(np.log10(data+1), bins=22)
ax.set_xlabel('log(z+1)')
ax.minorticks_on()
axz = add_zaxis(ax,.3)
axz.set_xlabel('z')
axz.minorticks_on()
The idea would be to use a FixedLocator to position the ticks on the axis. You may then have one FixedLocator for the major ticks and one for the minor ticks.
import matplotlib.pyplot as plt
import matplotlib.ticker
import numpy as np
def add_zaxis(ax,d=0.3, dminor=0.1):
f = lambda x: np.log10(x+1)
invf = lambda x: 10.0**x - 1.
xlim = ax.get_xlim()
zlim = [invf(x) for x in xlim]
axz = ax.twiny()
axz.set_xlim(xlim)
zs = np.arange(0,zlim[1],d)
zpos = f(zs)
axz.xaxis.set_major_locator(matplotlib.ticker.FixedLocator(zpos))
axz.xaxis.set_major_formatter(matplotlib.ticker.FixedFormatter(zs))
zsminor = np.arange(0,zlim[1],dminor)
zposminor = f(zsminor)
axz.xaxis.set_minor_locator(matplotlib.ticker.FixedLocator(zposminor))
axz.tick_params(axis='x',which='minor',bottom='off', top="on")
axz.set_xlabel('z')
data = np.random.randn(400)
data = data[data>0.]
fig, ax = plt.subplots(1)
plt.hist(np.log10(data+1), bins=22)
ax.set_xlabel('log(z+1)')
add_zaxis(ax)
ax.minorticks_on()
ax.tick_params(axis='x',which='minor',bottom='on', top="off")
plt.show()

Remove axis scale

I've spent some time fruitlessly searching for an answer to my question, so I think a new question is in order. Consider this plot:
The axes labels use scientific notation. On the y-axis, all is well. However, I have tried and failed to get rid off the scaling factor that Python added in the lower-right corner. I would like to either remove this factor completely and simply indicate it by the units in the axis title or have it multiplied to every tick label. Everything would look better than this ugly 1e14.
Here's the code:
import numpy as np data_a = np.loadtxt('exercise_2a.txt')
import matplotlib as mpl
font = {'family' : 'serif',
'size' : 12}
mpl.rc('font', **font)
import matplotlib.pyplot as plt
fig = plt.figure()
subplot = fig.add_subplot(1,1,1)
subplot.plot(data_a[:,0], data_a[:,1], label='$T(t)$', linewidth=2)
subplot.set_yscale('log')
subplot.set_xlabel("$t[10^{14}s]$",fontsize=14)
subplot.set_ylabel("$T\,[K]$",fontsize=14)
plt.xlim(right=max(data_a [:,0]))
plt.legend(loc='upper right')
plt.savefig('T(t).pdf', bbox_inches='tight')
Update: Incorporating Will's implementation of scientificNotation into my script, the plot now looks like
Much nicer if you ask me. Here's the complete code for anyone wanting to adopt some part of it:
import numpy as np
data = np.loadtxt('file.txt')
import matplotlib as mpl
font = {'family' : 'serif',
'size' : 16}
mpl.rc('font', **font)
import matplotlib.pyplot as plt
fig = plt.figure()
subplot = fig.add_subplot(1,1,1)
subplot.plot(data[:,0], data[:,1], label='$T(t)$', linewidth=2)
subplot.set_yscale('log')
subplot.set_xlabel("$t[s]$",fontsize=20)
subplot.set_ylabel("$T\,[K]$",fontsize=20)
plt.xlim(right=max(data [:,0]))
plt.legend(loc='upper right')
def scientificNotation(value):
if value == 0:
return '0'
else:
e = np.log10(np.abs(value))
m = np.sign(value) * 10 ** (e - int(e))
return r'${:.0f} \cdot 10^{{{:d}}}$'.format(m, int(e))
formatter = mpl.ticker.FuncFormatter(lambda x, p: scientificNotation(x))
plt.gca().xaxis.set_major_formatter(formatter)
plt.savefig('T(t).pdf', bbox_inches='tight', transparent=True)
Just divide the x-values by 1e14:
subplot.plot(data_a[:,0] / 1e14, data_a[:,1], label='$T(t)$', linewidth=2)
If you want to add the label to each individual tick, you'll have to provide a custom formatter, like in tom's answer.
If you want it to look like as nice as the ticks on your y-axis, you could provide a function to format it with LaTeX:
def scientificNotation(value):
if value == 0:
return '0'
else:
e = np.log10(np.abs(value))
m = np.sign(value) * 10 ** (e - int(e))
return r'${:.0f} \times 10^{{{:d}}}$'.format(m, int(e))
# x is the tick value; p is the position on the axes.
formatter = mpl.ticker.FuncFormatter(lambda x, p: scientificNotation(x))
plt.gca().xaxis.set_major_formatter(formatter)
Of course, this will clutter your x-axis up quite a bit, so you might end up needing to display them at an angle, for example.
You can also change the tick formatter with the ticker module.
An example would be to use a FormatStrFormatter:
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
fig,ax = plt.subplots()
ax.semilogy(np.linspace(0,5e14,50),np.logspace(3,7,50),'b-')
ax.xaxis.set_major_formatter(ticker.FormatStrFormatter('%.0e'))
Also see the answers here with lots of good ideas for ways to solve this.
In addition to the good answer from Will Vousden, you can set what you write in your ticks with:
plt.xticks(range(6), range(6))
the first range(6) is the location and the second is the label.

Specify format of floats for tick labels

I am trying to set the format to two decimal numbers in a matplotlib subplot environment. Unfortunately, I do not have any idea how to solve this task.
To prevent using scientific notation on the y-axis I used ScalarFormatter(useOffset=False) as you can see in my snippet below. I think my task should be solved by passing further options/arguments to the used formatter. However, I could not find any hint in matplotlib's documentation.
How can I set two decimal digits or none (both cases are needed)? I am not able to provide sample data, unfortunately.
-- SNIPPET --
f, axarr = plt.subplots(3, sharex=True)
data = conv_air
x = range(0, len(data))
axarr[0].scatter(x, data)
axarr[0].set_ylabel('$T_\mathrm{air,2,2}$', size=FONT_SIZE)
axarr[0].yaxis.set_major_locator(MaxNLocator(5))
axarr[0].yaxis.set_major_formatter(ScalarFormatter(useOffset=False))
axarr[0].tick_params(direction='out', labelsize=FONT_SIZE)
axarr[0].grid(which='major', alpha=0.5)
axarr[0].grid(which='minor', alpha=0.2)
data = conv_dryer
x = range(0, len(data))
axarr[1].scatter(x, data)
axarr[1].set_ylabel('$T_\mathrm{dryer,2,2}$', size=FONT_SIZE)
axarr[1].yaxis.set_major_locator(MaxNLocator(5))
axarr[1].yaxis.set_major_formatter(ScalarFormatter(useOffset=False))
axarr[1].tick_params(direction='out', labelsize=FONT_SIZE)
axarr[1].grid(which='major', alpha=0.5)
axarr[1].grid(which='minor', alpha=0.2)
data = conv_lambda
x = range(0, len(data))
axarr[2].scatter(x, data)
axarr[2].set_xlabel('Iterationsschritte', size=FONT_SIZE)
axarr[2].xaxis.set_major_locator(MaxNLocator(integer=True))
axarr[2].set_ylabel('$\lambda$', size=FONT_SIZE)
axarr[2].yaxis.set_major_formatter(ScalarFormatter(useOffset=False))
axarr[2].yaxis.set_major_locator(MaxNLocator(5))
axarr[2].tick_params(direction='out', labelsize=FONT_SIZE)
axarr[2].grid(which='major', alpha=0.5)
axarr[2].grid(which='minor', alpha=0.2)
See the relevant documentation in general and specifically
from matplotlib.ticker import FormatStrFormatter
fig, ax = plt.subplots()
ax.yaxis.set_major_formatter(FormatStrFormatter('%.2f'))
If you are directly working with matplotlib's pyplot (plt) and if you are more familiar with the new-style format string, you can try this:
from matplotlib.ticker import StrMethodFormatter
plt.gca().yaxis.set_major_formatter(StrMethodFormatter('{x:,.0f}')) # No decimal places
plt.gca().yaxis.set_major_formatter(StrMethodFormatter('{x:,.2f}')) # 2 decimal places
From the documentation:
class matplotlib.ticker.StrMethodFormatter(fmt)
Use a new-style format string (as used by str.format()) to format the
tick.
The field used for the value must be labeled x and the field used for
the position must be labeled pos.
The answer above is probably the correct way to do it, but didn't work for me.
The hacky way that solved it for me was the following:
ax = <whatever your plot is>
# get the current labels
labels = [item.get_text() for item in ax.get_xticklabels()]
# Beat them into submission and set them back again
ax.set_xticklabels([str(round(float(label), 2)) for label in labels])
# Show the plot, and go home to family
plt.show()
format labels using lambda function
3x the same plot with differnt y-labeling
Minimal example
import numpy as np
import matplotlib as mpl
import matplotlib.pylab as plt
from matplotlib.ticker import FormatStrFormatter
fig, axs = mpl.pylab.subplots(1, 3)
xs = np.arange(10)
ys = 1 + xs ** 2 * 1e-3
axs[0].set_title('default y-labeling')
axs[0].scatter(xs, ys)
axs[1].set_title('custom y-labeling')
axs[1].scatter(xs, ys)
axs[2].set_title('x, pos arguments')
axs[2].scatter(xs, ys)
fmt = lambda x, pos: '1+ {:.0f}e-3'.format((x-1)*1e3, pos)
axs[1].yaxis.set_major_formatter(mpl.ticker.FuncFormatter(fmt))
fmt = lambda x, pos: 'x={:f}\npos={:f}'.format(x, pos)
axs[2].yaxis.set_major_formatter(mpl.ticker.FuncFormatter(fmt))
You can also use 'real'-functions instead of lambdas, of course.
https://matplotlib.org/3.1.1/gallery/ticks_and_spines/tick-formatters.html
In matplotlib 3.1, you can also use ticklabel_format. To prevents scientific notation without offsets:
plt.gca().ticklabel_format(axis='both', style='plain', useOffset=False)

Categories

Resources