Comment définir l'échelle de graduation comme puissance de 2 dans matplotlib? [dupliquer]

Dec 16 2020

Je veux tracer une figure dont les valeurs xtick augmentent à la puissance de 2.

Par exemple,

import pandas as pd
data = pd.DataFrame({
    'x': [2, 4, 8, 16, 32, 64],
    'y': [1, 2, 3, 4, 5, 6]
})

Ce que j'attends, c'est un chiffre comme celui-ci,

Pour des données ordonnées comme l'exemple ci-dessus, je peux simplement créer une colonne auxiliaire x2de 1 ~ 6 dans ce dataframe, puis définir les xticklabels de la figure sur 2 ^ 1 ~ 2 ^ 6. Cependant, cette solution de contournement ne s'applique pas aux situations avec d'autres valeurs telles que 3, 7 ou 30.

Il semble que matplotlib ne supporte qu'une échelle logarithmique. Comment puis-je obtenir une graduation avec une échelle de puissance de 2?

Réponses

max Dec 16 2020 at 17:20

C'est une question particulièrement piège (que je ne m'attendais pas à être ^^).

OK, commençons par quelques conseils de lecture: Vous voulez définir l'échelle x / y: .matplotlib.axes.Axes.set_yscale () . Bien qu'il existe quelques échelles standard (la valeur par défaut est évidemment 'liner', on peut définir une échelle personnalisée. Voici quelques exemples intéressants.

En gros, vous définissez deux fonctions avec la transformation avant et avec l' inverse de celle-ci. Ensuite, vous devez définir correctement les graduations (parce que vous appliquez la transformation après le tracé, les graduations restent les mêmes (mais pas à la même position en raison de la transformation). On a deux options pour cela:

  • définir les graduations manuellement matplotlib.axes.Axes.set_xticks(), ou
  • en réglant le positionnement de l'axe: matplotlib.axes.Axes.xaxis.set_major_locator(). Ceci est recommandé si vous utilisez des grilles. Mais comme mes connaissances sont limitées, j'apprécie une explication plus détaillée (car maintenant je suis aussi curieuse de cette fonctionnalité ^^)

Et maintenant vient la partie délicate: formater les étiquettes de graduation pour représenter un fichier '2^x'. Je n'ai pas trouvé de meilleure idée que de les définir explicitement comme des chaînes. Il semble que l'on ne puisse changer le format général que dans des limites restreintes, voir matplotlib.pyplot.ticklabel_format(), où l'on peut choisir si et quand une notation scientifique doit être utilisée (c'est-à-dire en affichant un '10^x'en bas à droite). Faites-moi savoir s'il existe une solution plus générique pour cela.

import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.ticker import FixedLocator

# create dummy data
df = pd.DataFrame({
    'x': [2**x for x in range(1,10)],
    'y': list(range(1,10))
})

def forward(x):
    return np.log2(x)


def inverse(x):
    return 2**x

# open figure
fig, ax = plt.subplots(2,2)
axs = ax.flatten()
for i in range(0,4):
    # plot data
    axs[i].plot(df['x'],df['y'])
    if i > 0:
        # set scale function
        axs[i].set_xscale('function', functions=(forward,inverse))
    if i > 1:
        # set ticks
        # - OPTION 1
        axs[i].set_xticks(df['x'])
        # - OPTION 2
      axs[i].xaxis.set_major_locator(FixedLocator(2**np.arange(1,10)))
    if i > 2:
        # est tick labels
        axs[i].set_xticklabels( [f"2^{j:.0f}" for j in np.log2(df['x'])] )

plt.show()