matplotlib: formater les valeurs de décalage de l'axe en nombres entiers ou en nombre spécifique

92

J'ai une figure matplotlib que je trace des données qui sont toujours appelées nanosecondes (1e-9). Sur l'axe des y, si j'ai des données qui sont des dizaines de nanosecondes, c'est à dire. 44e-9, la valeur sur l'axe indique 4,4 avec un + 1e-8 comme décalage. Est-il possible de forcer l'axe à afficher 44 avec un décalage de + 1e-9?

Il en va de même pour mon axe des x où l'axe montre + 5,54478e4, où je préférerais qu'il affiche un décalage de +55447 (nombre entier, pas de décimale - la valeur ici est en jours).

J'ai essayé quelques trucs comme ça:

p = axes.plot(x,y)
p.ticklabel_format(style='plain')

pour l'axe des x, mais cela ne fonctionne pas, bien que je l'utilise probablement de manière incorrecte ou que j'interprète mal quelque chose de la documentation, quelqu'un peut-il me diriger dans la bonne direction?

Merci, Jonathan

Illustration du problème


J'ai essayé de faire quelque chose avec les formateurs mais je n'ai pas encore trouvé de solution ...:

myyfmt = ScalarFormatter(useOffset=True)
myyfmt._set_offset(1e9)
axes.get_yaxis().set_major_formatter(myyfmt)

et

myxfmt = ScalarFormatter(useOffset=True)
myxfmt.set_portlimits((-9,5))
axes.get_xaxis().set_major_formatter(myxfmt)

Sur une note latérale, je suis en fait confus quant à l'endroit où l'objet 'offset number' réside réellement ... fait-il partie des graduations majeures / mineures?

Jonathan
la source
1
Avez-vous essayé set_units? matplotlib.sourceforge.net/api/… (Je ne peux pas l'essayer car je n'ai pas matplotlib ici.)
Katriel
1
J'ai vérifié la fonction set_units et cela semble bien plus compliqué que nécessaire (devez écrire / ajouter un module supplémentaire ?? - basic_units?). Il doit y avoir un moyen de simplement modifier le format de la coche. La fonction units / set_unit semble plus proche de la conversion d'unité. Merci pour le conseil, cela m'a conduit à d'autres solutions que je recherche maintenant!
Jonathan le
1
s'il vous plaît envisager rcParamsde désactiver si désactivé par défaut: rcParams["axes.formatter.useoffset"] = Falsecomme ici: stackoverflow.com/questions/24171064/…
Ruggero Turra

Réponses:

100

J'ai eu exactement le même problème, et ces lignes ont résolu le problème:

from matplotlib.ticker import ScalarFormatter

y_formatter = ScalarFormatter(useOffset=False)
ax.yaxis.set_major_formatter(y_formatter)
Gonzalo
la source
1
C'est la réponse rapide et facile. Je vous remercie.
maxm
6
Un one-liner serait:ax.get_yaxis().get_major_formatter().set_useOffset(False)
Dataman
3
pour les noobs comme moi, n'oubliez pas que le from matplotlib.ticker import ScalarFormattercode de @Gonzalo fonctionne ou utilisez simplement la solution de
@Dataman
36

Une solution beaucoup plus simple consiste simplement à personnaliser les étiquettes de graduation. Prenons cet exemple:

from pylab import *

# Generate some random data...
x = linspace(55478, 55486, 100)
y = random(100) - 0.5
y = cumsum(y)
y -= y.min()
y *= 1e-8

# plot
plot(x,y)

# xticks
locs,labels = xticks()
xticks(locs, map(lambda x: "%g" % x, locs))

# ytikcs
locs,labels = yticks()
yticks(locs, map(lambda x: "%.1f" % x, locs*1e9))
ylabel('microseconds (1E-9)')

show()

texte alternatif

Remarquez comment dans le cas de l'axe y, j'ai multiplié les valeurs par 1e9puis mentionné cette constante dans l'étiquette y


ÉDITER

Une autre option consiste à simuler le multiplicateur d'exposant en ajoutant manuellement son texte en haut du graphique:

locs,labels = yticks()
yticks(locs, map(lambda x: "%.1f" % x, locs*1e9))
text(0.0, 1.01, '1e-9', fontsize=10, transform = gca().transAxes)

EDIT2

Vous pouvez également formater la valeur de décalage de l'axe x de la même manière:

locs,labels = xticks()
xticks(locs, map(lambda x: "%g" % x, locs-min(locs)))
text(0.92, -0.07, "+%g" % min(locs), fontsize=10, transform = gca().transAxes)

texte alternatif

Amro
la source
C'est exactement ce que j'ai fait, au début. Malheureusement, je n'ai pas trouvé de moyen facile de définir / afficher le multiplicateur d'axe (autre que de le mettre explicitement dans l'étiquette de l'axe y, comme vous l'avez fait.). Si cela ne vous dérange pas de ne pas avoir l'étiquette de multiplicateur d'axe, c'est la manière la plus simple. De toute façon, +1 de moi.
Joe Kington
1
@Joe Kington: vous pouvez l'ajouter manuellement sous forme de texte ... voir la modification ci-dessus :)
Amro
Génial! Je vais essayer votre approche avec les étiquettes de l'axe des x. Je vais prendre la parole de la première valeur x, puis la supprimer de chaque valeur x et ajouter un "+ minxval" comme étiquette. Je ne peux pas comprendre comment formater le décalage x-tick. Je suis d'accord avec l'ampleur du décalage, il me faut juste qu'il s'affiche comme une valeur non exponentielle.
Jonathan
Sensationnel. Excellent travail pour montrer comment vous pouvez vraiment prendre le contrôle de matplotlib et l'ajuster à vos besoins et vraiment et un peu de piquant dans vos intrigues.
physicsmichael
Comment changez-vous la taille de police de 1e-9 dans la figure?
une offre ne peut pas refuser
30

Vous devez sous-classer ScalarFormatterpour faire ce dont vous avez besoin ... _set_offsetajoute simplement une constante que vous voulez définir ScalarFormatter.orderOfMagnitude. Malheureusement, la configuration manuelle orderOfMagnitudene fera rien, car elle est réinitialisée lorsque l' ScalarFormatterinstance est appelée pour formater les étiquettes de graduation des axes. Cela ne devrait pas être si compliqué, mais je ne trouve pas de moyen plus simple de faire exactement ce que vous voulez ... Voici un exemple:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import ScalarFormatter, FormatStrFormatter

class FixedOrderFormatter(ScalarFormatter):
    """Formats axis ticks using scientific notation with a constant order of 
    magnitude"""
    def __init__(self, order_of_mag=0, useOffset=True, useMathText=False):
        self._order_of_mag = order_of_mag
        ScalarFormatter.__init__(self, useOffset=useOffset, 
                                 useMathText=useMathText)
    def _set_orderOfMagnitude(self, range):
        """Over-riding this to avoid having orderOfMagnitude reset elsewhere"""
        self.orderOfMagnitude = self._order_of_mag

# Generate some random data...
x = np.linspace(55478, 55486, 100) 
y = np.random.random(100) - 0.5
y = np.cumsum(y)
y -= y.min()
y *= 1e-8

# Plot the data...
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, y, 'b-')

# Force the y-axis ticks to use 1e-9 as a base exponent 
ax.yaxis.set_major_formatter(FixedOrderFormatter(-9))

# Make the x-axis ticks formatted to 0 decimal places
ax.xaxis.set_major_formatter(FormatStrFormatter('%0.0f'))
plt.show()

Ce qui donne quelque chose comme: texte alternatif

Alors que la mise en forme par défaut ressemblerait à: texte alternatif

J'espère que ça aidera un peu!

Edit: Pour ce que ça vaut, je ne sais pas non plus où se trouve l'étiquette offset ... Ce serait un peu plus facile de la définir manuellement, mais je ne savais pas comment le faire ... J'ai le sentiment qu'il doit y avoir un moyen plus simple que tout cela. Cela fonctionne, cependant!

Joe Kington
la source
Merci! Sous-classer le ScalarFormatter fonctionne très bien! Mais je suppose que je n'ai pas clairement indiqué ce que je voulais pour l'axe des x. Je voudrais conserver le décalage pour l'axe des x, mais formatez la valeur du décalage afin qu'il ne soit pas affiché comme un exposant.
Jonathan
C'est la seule méthode qui a fonctionné pour moi! Merci :)
Ocean Scientist
11

Similaire à la réponse d'Amro, vous pouvez utiliser FuncFormatter

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

# Generate some random data...
x = np.linspace(55478, 55486, 100) 
y = np.random.random(100) - 0.5
y = np.cumsum(y)
y -= y.min()
y *= 1e-8

# Plot the data...
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, y, 'b-')

# Force the y-axis ticks to use 1e-9 as a base exponent 
ax.yaxis.set_major_formatter(FuncFormatter(lambda x, pos: ('%.1f')%(x*1e9)))
ax.set_ylabel('microseconds (1E-9)')

# Make the x-axis ticks formatted to 0 decimal places
ax.xaxis.set_major_formatter(FuncFormatter(lambda x, pos: '%.0f'%x))
plt.show()
idrinkpabst
la source
5

La solution de Gonzalo a commencé à fonctionner pour moi après avoir ajouté set_scientific(False):

ax=gca()
fmt=matplotlib.ticker.ScalarFormatter(useOffset=False)
fmt.set_scientific(False)
ax.xaxis.set_major_formatter(fmt)
ha7ilm
la source
5

Comme cela a été souligné dans les commentaires et dans cette réponse , le décalage peut être désactivé globalement, en procédant comme suit:

matplotlib.rcParams['axes.formatter.useoffset'] = False
Evgeni Sergeev
la source
4

Je pense qu'une manière plus élégante est d'utiliser le formateur de ticker. Voici un exemple pour xaxis et yaxis:

from pylab import *
from matplotlib.ticker import MultipleLocator, FormatStrFormatter

majorLocator   = MultipleLocator(20)
xFormatter = FormatStrFormatter('%d')
yFormatter = FormatStrFormatter('%.2f')
minorLocator   = MultipleLocator(5)


t = arange(0.0, 100.0, 0.1)
s = sin(0.1*pi*t)*exp(-t*0.01)

ax = subplot(111)
plot(t,s)

ax.xaxis.set_major_locator(majorLocator)
ax.xaxis.set_major_formatter(xFormatter)
ax.yaxis.set_major_formatter(yFormatter)

#for the minor ticks, use no labels; default NullFormatter
ax.xaxis.set_minor_locator(minorLocator)
Bogdan
la source
1
Cela ne répond pas à la question, qui est de savoir comment spécifier le décalage et / ou le facteur utilisé en notation scientifique .
sodd
@nordev Même si ma réponse ne répond pas spécifiquement à la question, elle donne quand même un indice. Le message est que vous pouvez choisir un autre formateur et obtenir ce que vous voulez au lieu d'une date de mon exemple. Dans le monde scientifique, le jour julien est la norme, ou vous pouvez utiliser la date comme dans mon exemple. Ce que j'essayais de suggérer, c'est qu'une approche différente peut être adoptée. Parfois, une question peut être posée parce que la personne n'a pas une meilleure idée pour le moment. Les solutions alternatives ne doivent pas être jetées ou traitées avec manque de respect. Dans l'ensemble, je ne méritais pas le vote -1.
Bogdan
2

Pour la deuxième partie, sans réinitialiser manuellement tous les ticks, c'était ma solution:

class CustomScalarFormatter(ScalarFormatter):
    def format_data(self, value):
        if self._useLocale:
            s = locale.format_string('%1.2g', (value,))
        else:
            s = '%1.2g' % value
        s = self._formatSciNotation(s)
        return self.fix_minus(s)
xmajorformatter = CustomScalarFormatter()  # default useOffset=True
axes.get_xaxis().set_major_formatter(xmajorformatter)

évidemment, vous pouvez définir la chaîne de format sur ce que vous voulez.

astrojuanlu
la source
Malheureusement, je n'ai pas étudié comment définir le multiplicateur dans la première partie de votre question.
astrojuanlu