4

How can i make the plot to show in the x-axis scientific notation but only in multiples of 3? that is: 1e-3, 1e-6, 1e-9, etc.

The following example generates a scale in 1e-4, which is not multiple of 3. My desire is a scale in either 10^-3, so x-axis [0:0.3] or a scale in 10^-6, so x-axis [0:300]

import numpy as np
import matplotlib.pyplot as plt

# time
T = 30e-6
N = 10
time = np.linspace(0, N*T, num=N)

plt.plot(time,np.sin(time))
ax = plt.gca()
ax.ticklabel_format(style='sci',axis='both',scilimits=(0,0))
5
  • 1
    So, what is the expected result in your example? To have also a scale, that is 10⁻³, and the ticks going from 0 to 30, instead of 0 to 3? Or to have just a scale labelled 10.10⁻³? Commented Jul 31 at 14:13
  • 1
    Will you actually be doing a loglog plot (or semilog plot) rather than one with a linear scale? Commented Jul 31 at 14:15
  • @chrslg, i want a scale in 10^-3, so from 0 to 0.3 or a scale in 10^-6, so from 0 to 300 Commented Jul 31 at 14:22
  • @MattPitkin, i need it in linear scale Commented Jul 31 at 14:23
  • 1
    To manually achieve this, you can set ax.ticklabel_format(style='sci',axis='both',scilimits=(-3,-3)) for 10^(-3), and likewise scilimits=(-6,-6) for 10^(-6). Unfortunately you can only set the limits for the exponents to one fixed value (as I did here) or provide a lower and upper bound, but there is no direct way to explicitly provide a list of allowed exponents. Commented Jul 31 at 14:32

2 Answers 2

2

You can create your own custom ScalarFormatter and set the order of magnitude value (which give the scale factor). For example,

import numpy as np
import matplotlib.pyplot as plt

from matplotlib.ticker import ScalarFormatter


# create a custom for which you can manually set the "order of magnitude"
class CustomScalerFormatter(ScalarFormatter):
    def set_locs(self, locs):
        # docstring inherited
        self.locs = locs
        if len(self.locs) > 0:
            if self._useOffset:
                self._compute_offset()
            # comment out this line below from the original implementation,
            # so that you can manually set the order of magnitude 
            #self._set_order_of_magnitude()
            self._set_format()


# time
T = 30e-6
N = 10
time = np.linspace(0, N*T, num=N)

plt.plot(time, np.sin(time))
ax = plt.gca()

# create custom formatter for y-axis with 10^-3 scale
yformatter = CustomScalerFormatter()
yformatter.orderOfMagnitude = -3

# create custom formatter for y-axis with 10^-6 scale
xformatter = CustomScalerFormatter()
xformatter.orderOfMagnitude = -6

ax.yaxis.set_major_formatter(yformatter)
ax.xaxis.set_major_formatter(xformatter)

enter image description here

Sign up to request clarification or add additional context in comments.

1 Comment

Wow, 20 seconds apart, and we have basically the same answer, and even used the same "use xaxis for one version and yaxis for the other". Normally in such case, being 20 seconds later, I would have removed my answer. But since they are not identical, I let both: your answer deals with offset, and mine doesn't. And mine does the "let the superclass compute the order of magnitude then adjust it" tricks.
2

Two different methods can be used, depending on whether you want to keep the factor (order of magnitude) or not

One way could be to define your own formatter function

Own formatter function

def myform(x, pos):
    e=0
    while x>1e3 or x<-1e3:
        x/=1000
        e+=3
    while x>-1 and x<1 and x!=0:
        x*=1000
        e-=3
    if e==0: return f'${x:.1f}$'
    return f'${x:.0f}.10^{{{e}}}$'

ax.xaxis.set_major_formatter(myform)

Advantage of this is that you have a total control on how the label is formatted.
Drawback is that you can't have the order of magnitude factor (at least I don't know how to combine it, other that with the second method, but if you use the second method, then, there is no point in still using it). Also, you need to reinvent the wheel (to decide how to display a number)

The way I compute the exponent is of course naive. You may want to replace that with some log10, or the like. But I wanted to make it explicit. Plus, as naive as is it, the computation cost is still negligible before anything related to plotting anyway (it even with very large of very small numbers, it is still just a handful multiplication)

Define your own variant of scalar formatter

Other method is to still let the control of all that to ScalarFormatter (this is what you are doing now. ScalarFormatter is the default one in your case). But subsume the way it decides the order of magnitude factor

# Just a subclass of ScalarFormatter that does almost nothing different
class MyForm(ticker.ScalarFormatter):
    def __init__(self, *arg, **kw):
        ticker.ScalarFormatter.__init__(self, *arg, **kw)
        # I "hardcode" in it the two options you set with `style=sci` and `scilimits`
        self.set_scientific(True)
        self.set_powerlimits((0,0))

    # And the only thing that this scalarformatter does differently as the parent one is the way it chooses the order of magnitude
    def _set_order_of_magnitude(self):
        # Rather than rewriting one, I rely on the original one
        super()._set_order_of_magnitude()
        # But after it has done its computation (leading to -4 in your example)
        # I force it to choose one that is multiple of 3, my removing the remainder
        self.orderOfMagnitude -= self.orderOfMagnitude%3

ax.yaxis.set_major_formatter(MyForm())

Advantage is that you don't reinvent the wheel. That is the same scalar formatter you were using. Drawback is the one of not reinventing the wheel generally speaking: you are stuck with the choices made by those who invented it (for example, see how in the first version, I used latex to have nice power notation).

Result

Following picture demonstrates it on your example, using the first one on x-axis and second one on yaxis

Both formatter, one per axis

Whole code

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

def myform(x, pos):
    e=0
    while x>1e3 or x<-1e3:
        x/=1000
        e+=3
    while x>-1 and x<1 and x!=0:
        x*=1000
        e-=3
    if e==0: return f'${x:.1f}$'
    return f'${x:.0f}.10^{{{e}}}$'

class MyForm(ticker.ScalarFormatter):
    def __init__(self, *args, **kw):
        ticker.ScalarFormatter.__init__(self, *args, **kw)
        self.set_scientific(True)
        self.set_powerlimits((0,0))
#
    def _set_order_of_magnitude(self):
        super()._set_order_of_magnitude()
        self.orderOfMagnitude -= self.orderOfMagnitude%3


T = 30e-6
N = 10
time = np.linspace(0, N*T, num=N) 

plt.plot(time,np.sin(time))
ax = plt.gca()

ax.xaxis.set_major_formatter(myform)
ax.yaxis.set_major_formatter(MyForm())
plt.show()

(ok, the choice of myform for the function name and MyForm for the class name is not very good. But anyway, you are not expected to use both. And I am sure you'll come up with better names)

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.