Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to plot x-axis in scientific notation but only mutiples of 3?

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))
like image 440
stefaniecg Avatar asked Oct 24 '25 14:10

stefaniecg


2 Answers

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

like image 59
Matt Pitkin Avatar answered Oct 26 '25 05:10

Matt Pitkin


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)

like image 34
chrslg Avatar answered Oct 26 '25 04:10

chrslg



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!