Pour le moment, si je mets les ticklabels de l'axe y de matplotlib en mode scientifique, cela me donne un exposant en haut de l'axe y de la forme 1e-5

Je voudrais ajuster ceci pour lire r'$\mathregular{10^{-5}}$' afin qu'il s'imprime bien.

Voici mon exemple de code:

# Create a figure and axis
fig, ax = plt.subplots()

# Plot 100 random points 
# the y values of which are very small
ax.scatter(np.random.rand(100), np.random.rand(100)/100000.0)

# Set the y limits appropriately
ax.set_ylim(0, 1/100000.0)

# Change the y ticklabel format to scientific format
ax.ticklabel_format(axis='y', style='sci', scilimits=(-2, 2))

# Get the offset value
offset = ax.yaxis.get_offset_text()

# Print it out
print '1st offset printout: {}'.format(offset)

# Run plt.tight_layout()
plt.tight_layout()

# Print out offset again - you can see the value now!
print '2nd offset printout: {}'.format(offset)

# Change it to latex format
offset.set_text(r'$\mathregular{10^{-5}}$')

# Print it out
print '3rd offset printout: {}'.format(offset)

# Add some text to the middle of the figure just to 
# check that it isn't the latex format that's the problem
ax.text(0.5, 0.5/100000.0, r'$\mathregular{10^{-2}}$')

# And show the figure
plt.show()

Ma sortie ressemble à ceci:

1st offset printout: Text(0,0.5,u'')
2nd offset printout: Text(0,636.933,u'1e\u22125')
3rd offset printout: Text(0,636.933,u'$\\mathregular{10^{-5}}$')

enter image description here

Vous pouvez trouver le code et la figure de sortie ici.

Il y a deux bizarreries: l'une est que je ne peux pas écraser le 1e-5 en haut de l'axe y (qui est l'objectif), et l'autre est que je dois exécuter plt.tight_layout() pour voir même cette valeur unicode comme décalage.

Quelqu'un peut-il me dire où je me trompe?

Je vous remercie

EDIT: La question d'origine ne précisait pas que j'aimerais détecter automatiquement l'exposant tel qu'il est actuellement calculé par ticklabel_format. Ainsi, au lieu de passer une chaîne définie au texte décalé, il devrait détecter automatiquement cette valeur et ajuster la chaîne en latex en conséquence.

9
KirstieJane 20 juil. 2015 à 15:56

4 réponses

Meilleure réponse

En s'appuyant sur la réponse de @ edsmith, une solution possible consiste à obtenir le texte de décalage, à le convertir en chaîne de latex, à désactiver le décalage et à ajouter cette chaîne en haut de l'axe.

def format_exponent(ax, axis='y'):

    # Change the ticklabel format to scientific format
    ax.ticklabel_format(axis=axis, style='sci', scilimits=(-2, 2))

    # Get the appropriate axis
    if axis == 'y':
        ax_axis = ax.yaxis
        x_pos = 0.0
        y_pos = 1.0
        horizontalalignment='left'
        verticalalignment='bottom'
    else:
        ax_axis = ax.xaxis
        x_pos = 1.0
        y_pos = -0.05
        horizontalalignment='right'
        verticalalignment='top'

    # Run plt.tight_layout() because otherwise the offset text doesn't update
    plt.tight_layout()
    ##### THIS IS A BUG 
    ##### Well, at least it's sub-optimal because you might not
    ##### want to use tight_layout(). If anyone has a better way of 
    ##### ensuring the offset text is updated appropriately
    ##### please comment!

    # Get the offset value
    offset = ax_axis.get_offset_text().get_text()

    if len(offset) > 0:
        # Get that exponent value and change it into latex format
        minus_sign = u'\u2212'
        expo = np.float(offset.replace(minus_sign, '-').split('e')[-1])
        offset_text = r'x$\mathregular{10^{%d}}$' %expo

        # Turn off the offset text that's calculated automatically
        ax_axis.offsetText.set_visible(False)

        # Add in a text box at the top of the y axis
        ax.text(x_pos, y_pos, offset_text, transform=ax.transAxes,
               horizontalalignment=horizontalalignment,
               verticalalignment=verticalalignment)
    return ax

Notez que vous devriez pouvoir utiliser la position du texte décalé en appelant pos = ax_axis.get_offset_text().get_position() mais ces valeurs ne sont pas en unités d'axe (ce sont probablement des unités de pixels - merci @EdSmith - et donc pas très utiles). Par conséquent, je viens de définir les valeurs x_pos et y_pos en fonction de l'axe que nous regardons.

J'ai également écrit une petite fonction pour détecter automatiquement les limites x et y appropriées (même si je sais que matplotlib a beaucoup de façons sophistiquées de le faire).

def get_min_max(x, pad=0.05):
    '''
    Find min and max values such that
    all the data lies within 90% of
    of the axis range
    '''
    r = np.max(x) - np.min(x)
    x_min = np.min(x) - pad * r
    x_max = np.max(x) + pad * r
    return x_min, x_max

Donc, pour mettre à jour mon exemple à partir de la question (avec un léger changement pour que les deux axes aient besoin de l'exposant):

import matplotlib.pylab as plt
import numpy as np

# Create a figure and axis
fig, ax = plt.subplots()

# Plot 100 random points that are very small
x = np.random.rand(100)/100000.0
y = np.random.rand(100)/100000.0
ax.scatter(x, y)

# Set the x and y limits
x_min, x_max = get_min_max(x)
ax.set_xlim(x_min, x_max)
y_min, y_max = get_min_max(y)    
ax.set_ylim(y_min, y_max)

# Format the exponents nicely
ax = format_exponent(ax, axis='x')
ax = format_exponent(ax, axis='y')

# And show the figure
plt.show()

enter image description here

Un résumé avec un bloc-notes ipython montrant la sortie du code est disponible ici.

J'espère que ça aide!

3
KirstieJane 23 juil. 2015 à 10:52

Ajoutez deux lignes dans votre code

import matplotlib.ticker as ptick
ax.yaxis.set_major_formatter(ptick.ScalarFormatter(useMathText=True)) 
0
Hideki Fujioka 22 déc. 2015 à 20:10

Vous obtenez offset et définissez la valeur du texte, mais il ne semble pas y avoir de moyen d'appliquer cela à l'axe ... Même appeler ax.yaxis.offsetText.set_text(offset) ne met pas à jour le décalage affiché. Un travail autour de lui pour supprimer le texte décalé et le remplacer par des crochets sur l'étiquette de l'axe,

ax.yaxis.offsetText.set_visible(False)
ax.set_ylabel("datalabel " +  r'$\left(\mathregular{10^{-5}}\right)$')

Ou remplacez-le par une zone de texte manuelle, comme exemple minimal,

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np

# Create a figure and axis
fig, ax = plt.subplots()
mpl.rc('text', usetex = True)

# Plot 100 random points 
# the y values of which are very small
large = 100000.0
x = np.random.rand(100)
y = np.random.rand(100)/large

ax.scatter(x,y)

# Set the y limits appropriately
ax.set_ylim(0, 1/large)

# Change the y ticklabel format to scientific format
ax.ticklabel_format(axis='y', style='sci', scilimits=(-2, 2))

#print(ax.yaxis.offsetText.get_position())
ax.yaxis.offsetText.set_visible(False)
ax.text(-0.21, 1.01/large, r'$\mathregular{10^{-2}}$')

# And show the figure
plt.show()

Je sais que ce n'est pas idéal, mais il se peut que le texte décalé ne puisse pas être modifié manuellement ou ne puisse être que cohérent avec les valeurs numériques ...

2
Ed Smith 20 juil. 2015 à 14:30

Il semble que plt.ticklabel_format ne fonctionne pas correctement. Cependant, si vous définissez ScalarFormatter vous-même et définissez les limites de la notation scientifique au formateur, vous pouvez obtenir le décalage automatiquement au format mathtext comme ceci:

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

x = np.linspace(3,5)
y = np.sin(np.linspace(0,6*np.pi))*1e5

plt.plot(x,y)

mf = matplotlib.ticker.ScalarFormatter(useMathText=True)
mf.set_powerlimits((-2,2))
plt.gca().yaxis.set_major_formatter(mf)

plt.show()

enter image description here

3
ImportanceOfBeingErnest 4 avril 2017 à 20:11