Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to place minor ticks on symlog scale?

I use the symlog scale of matplotlib to cover a large ranges of parameters extending both into positive and negative direction. Unfortunately, the symlog-scale is not very intuitive and probably also not very commonly used. Therefore, I'd like to make the used scaling more obvious by placing minor ticks between the major ticks. On the log part of the scale, I want to place ticks at [2,3,…,9]*10^e where e is the nearby major tick. Additionally, the range between 0 and 0.1 should be covered with evenly placed minor ticks, which would be 0.01 apart. I tried using the matplotlib.ticker API to arrive at such ticks using the following code:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import LogLocator, AutoLocator

x = np.linspace(-5, 5, 100)
y = x

plt.plot(x, y)
plt.yscale('symlog', linthreshy=1e-1)

yaxis = plt.gca().yaxis
yaxis.set_minor_locator(LogLocator(subs=np.arange(2, 10)))

plt.show()

Unfortunately, this does not produce what I want:

enter image description here

Note that there are way to many minor ticks around 0, which are probably due to the LogLocator. Furthermore, there are no minor ticks on the negative axis.

No minor ticks appear if I use and AutoLocator instead. The AutoMinorLocator does only support evenly scaled axes. My question thus is how to achieve the desired tick placement?

like image 506
David Zwicker Avatar asked Dec 09 '13 12:12

David Zwicker


1 Answers

Digging a little bit deeper into the issue, I noticed that it will be hard to come up with a general solution. Luckily, I can assume some constraints on my data and a custom made class was therefore enough to solve the problem:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import Locator


class MinorSymLogLocator(Locator):
    """
    Dynamically find minor tick positions based on the positions of
    major ticks for a symlog scaling.
    """
    def __init__(self, linthresh):
        """
        Ticks will be placed between the major ticks.
        The placement is linear for x between -linthresh and linthresh,
        otherwise its logarithmically
        """
        self.linthresh = linthresh

    def __call__(self):
        'Return the locations of the ticks'
        majorlocs = self.axis.get_majorticklocs()

        # iterate through minor locs
        minorlocs = []

        # handle the lowest part
        for i in xrange(1, len(majorlocs)):
            majorstep = majorlocs[i] - majorlocs[i-1]
            if abs(majorlocs[i-1] + majorstep/2) < self.linthresh:
                ndivs = 10
            else:
                ndivs = 9
            minorstep = majorstep / ndivs
            locs = np.arange(majorlocs[i-1], majorlocs[i], minorstep)[1:]
            minorlocs.extend(locs)

        return self.raise_if_exceeds(np.array(minorlocs))

    def tick_values(self, vmin, vmax):
        raise NotImplementedError('Cannot get tick locations for a '
                                  '%s type.' % type(self))


x = np.linspace(-5, 5, 100)
y = x

plt.plot(x, y)
plt.yscale('symlog', linthreshy=1e-1)

yaxis = plt.gca().yaxis
yaxis.set_minor_locator(MinorSymLogLocator(1e-1))

plt.show()

This produces

enter image description here

Note that this method only places ticks between major ticks. This will become noticeable if you zoom and pan into the image. Furthermore, the linear threshold has to be supplied to the class explicitly, since I found no way to easily and robustly read it off the axis itself.

like image 184
David Zwicker Avatar answered Sep 21 '22 20:09

David Zwicker