'How can I exponentially scale the Y axis with matplotlib

I'm trying to create a matplotlib plot with an exponential(?) Y axis like the fake one I've mocked up below. For my data I want to spread the values out as they approach the max Y value. And I'd like to compress the values as Y gets close to zero.

All the normal 'log' examples do the opposite: they compress values as they get away from zero. Which is what 'log' does of course. How can I create an exponential(?) scaling instead?

graph



Solution 1:[1]

From matplotlib 3.1 onwards you can define any custom scale easily via

ax.set_yscale('function', functions=(forward, inverse))

Also see https://matplotlib.org/3.1.1/gallery/scales/scales.html

In this case, e.g.:

from functools import partial

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(1, 40, 100)
y = np.linspace(1, 4, 100)

fig, ax = plt.subplots()
ax.plot(x, y)

# Set y scale to exponential
ax.set_yscale('function', functions=(partial(np.power, 10.0), np.log10))
ax.set(xlim=(1,40), ylim=(1,4))
ax.set_yticks([1, 3, 3.5, 3.75, 4.0])

plt.show()

enter image description here

Solution 2:[2]

I don't think its directly possible. But of course you can always try cheating. In my example I just write something else in the label:

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(1, 40, 100);
y = np.linspace(1, 4, 100);

# Actually plot the exponential values
plt.plot(x, np.e**y)
ax = plt.gca()

# Set x logaritmic
ax.set_xscale('log')

# Rewrite the y labels
y_labels = np.linspace(min(y), max(y), 4)
ax.set_yticks(np.e**y_labels)
ax.set_yticklabels(y_labels)

plt.show()

Which results into: enter image description here

Solution 3:[3]

This is not fully general because the locators are hard coded for my case. But this worked for me. I had to create a new scale called ExponentialScale used ma.power with a base of 1.1. Way too complicated for a seemingly simple thing:

class ExponentialScale(mscale.ScaleBase):
    name = 'expo'
    base = 1.1
    logbase = math.log(base)

def __init__(self, axis, **kwargs):
    mscale.ScaleBase.__init__(self)
    self.thresh = None #thresh

def get_transform(self):
    return self.ExponentialTransform(self.thresh)

def set_default_locators_and_formatters(self, axis):
    # I could not get LogLocator to do what I wanted. I don't understand
    # the docs about "subs" and the source was not clear to me.
    # So I just spell out the lines I want:
    major = [1, 5, 10, 12, 14, 16, 18, 20, 25, 28, 30] + range(31,60)
    axis.set_major_locator(ticker.FixedLocator(major))

class ExponentialTransform(mtransforms.Transform):
    input_dims = 1
    output_dims = 1
    is_separable = True

    def __init__(self, thresh):
        mtransforms.Transform.__init__(self)
        self.thresh = thresh

    def transform_non_affine(self, a):
        res = ma.power(ExponentialScale.base, a)
        return res

    def inverted(self):
        return ExponentialScale.InvertedExponentialTransform(self.thresh)

class InvertedExponentialTransform(mtransforms.Transform):
    input_dims = 1
    output_dims = 1
    is_separable = True

    def __init__(self, thresh):
        mtransforms.Transform.__init__(self)
        self.thresh = thresh

    def transform_non_affine(self, a):
        denom = np.repeat(ExponentialScale.logbase, len(a))
        return np.log(a) / denom

    def inverted(self):
        return ExponentialScale.ExponentialTransform(self.thresh)

Solution 4:[4]

simply add this into your code for log scale:

plt.figure()
ax = plt.subplot(111)
ax.set_yscale('log')

But if you do want a exponential scale, this question answers it: link to question

Solution 5:[5]

Add this to your code:

plt.yscale('symlog')

Source: https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.yscale.html

Solution 6:[6]

I assume you mean X axis because in your mock figure, the X axis is exponential, not the Y axis.

You can do something like this:

...
ax = plt.subplot(111)
ax.plot(Xs,Ys,color='blue',linewidth=2)
....
xlabs = [pow(10,i) for i in range(0,6)]
ax.set_xticklabels(xlabs)
ax.set_xticks(xlabs)

What I am doing here is manually creating a list of 6 Xs where each is represented by 10^i, i.e., 10^1,10^2,.... This will put X tick marks, and label them correctly, at [1, 10, 100, 1000, 10000, 100000]. If you need more labels, change the 6.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Kolen Cheung
Solution 2 magu_
Solution 3
Solution 4 Community
Solution 5 j0lama
Solution 6