Philip
Philip

Reputation: 1901

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

Upvotes: 12

Views: 40668

Answers (7)

kilojoules
kilojoules

Reputation: 10083

What would it mean to have exponential scaling?

In log axis, a power relationship, y=x**a, will yield a straight line with slope a. This is because we plot log(y) = log(x**a) = a log(x).

With a exponential axis, the relationship y=log(a x) should yield a straight line with slope a: exp(y) = exp[log(a x)] = a x.

How to deal with this simply

just take the exponent yourself and label the axis to indicate that this operation was done.

plt.plot(np.exp(your_x_data), np.exp(your_y_data))
plt.xlabel('exp(x)')
plt.xlabel('exp(y)')

Upvotes: 0

ImportanceOfBeingErnest
ImportanceOfBeingErnest

Reputation: 339170

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

Upvotes: 12

j0lama
j0lama

Reputation: 77

Add this to your code:

plt.yscale('symlog')

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

Upvotes: -1

Philip
Philip

Reputation: 1901

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)

Upvotes: 3

mjp
mjp

Reputation: 1679

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

Upvotes: -1

magu_
magu_

Reputation: 4856

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

Upvotes: 4

Tommy
Tommy

Reputation: 13632

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.

Upvotes: -1

Related Questions