0

I'm using Python to plot several straight lines on the same plot. I have a great variability in values magnitude over x values (called with variable q in my code), so I want to put them at the same distance on the x-axis, in order to have a clear vision on the first part of the graph.

Here my code:

def c(m,x):
'''
Linear cost function, describing costs for each weight range
:param m: the slope, modelling rates
:param x: the abscissa, modelling quantity ordered
:return: the cost value for each quantity
'''
    return m * x

for i in range(0,9):
    w = np.arange(0., q[9], 0.01)
    plt.plot(w,c(r[i],w),'b--',linewidth=0.3)
    plt.plot( [q[i],breakpoints[i]] , [c(r[i], q[i]), c(r[i], breakpoints[i])], 'r')
    plt.plot(q[i + 1], c(r[i], breakpoints[i]), 'r.')

plt.show()

For the sake of simplicity, here there is all data involved in my code snippet:

enter image description here

As you can see, this is the classical quantity discount study. So, if I simply plot these values, I can't distinguish what is happening at the first straight lines, since little space is reserved to the first q values, see below:

enter image description here

Now I show you the zoomed graph on first q values:

enter image description here

The solution I have thought about is to plot all q[] values at same distance: see the next picture. enter image description here So all I want to achieve is simply to have all q[] values at the same distance on x-axis, independently on their value. How can I do this?

Bernheart
  • 607
  • 1
  • 8
  • 17
  • Thank you. I've edited my question. – Bernheart Jul 25 '17 at 16:27
  • Using an axis like you show would be possible, but then straight lines would not be as straight lines any more (they would be curved and/or have kinks). Maybe you first want to check if logarithmic scaling goes in the right direction for what you want (`plt.gca().set_xscale("log", nonposx='clip')`). – ImportanceOfBeingErnest Jul 25 '17 at 16:33
  • If I'm not wrong, they should remain straight lines, since a linear transformation on a single axis would be performed. I simply want each interval on x-axis to have the same length (for istance, 2 cm for the interval [0,0.1], 2cm for interval [100,250]). By the way, thank you for your help – Bernheart Jul 25 '17 at 18:31
  • It's not a linear transformation if the space occupied by the interval 0 to 0.1 is the same as from e.g. 10 to 20. – ImportanceOfBeingErnest Jul 25 '17 at 18:36
  • Why not? You are simply scaling by a constant factor – Bernheart Jul 25 '17 at 19:33
  • Good luck with finding that "factor". ;-) – ImportanceOfBeingErnest Jul 25 '17 at 21:19
  • :))) finding that constant is a horse of a different colour!!! – Bernheart Jul 25 '17 at 21:45
  • Some people would consider finding the holy grail difficult because it's very well hidden; others will not even try to search for it as it doesn't exist. It's your choice. Concerning the question, maybe you can paint an image of the plot how you envision it, including all the lines, such that one may try to find a solution for your actual problem. – ImportanceOfBeingErnest Jul 25 '17 at 21:51

1 Answers1

3

As said in the comments, when manipulating scale to show uneven spacings, straight lines would not be as straight lines any more.

Below is a code that implements a scale like desired in the question.

import numpy as np
from numpy import ma
from matplotlib import scale as mscale
from matplotlib import transforms as mtransforms
from matplotlib.ticker import FixedLocator


class SegmentedScale(mscale.ScaleBase):
    name = 'segmented'

    def __init__(self, axis, **kwargs):
        mscale.ScaleBase.__init__(self)
        self.points = kwargs.get('points',[0,1])
        self.lb = self.points[0]
        self.ub = self.points[-1]

    def get_transform(self):
        return self.SegTrans(self.lb, self.ub, self.points)

    def set_default_locators_and_formatters(self, axis):
        axis.set_major_locator(FixedLocator(self.points))

    def limit_range_for_scale(self, vmin, vmax, minpos):
        return max(vmin, self.lb), min(vmax, self.ub)

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

        def __init__(self, lb, ub, points):
            mtransforms.Transform.__init__(self)
            self.lb = lb
            self.ub = ub
            self.points = points

        def transform_non_affine(self, a):
            masked = a # ma.masked_where((a < self.lb) | (a > self.ub), a)
            return np.interp(masked, self.points, np.arange(len(self.points)))

        def inverted(self):
            return SegmentedScale.InvertedSegTrans(self.lb, self.ub, self.points)

    class InvertedSegTrans(SegTrans):

        def transform_non_affine(self, a):
            return np.interp(a, np.arange(len(self.points)), self.points)
        def inverted(self):
            return SegmentedScale.SegTrans(self.lb, self.ub, self.points)

# Now that the Scale class has been defined, it must be registered so
# that ``matplotlib`` can find it.
mscale.register_scale(SegmentedScale)


if __name__ == '__main__':

    u=  u"""0, 137.13, 0.082
        0.1, 112.46, 0.175
        0.2, 98.23, 0.368
        0.5, 72.38, 0.838
        1, 60.69, 8.932
        10, 54.21, 17.602
        20, 47.71, 48.355
        50, 46.14, 89.358
        100, 41.23, 241.147
        250, 39.77, 0"""

    import io
    import matplotlib.pyplot as plt

    q,r,breakpoints = np.loadtxt(io.StringIO(u), delimiter=", ", unpack=True)

    c = lambda m,x : m*x

    for i in range(0,9):
        w = np.arange(0., q[9], 0.01)
        plt.plot(w,c(r[i],w),'b--',linewidth=0.3)
        plt.plot( [q[i],breakpoints[i]] , [c(r[i], q[i]), c(r[i], breakpoints[i])], 'r')
        plt.plot(q[i + 1], c(r[i], breakpoints[i]), 'r.')

    plt.gca().set_xscale('segmented', points = q)
    plt.show()

enter image description here

Apart from the kinks in the lines, which might not be desired, but are a necessary consequence from the kind of scale used here, the values on the y axis are still quite unreadable.

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712