2013-12-09 10 views
7

Uso la scala symlog di matplotlib per coprire un ampio intervallo di parametri che si estendono sia in direzione positiva che negativa. Sfortunatamente, la scala symlog non è molto intuitiva e probabilmente anche non molto usata. Pertanto, mi piacerebbe rendere più evidente lo scaling utilizzato posizionando tick minori tra i tick principali. Sulla parte di registro della scala, voglio posizionare le zecche a [2,3, ..., 9] * 10^e dove e è il segno di spunta maggiore vicino. Inoltre, l'intervallo tra 0 e 0,1 dovrebbe essere coperto con zecche minori posizionate in modo uniforme, che dovrebbero essere a 0,01. Ho provato ad utilizzare il matplotlib.ticker API per arrivare a tali zecche utilizzando il seguente codice:Come posizionare tick secondari sulla scala di symlog?

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() 

Purtroppo, questo non produce quello che voglio:

enter image description here

Nota che ci sono modo per molte zecche minori intorno a 0, che sono probabilmente dovuti allo LogLocator. Inoltre, non ci sono segni secondari secondari sull'asse negativo.

Nessun segno di spunta minore viene visualizzato se utilizzo e AutoLocator. Lo AutoMinorLocator supporta solo assi in scala uniforme. La mia domanda quindi è come ottenere il piazzamento desiderato?

risposta

8

Scavando un po 'più in profondità nel problema, ho notato che sarà difficile trovare una soluzione generale. Per fortuna, posso presumo alcuni vincoli sui miei dati e una classe su misura avuto pertanto quello di risolvere il problema:

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() 

Questo produce

enter image description here

Nota che questo metodo solo luoghi zecche tra maggiori zecche. Questo diventerà evidente se si esegue lo zoom e la panoramica dell'immagine. Inoltre, la soglia lineare deve essere fornita esplicitamente alla classe, dal momento che non ho trovato modo di leggerlo facilmente e in modo affidabile dall'asse stesso.

+0

Potrebbe aprire un PR in upstream per aggiungere questo al codebase mpl? – tacaswell

0

La soluzione degli OP funziona bene, ma i segni di graduazione non vengono prodotti ai bordi degli assi se non sono multipli dei valori di soglia lineare. Ho messo i PO MinorSymLogLocator() di classe per dare il seguente (che riempie i bordi con l'aggiunta di temporanee sedi principali di graduazione quando si imposta il segno di spunta locatoin minore):

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

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

     if len(majorlocs) == 1: 
      return self.raise_if_exceeds(np.array([])) 

     # add temporary major tick locs at either end of the current range 
     # to fill in minor tick gaps 
     dmlower = majorlocs[1] - majorlocs[0] # major tick difference at lower end 
     dmupper = majorlocs[-1] - majorlocs[-2] # major tick difference at upper end 

     # add temporary major tick location at the upper end 
     if majorlocs[0] != 0. and ((majorlocs[0] != self.linthresh and dmlower > self.linthresh) or (dmlower == self.linthresh and majorlocs[0] < 0)): 
      majorlocs = np.insert(majorlocs, 0, majorlocs[0]*10.) 
     else: 
      majorlocs = np.insert(majorlocs, 0, majorlocs[0]-self.linthresh) 

     # add temporary major tick location at the upper end 
     if majorlocs[-1] != 0. and ((np.abs(majorlocs[-1]) != self.linthresh and dmupper > self.linthresh) or (dmupper == self.linthresh and majorlocs[-1] > 0)): 
      majorlocs = np.append(majorlocs, majorlocs[-1]*10.) 
     else: 
      majorlocs = np.append(majorlocs, majorlocs[-1]+self.linthresh) 

     # 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 = self.nintervals 
      else: 
       ndivs = self.nintervals - 1. 

      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))