web-dev-qa-db-fra.com

Histogramme Matplotlib avec bac de collecte pour les valeurs élevées

J'ai un tableau avec des valeurs et je veux en créer un histogramme. Je suis principalement intéressé par les numéros bas de gamme et je souhaite collecter chaque numéro supérieur à 300 dans un seul bac. Ce bac doit avoir la même largeur que tous les autres bacs (également larges). Comment puis-je faire ceci?

Remarque: cette question est liée à cette question: Définition de la largeur du bac/échelle de l'axe des x dans l'histogramme Matplotlib

Voici ce que j'ai essayé jusqu'à présent:

import matplotlib.pyplot as plt
import numpy as np

def plot_histogram_01():
    np.random.seed(1)
    values_A = np.random.choice(np.arange(600), size=200, replace=True).tolist()
    values_B = np.random.choice(np.arange(600), size=200, replace=True).tolist()

    bins = [0, 25, 50, 75, 100, 125, 150, 175, 200, 225, 250, 275, 300, 600]

    fig, ax = plt.subplots(figsize=(9, 5))
    _, bins, patches = plt.hist([values_A, values_B], normed=1,  # normed is deprecated and will be replaced by density
                                bins=bins,
                                color=['#3782CC', '#AFD5FA'],
                                label=['A', 'B'])

    xlabels = np.array(bins[1:], dtype='|S4')
    xlabels[-1] = '300+'

    N_labels = len(xlabels)
    plt.xlim([0, 600])
    plt.xticks(25 * np.arange(N_labels) + 12.5)
    ax.set_xticklabels(xlabels)

    plt.yticks([])
    plt.title('')
    plt.setp(patches, linewidth=0)
    plt.legend()

    fig.tight_layout()
    plt.savefig('my_plot_01.png')
    plt.close()

Voilà le résultat, qui n'a pas l'air sympa: enter image description here

J'ai ensuite changé la ligne avec xlim:

plt.xlim([0, 325])

Avec le résultat suivant: enter image description here

Il ressemble plus ou moins à ce que je veux, mais le dernier bac n'est pas visible maintenant. Quelle astuce me manque pour visualiser ce dernier bac d'une largeur de 25?

23

Numpy a une fonction pratique pour gérer cela: np.clip . Malgré le nom du nom, il ne supprime pas les valeurs , il les limite simplement à la plage que vous spécifiez. Fondamentalement, il fait le "hack sale" d'Artem en ligne. Vous pouvez laisser les valeurs telles quelles, mais dans l'appel hist, enveloppez simplement le tableau dans un np.clip appeler, comme ça

plt.hist(np.clip(values_A, bins[0], bins[-1]), bins=bins)

C'est plus agréable pour plusieurs raisons:

  1. C'est manière plus rapide - au moins pour un grand nombre d'éléments. Numpy fait son travail au niveau C. Opérer sur python lists (comme dans la compréhension des listes d'Artem) a beaucoup de surcharge pour chaque élément. En gros, si vous avez la possibilité d'utiliser numpy, vous devriez.

  2. Vous le faites là où vous en avez besoin, ce qui réduit les risques d'erreurs dans votre code.

  3. Vous n'avez pas besoin de garder une deuxième copie du tableau qui traîne, ce qui réduit l'utilisation de la mémoire (sauf dans cette seule ligne) et réduit encore les risques d'erreurs.

  4. En utilisant bins[0], bins[-1] au lieu de coder en dur, les valeurs réduisent les chances de commettre à nouveau des erreurs, car vous pouvez modifier les casiers là où bins a été défini; vous n'avez pas besoin de vous souvenir de les changer dans l'appel à clip ou ailleurs.

Donc, pour tout rassembler comme dans le PO:

import matplotlib.pyplot as plt
import numpy as np

def plot_histogram_01():
    np.random.seed(1)
    values_A = np.random.choice(np.arange(600), size=200, replace=True)
    values_B = np.random.choice(np.arange(600), size=200, replace=True)

    bins = np.arange(0,350,25)

    fig, ax = plt.subplots(figsize=(9, 5))
    _, bins, patches = plt.hist([np.clip(values_A, bins[0], bins[-1]),
                                 np.clip(values_B, bins[0], bins[-1])],
                                # normed=1,  # normed is deprecated; replace with density
                                density=True,
                                bins=bins, color=['#3782CC', '#AFD5FA'], label=['A', 'B'])

    xlabels = bins[1:].astype(str)
    xlabels[-1] += '+'

    N_labels = len(xlabels)
    plt.xlim([0, 325])
    plt.xticks(25 * np.arange(N_labels) + 12.5)
    ax.set_xticklabels(xlabels)

    plt.yticks([])
    plt.title('')
    plt.setp(patches, linewidth=0)
    plt.legend(loc='upper left')

    fig.tight_layout()
plot_histogram_01()

result of code above

32
Mike

Désolé, je ne connais pas matplotlib. J'ai donc un sale hack pour vous. Je viens de mettre toutes les valeurs supérieures à 300 dans un bac et j'ai changé la taille du bac.

La racine du problème est que matplotlib essaie de mettre tous les bacs sur l'intrigue. Dans R, je convertirais mes cases en variable factorielle, donc elles ne sont pas traitées comme des nombres réels.

import matplotlib.pyplot as plt
import numpy as np

def plot_histogram_01():
    np.random.seed(1)
    values_A = np.random.choice(np.arange(600), size=200, replace=True).tolist()
    values_B = np.random.choice(np.arange(600), size=200, replace=True).tolist()
    values_A_to_plot = [301 if i > 300 else i for i in values_A]
    values_B_to_plot = [301 if i > 300 else i for i in values_B]

    bins = [0, 25, 50, 75, 100, 125, 150, 175, 200, 225, 250, 275, 300, 325]

    fig, ax = plt.subplots(figsize=(9, 5))
    _, bins, patches = plt.hist([values_A_to_plot, values_B_to_plot], normed=1,  # normed is deprecated and will be replaced by density
                                bins=bins,
                                color=['#3782CC', '#AFD5FA'],
                                label=['A', 'B'])

    xlabels = np.array(bins[1:], dtype='|S4')
    xlabels[-1] = '300+'

    N_labels = len(xlabels)

    plt.xticks(25 * np.arange(N_labels) + 12.5)
    ax.set_xticklabels(xlabels)

    plt.yticks([])
    plt.title('')
    plt.setp(patches, linewidth=0)
    plt.legend()

    fig.tight_layout()
    plt.savefig('my_plot_01.png')
    plt.close()

plot_histogram_01()

enter image description here

5
Artem Fedosov