Je génère des histogrammes avec matplotlib et j'ai du mal à comprendre comment aligner les xticks d'un histogramme sur les barres.
Voici un exemple du code que j'utilise pour générer l'histogramme:
from matplotlib import pyplot as py
py.hist(histogram_data, 49, alpha=0.75)
py.title(column_name)
py.xticks(range(49))
py.show()
Je sais que toutes les valeurs du tableau histogram_data
sont dans [0,1,...,48]
. Ce qui, si j’ai bien fait les calculs, signifie qu’il existe 49 valeurs uniques. Je voudrais montrer un histogramme de chacune de ces valeurs. Voici une image de ce qui est généré.
Comment puis-je configurer le graphique de telle sorte que tous les xticks soient alignés à gauche, au milieu ou à droite de chacune des barres?
Réponse courte: Utilisez plt.hist(data, bins=range(50))
à la place pour obtenir des bacs alignés à gauche, plt.hist(data, bins=np.arange(50)-0.5)
pour obtenir des bacs alignés au centre, etc.
De plus, si les performances ont une importance, parce que vous voulez compter les entiers uniques, il existe quelques méthodes légèrement plus efficaces (np.bincount
) que je montrerai à la fin.
En tant qu'exemple autonome de ce que vous voyez, prenez en compte les éléments suivants:
import matplotlib.pyplot as plt
import numpy as np
# Generate a random array of integers between 0-9
# data.min() will be 0 and data.max() will be 9 (not 10)
data = np.random.randint(0, 10, 1000)
plt.hist(data, bins=10)
plt.xticks(range(10))
plt.show()
Comme vous l'avez remarqué, les bacs ne sont pas alignés sur des intervalles entiers. Cela est dû au fait que vous avez demandé 10 bacs entre 0 et 9, ce qui n’est pas la même chose que de demander des bacs pour les 10 valeurs uniques.
Le nombre de bacs que vous voulez n'est pas exactement le même que le nombre de valeurs uniques. Ce que vous devez réellement faire dans ce cas est de spécifier manuellement les bords de la corbeille.
Pour expliquer ce qui se passe, ignorons matplotlib.pyplot.hist
et utilisons simplement la fonction numpy.histogram
sous-jacente.
Par exemple, supposons que vous ayez les valeurs [0, 1, 2, 3]
. Votre premier instinct serait de faire:
In [1]: import numpy as np
In [2]: np.histogram([0, 1, 2, 3], bins=4)
Out[2]: (array([1, 1, 1, 1]), array([ 0. , 0.75, 1.5 , 2.25, 3. ]))
Le premier tableau renvoyé est le nombre et le second les bords de la corbeille (autrement dit, où les bords de la barre seraient dans votre graphique).
Notez que nous obtenons les comptes que nous attendions, mais comme nous avons demandé 4 intervalles entre les valeurs min et max des données, les bords des emplacements ne sont pas sur des valeurs entières.
Ensuite, vous pouvez essayer:
In [3]: np.histogram([0, 1, 2, 3], bins=3)
Out[3]: (array([1, 1, 2]), array([ 0., 1., 2., 3.]))
Notez que les bords de la corbeille (le second tableau) correspondent à ce que vous attendiez, mais que les décomptes ne le sont pas. En effet, le dernier bac se comporte différemment des autres, comme indiqué dans la documentation de numpy.histogram
:
Notes
-----
All but the last (righthand-most) bin is half-open. In other words, if
`bins` is::
[1, 2, 3, 4]
then the first bin is ``[1, 2)`` (including 1, but excluding 2) and the
second ``[2, 3)``. The last bin, however, is ``[3, 4]``, which *includes*
4.
Par conséquent, vous devez spécifier les bords de bac souhaités et en inclure un au-delà de votre dernier point de données ou déplacer les bords de bac aux intervalles 0.5
. Par exemple:
In [4]: np.histogram([0, 1, 2, 3], bins=range(5))
Out[4]: (array([1, 1, 1, 1]), array([0, 1, 2, 3, 4]))
Appliquons maintenant ceci au premier exemple et voyons à quoi il ressemble:
import matplotlib.pyplot as plt
import numpy as np
# Generate a random array of integers between 0-9
# data.min() will be 0 and data.max() will be 9 (not 10)
data = np.random.randint(0, 10, 1000)
plt.hist(data, bins=range(11)) # <- The only difference
plt.xticks(range(10))
plt.show()
D'accord! Super! Cependant, nous avons maintenant effectivement des bacs alignés à gauche. Et si nous voulions des bacs alignés au centre pour mieux refléter le fait que ce sont des valeurs uniques?
La solution rapide consiste simplement à déplacer les bords de la corbeille:
import matplotlib.pyplot as plt
import numpy as np
# Generate a random array of integers between 0-9
# data.min() will be 0 and data.max() will be 9 (not 10)
data = np.random.randint(0, 10, 1000)
bins = np.arange(11) - 0.5
plt.hist(data, bins)
plt.xticks(range(10))
plt.xlim([-1, 10])
plt.show()
De même pour les bacs alignés à droite, il suffit de décaler de -1
.
Dans le cas particulier de valeurs entières uniques, il existe une autre approche, plus efficace, que nous pouvons adopter.
Si vous avez affaire à des nombres entiers uniques commençant par 0, mieux vaut utiliser numpy.bincount
plutôt que numpy.hist
.
Par exemple:
import matplotlib.pyplot as plt
import numpy as np
data = np.random.randint(0, 10, 1000)
counts = np.bincount(data)
# Switching to the OO-interface. You can do all of this with "plt" as well.
fig, ax = plt.subplots()
ax.bar(range(10), counts, width=1, align='center')
ax.set(xticks=range(10), xlim=[-1, 10])
plt.show()
Cette approche présente deux grands avantages. L'un est la vitesse. numpy.histogram
(et donc plt.hist
) exécute les données via numpy.digitize
et ensuite numpy.bincount
. Comme vous avez affaire à des valeurs entières uniques, il n’est pas nécessaire de passer à l’étape numpy.digitize
.
Cependant, le plus gros avantage réside dans un meilleur contrôle de l'affichage. Si vous préférez des rectangles plus minces, utilisez une largeur plus petite:
import matplotlib.pyplot as plt
import numpy as np
data = np.random.randint(0, 10, 1000)
counts = np.bincount(data)
# Switching to the OO-interface. You can do all of this with "plt" as well.
fig, ax = plt.subplots()
ax.bar(range(10), counts, width=0.8, align='center')
ax.set(xticks=range(10), xlim=[-1, 10])
plt.show()
L'utilisation de l'interface OO pour configurer les ticks présente l'avantage de centrer les étiquettes tout en préservant les xticks. En outre, cela fonctionne avec n'importe quelle fonction de traçage et ne dépend pas de np.bincount()
ou ax.bar()
import matplotlib.ticker as tkr
data = np.random.randint(0, 10, 1000)
mybins = range(11)
fig, ax = subplots()
ax.hist(data, bins=mybins, rwidth=0.8)
ax.set_xticks(mybins)
ax.xaxis.set_minor_locator(tkr.AutoMinorLocator(n=2))
ax.xaxis.set_minor_formatter(tkr.FixedFormatter(mybins))
ax.xaxis.set_major_formatter(tkr.NullFormatter())
for tick in ax.xaxis.get_minor_ticks():
tick.tick1line.set_markersize(0)