99

I have a matplotlib figure which I am plotting data that is always referred to as nanoseconds (1e-9). On the y-axis, if I have data that is tens of nanoseconds, ie. 44e-9, the value on the axis shows as 4.4 with a +1e-8 as an offset. Is there anyway to force the axis to show 44 with a +1e-9 offset?

The same goes for my x-axis where the axis is showing +5.54478e4, where I would rather it show an offset of +55447 (whole number, no decimal - the value here is in days).

I've tried a couple things like this:

p = axes.plot(x,y)
p.ticklabel_format(style='plain')

for the x-axis, but this doesn't work, though I'm probably using it incorrectly or misinterpreting something from the docs, can someone point me in the correct direction?

Thanks, Jonathan

Problem illustration


I tried doing something with formatters but haven't found any solution yet...:

myyfmt = ScalarFormatter(useOffset=True)
myyfmt._set_offset(1e9)
axes.get_yaxis().set_major_formatter(myyfmt)

and

myxfmt = ScalarFormatter(useOffset=True)
myxfmt.set_portlimits((-9,5))
axes.get_xaxis().set_major_formatter(myxfmt)

On a side note, I'm actually confused as to where the 'offset number' object actually resides...is it part of the major/minor ticks?

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Jonathan
  • 1,205
  • 1
  • 10
  • 12

8 Answers8

104

I had exactly the same problem, and these lines fixed the problem:

from matplotlib.ticker import ScalarFormatter

y_formatter = ScalarFormatter(useOffset=False)
ax.yaxis.set_major_formatter(y_formatter)
NichtJens
  • 1,709
  • 19
  • 27
Gonzalo
  • 1,344
  • 1
  • 10
  • 8
  • 7
    A one-liner would be: `ax.get_yaxis().get_major_formatter().set_useOffset(False)` – Dataman Apr 06 '16 at 12:48
  • 3
    for noobs like me dont forget the `from matplotlib.ticker import ScalarFormatter` for @Gonzalo 's code to work or simply use @Dataman 's solution above – champost Jun 24 '16 at 11:25
37

A much easier solution is to simply customize the tick labels. Take this example:

from pylab import *

# Generate some random data...
x = linspace(55478, 55486, 100)
y = random(100) - 0.5
y = cumsum(y)
y -= y.min()
y *= 1e-8

# plot
plot(x,y)

# xticks
locs,labels = xticks()
xticks(locs, map(lambda x: "%g" % x, locs))

# ytikcs
locs,labels = yticks()
yticks(locs, map(lambda x: "%.1f" % x, locs*1e9))
ylabel('microseconds (1E-9)')

show()

alt text

Notice how in the y-axis case, I multiplied the values by 1e9 then mentioned that constant in the y-label


EDIT

Another option is to fake the exponent multiplier by manually adding its text to the top of the plot:

locs,labels = yticks()
yticks(locs, map(lambda x: "%.1f" % x, locs*1e9))
text(0.0, 1.01, '1e-9', fontsize=10, transform = gca().transAxes)

EDIT2

Also you can format the x-axis offset value in the same manner:

locs,labels = xticks()
xticks(locs, map(lambda x: "%g" % x, locs-min(locs)))
text(0.92, -0.07, "+%g" % min(locs), fontsize=10, transform = gca().transAxes)

alt text

Amro
  • 123,847
  • 25
  • 243
  • 454
  • That was exactly what I did, at first. Unfortunately, I couldn't find an easy way to set/show the axis multiplier (other than explicitly putting it in the y-axis label, as you did.). If you don't mind not having the axis multiplier label, this is the simpler way. Either way, +1 from me. – Joe Kington Sep 09 '10 at 21:32
  • 1
    @Joe Kington: you can add it manually as text... see the edit above :) – Amro Sep 09 '10 at 21:46
  • Great! I'm going to try your approach with the labels for the x-axis. I'll take the floor of the first x value, then remove it from every x-value and add a "+minxval" as a label. I can't figure out how else to format the x-tick offset. I'm fine with the magnitude of the offset, I just need for it to display as a non-exponential value. – Jonathan Sep 10 '10 at 13:34
  • Wow. Great job in showing how you can really take control of matplotlib and tweak it to your needs and really and some pizazz to your plots. – physicsmichael Feb 18 '11 at 08:28
  • How do you change the fontsize of 1e-9 in the figure? – an offer can't refuse Aug 03 '15 at 13:56
33

You have to subclass ScalarFormatter to do what you need... _set_offset just adds a constant, you want to set ScalarFormatter.orderOfMagnitude. Unfortunately, manually setting orderOfMagnitude won't do anything, as it's reset when the ScalarFormatter instance is called to format the axis tick labels. It shouldn't be this complicated, but I can't find an easier way to do exactly what you want... Here's an example:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import ScalarFormatter, FormatStrFormatter

class FixedOrderFormatter(ScalarFormatter):
    """Formats axis ticks using scientific notation with a constant order of 
    magnitude"""
    def __init__(self, order_of_mag=0, useOffset=True, useMathText=False):
        self._order_of_mag = order_of_mag
        ScalarFormatter.__init__(self, useOffset=useOffset, 
                                 useMathText=useMathText)
    def _set_orderOfMagnitude(self, range):
        """Over-riding this to avoid having orderOfMagnitude reset elsewhere"""
        self.orderOfMagnitude = self._order_of_mag

# Generate some random data...
x = np.linspace(55478, 55486, 100) 
y = np.random.random(100) - 0.5
y = np.cumsum(y)
y -= y.min()
y *= 1e-8

# Plot the data...
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, y, 'b-')

# Force the y-axis ticks to use 1e-9 as a base exponent 
ax.yaxis.set_major_formatter(FixedOrderFormatter(-9))

# Make the x-axis ticks formatted to 0 decimal places
ax.xaxis.set_major_formatter(FormatStrFormatter('%0.0f'))
plt.show()

Which yields something like: alt text

Whereas, the default formatting would look like: alt text

Hope that helps a bit!

Edit: For what it's worth, I don't know where the offset label resides either... It would be slightly easier to just manually set it, but I couldn't figure out how to do so... I get the feeling that there has to be an easier way than all of this. It works, though!

Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • Thanks! Subclassing the ScalarFormatter works great! But I guess I didn't clearly state what I wanted for the x-axis. I would like to keep the offset for the x-axis, but format the value of the offset so it isn't shown as an exponent. – Jonathan Sep 10 '10 at 13:30
  • This is the only method that worked for me! Thanks :) – Ocean Scientist Jul 23 '20 at 05:04
11

Similar to Amro's answer, you can use FuncFormatter

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter

# Generate some random data...
x = np.linspace(55478, 55486, 100) 
y = np.random.random(100) - 0.5
y = np.cumsum(y)
y -= y.min()
y *= 1e-8

# Plot the data...
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, y, 'b-')

# Force the y-axis ticks to use 1e-9 as a base exponent 
ax.yaxis.set_major_formatter(FuncFormatter(lambda x, pos: ('%.1f')%(x*1e9)))
ax.set_ylabel('microseconds (1E-9)')

# Make the x-axis ticks formatted to 0 decimal places
ax.xaxis.set_major_formatter(FuncFormatter(lambda x, pos: '%.0f'%x))
plt.show()
idrinkpabst
  • 1,838
  • 23
  • 25
7

Gonzalo's solution started working for me after having added set_scientific(False):

ax=gca()
fmt=matplotlib.ticker.ScalarFormatter(useOffset=False)
fmt.set_scientific(False)
ax.xaxis.set_major_formatter(fmt)
ha7ilm
  • 1,433
  • 13
  • 11
7

As has been pointed out in the comments and in this answer, the offset may be switched off globally, by doing the following:

matplotlib.rcParams['axes.formatter.useoffset'] = False
Evgeni Sergeev
  • 22,495
  • 17
  • 107
  • 124
4

I think that a more elegant way is to use the ticker formatter. Here is an example for both xaxis and yaxis:

from pylab import *
from matplotlib.ticker import MultipleLocator, FormatStrFormatter

majorLocator   = MultipleLocator(20)
xFormatter = FormatStrFormatter('%d')
yFormatter = FormatStrFormatter('%.2f')
minorLocator   = MultipleLocator(5)


t = arange(0.0, 100.0, 0.1)
s = sin(0.1*pi*t)*exp(-t*0.01)

ax = subplot(111)
plot(t,s)

ax.xaxis.set_major_locator(majorLocator)
ax.xaxis.set_major_formatter(xFormatter)
ax.yaxis.set_major_formatter(yFormatter)

#for the minor ticks, use no labels; default NullFormatter
ax.xaxis.set_minor_locator(minorLocator)
Bogdan
  • 593
  • 7
  • 14
  • 1
    This does not answer the question, which is _how to specify the offset_ and/or the _factor used in scientific notation_. – sodd Sep 07 '13 at 19:42
  • @nordev Even if my answer does not specifically answer the question it still gives a hint. The message is that you can chose another formatter and get what you want instead of a date from my example. In the scientific world Julian day is the norm, or you can use date as in my example. What I was trying to suggest is that a different approach can be taken. Sometimes a question may be asked because the person doesn't have a better idea at the moment. Alternative solutions should not be discarded or treated with disrespect. All in all I did not deserve the -1 vote. – Bogdan Mar 21 '14 at 00:58
2

For the second part, without manually resetting all the ticks again, this was my solution:

class CustomScalarFormatter(ScalarFormatter):
    def format_data(self, value):
        if self._useLocale:
            s = locale.format_string('%1.2g', (value,))
        else:
            s = '%1.2g' % value
        s = self._formatSciNotation(s)
        return self.fix_minus(s)
xmajorformatter = CustomScalarFormatter()  # default useOffset=True
axes.get_xaxis().set_major_formatter(xmajorformatter)

obviously you can set the format string to whatever you want.

astrojuanlu
  • 6,744
  • 8
  • 45
  • 105