gladys0313
gladys0313

Reputation: 2679

python: plot unevenly distributed axis

I am using python and have a plot which looks like this: enter image description here

Now the problem is that, as most bins are in the range 0-500 on x-axis, so I want to make the x-axis like [0, 100, 200, 300, 400, 500, 1000, 1500, 2000, 2500] and each interval has the same length.

I don't know how to do this in python. Any idea?

Upvotes: 1

Views: 3757

Answers (2)

gboffi
gboffi

Reputation: 25023

As already said, you have to map the original abscissae to a new range, and then draw the xtics accordingly... The first part is the toughest, of course, and can be done in different ways, my take uses a vectorized approach using numpy and computes the function body at runtime using eval.

def make_xmap(l):
    from numpy import array
    ll = len(l)
    dy = 1.0 / (ll-1)
    def f(l, i):
        if i == 0 : return "0.0"
        y0 = i*dy-dy
        x0, x1 = l[i-1:i+1]
        return '%r+%r*(x-%r)/%r'%(y0,dy,x0,x1-x0)
    fmt = 'numpy.where(x<%f,%s%s'
    body = ' '.join(fmt%(j,f(l,i),"," if i<(ll-1) else ", 1.0") for i, j in enumerate(l))
    tail = ')'*ll
    def xm(x):
        x = array(x)
        return eval(body+tail)
    return xm

import numpy
xm = make_xmap([0.,200.,1000.])
x = (-10.,0.,100.,200.,600.,1000.,1010)
print xm(x)

# [0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0]

Note that you have to import numpy in your code, because we have used numpy.where to construct the function body... If you prefer to import numpy as np modify the fmt string in the factory function...

The second part is easier, if you have an x and an y array to plot, with the subdivision from your example, you can do

import numpy # I touched this point before...

...

intervals = [0., 100., 200., 300., 400., 500., 1000., 1500., 2000., 2500.]
xm = make_xmap(intervals)
plt.plot(xm(x),y)
plt.xticks(xm(intervals), [str(xi) for xi in intervals])
plt.show()

A small optimization

You may want to change

...
    tail = ')'*ll
    def xm(x):
        x = array(x)
        return eval(body+tail)
...

to

...
    tail = ')'*ll
    code = compile(body+tail,'','eval')
    def xm(x):
        x = array(x)
        return eval(code)
...

This small optimization avoids the compilation of the code string every time you call the mapping function, and is of course more relevant if the mapping is used many times on short inputs.

Upvotes: 0

Ami Tavory
Ami Tavory

Reputation: 76297

Perhaps there's a simpler way to do this, but it's certainly possible to do so in pyplot using these two steps:

  1. Plot a different function, namely one with the same y values but different x values

  2. Manipulate the x-ticks so that it appears like you've plotted your original function (but with a different axis).

I'll start with 2. Note the existence of the xticks, which allows you to do stuff like this:

ticks = [0, 100, 200, 300, 400, 500, 1000, 1500, 2000, 2500]

xticks(range(10), ticks)

This allows you to place both the locations of the xticks, as well as the labels.

Now, for 1., you just need to translate your original x array to a new_x array, which is spread out in arange(10), but non-linearly, according to your labels. If your points are in the array x, then using np.interp1d:

from scipy import interpolate

new_x = interpolate.interp1d(ticks, arange(10))(x)  

In conclusion, use plot(new_x, y) with the xticks above.

Upvotes: 2

Related Questions