Création de cartes thermiques annotées

MatplotlibMatplotlibBeginner
Pratiquer maintenant

This tutorial is from open-source community. Access the source code

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

Introduction

En analyse de données, nous souhaitons souvent afficher des données qui dépendent de deux variables indépendantes sous forme d'un graphique d'image coloré appelé carte thermique. Dans ce laboratoire, nous utiliserons la fonction imshow de Matplotlib pour créer une carte thermique avec des annotations. Nous commencerons par un exemple simple et l'élargirons pour qu'il puisse être utilisé comme une fonction universelle.

Conseils sur la machine virtuelle

Une fois le démarrage de la machine virtuelle terminé, cliquez dans le coin supérieur gauche pour basculer vers l'onglet Carnet de notes pour accéder au carnet Jupyter Notebook pour pratiquer.

Parfois, vous devrez peut-être attendre quelques secondes pour que le carnet Jupyter Notebook ait fini de charger. La validation des opérations ne peut pas être automatisée en raison des limites du carnet Jupyter Notebook.

Si vous rencontrez des problèmes pendant l'apprentissage, n'hésitez pas à demander à Labby. Donnez votre feedback après la session et nous résoudrons rapidement le problème pour vous.

Carte thermique catégorielle simple

Nous commencerons par définir certaines données. Nous avons besoin d'une liste ou d'un tableau 2D qui définit les données à colorer. Nous avons également besoin de deux listes ou tableaux de catégories. La carte thermique elle-même est un graphique imshow avec les étiquettes définies comme les catégories. Nous utiliserons la fonction text pour étiqueter les données elles-mêmes en créant un Text dans chaque cellule montrant la valeur de cette cellule.

import matplotlib.pyplot as plt
import numpy as np

légumes = ["concombre", "tomate", "laitue", "asperge", "pomme de terre", "blé", "orge"]
fermiers = ["Farmer Joe", "Upland Bros.", "Smith Gardening", "Agrifun", "Organiculture", "BioGoods Ltd.", "Cornylee Corp."]

récolte = np.array([[0,8, 2,4, 2,5, 3,9, 0,0, 4,0, 0,0],
                    [2,4, 0,0, 4,0, 1,0, 2,7, 0,0, 0,0],
                    [1,1, 2,4, 0,8, 4,3, 1,9, 4,4, 0,0],
                    [0,6, 0,0, 0,3, 0,0, 3,1, 0,0, 0,0],
                    [0,7, 1,7, 0,6, 2,6, 2,2, 6,2, 0,0],
                    [1,3, 1,2, 0,0, 0,0, 3,2, 5,1],
                    [0,1, 2,0, 0,0, 1,4, 0,0, 1,9, 6,3]])

fig, ax = plt.subplots()
im = ax.imshow(récolte)

## Affiche toutes les graduations et les étiquette avec les entrées respectives de la liste
ax.set_xticks(np.arange(len(fermiers)), labels=fermiers)
ax.set_yticks(np.arange(len(légumes)), labels=légumes)

## Fait tourner les étiquettes des graduations et définit leur alignement
plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor")

## Parcourez les dimensions des données et créez des annotations de texte
for i in range(len(légumes)):
    for j in range(len(fermiers)):
        text = ax.text(j, i, récolte[i, j], ha="center", va="center", color="w")

ax.set_title("Récolte des fermiers locaux (en tonnes/an)")
fig.tight_layout()
plt.show()

Utilisation du style de code de la fonction d'aide

Nous allons créer une fonction qui prend les données et les étiquettes de ligne et de colonne en entrée et accepte des arguments utilisés pour personnaliser le tracé. Nous allons désactiver les bords des axes environnants et créer une grille de lignes blanches pour séparer les cellules. Ici, nous voulons également créer une barre de couleur et positionner les étiquettes au-dessus de la carte thermique plutôt qu'au-dessous. Les annotations devront avoir des couleurs différentes selon un seuil pour un meilleur contraste avec la couleur des pixels.

def heatmap(data, row_labels, col_labels, ax=None, cbar_kw=None, cbarlabel="", **kwargs):
    """
    Crée une carte thermique à partir d'un tableau numpy et de deux listes d'étiquettes.

    Paramètres
    ----------
    data
        Un tableau numpy 2D de forme (M, N).
    row_labels
        Une liste ou un tableau de longueur M avec les étiquettes pour les lignes.
    col_labels
        Une liste ou un tableau de longueur N avec les étiquettes pour les colonnes.
    ax
        Une instance de `matplotlib.axes.Axes` sur laquelle la carte thermique est tracée. Si non fourni, utilise l'axe courant ou en crée un nouveau. Facultatif.
    cbar_kw
        Un dictionnaire avec des arguments pour `matplotlib.Figure.colorbar`. Facultatif.
    cbarlabel
        L'étiquette pour la barre de couleur. Facultatif.
    **kwargs
        Tous les autres arguments sont transmis à `imshow`.
    """

    if ax is None:
        ax = plt.gca()

    if cbar_kw is None:
        cbar_kw = {}

    ## Trace la carte thermique
    im = ax.imshow(data, **kwargs)

    ## Crée la barre de couleur
    cbar = ax.figure.colorbar(im, ax=ax, **cbar_kw)
    cbar.ax.set_ylabel(cbarlabel, rotation=-90, va="bottom")

    ## Affiche toutes les graduations et les étiquette avec les entrées respectives de la liste.
    ax.set_xticks(np.arange(data.shape[1]), labels=col_labels)
    ax.set_yticks(np.arange(data.shape[0]), labels=row_labels)

    ## Fait apparaître les étiquettes des axes horizontaux en haut.
    ax.tick_params(top=True, bottom=False, labeltop=True, labelbottom=False)

    ## Fait tourner les étiquettes des graduations et définit leur alignement.
    plt.setp(ax.get_xticklabels(), rotation=-30, ha="right", rotation_mode="anchor")

    ## Désactive les bords et crée une grille blanche.
    ax.spines[:].set_visible(False)
    ax.set_xticks(np.arange(data.shape[1]+1)-.5, minor=True)
    ax.set_yticks(np.arange(data.shape[0]+1)-.5, minor=True)
    ax.grid(which="minor", color="w", linestyle='-', linewidth=3)
    ax.tick_params(which="minor", bottom=False, left=False)

    return im, cbar


def annotate_heatmap(im, data=None, valfmt="{x:.2f}", textcolors=("black", "white"), threshold=None, **textkw):
    """
    Une fonction pour annoter une carte thermique.

    Paramètres
    ----------
    im
        L'AxesImage à étiqueter.
    data
        Données utilisées pour l'étiquetage. Si None, les données de l'image sont utilisées. Facultatif.
    valfmt
        Le format des annotations à l'intérieur de la carte thermique. Cela devrait utiliser soit la méthode de formatage de chaîne, par exemple, "$ {x:.2f}", soit être un `matplotlib.ticker.Formatter`. Facultatif.
    textcolors
        Une paire de couleurs. La première est utilisée pour les valeurs inférieures à un seuil, la seconde pour celles au-dessus. Facultatif.
    threshold
        Valeur en unités de données selon laquelle les couleurs de textcolors sont appliquées. Si None (la valeur par défaut), utilise le milieu de la carte de couleur comme séparation. Facultatif.
    **kwargs
        Tous les autres arguments sont transmis à chaque appel à `text` utilisé pour créer les étiquettes de texte.
    """

    if not isinstance(data, (list, np.ndarray)):
        data = im.get_array()

    ## Normalise le seuil à la plage de couleur de l'image.
    if threshold is not None:
        threshold = im.norm(threshold)
    else:
        threshold = im.norm(data.max())/2.

    ## Définit l'alignement par défaut sur le centre, mais autorise qu'il soit remplacé par textkw.
    kw = dict(horizontalalignment="center", verticalalignment="center")
    kw.update(textkw)

    ## Obtient le formateur au cas où une chaîne est fournie
    if isinstance(valfmt, str):
        valfmt = matplotlib.ticker.StrMethodFormatter(valfmt)

    ## Parcoure les données et crée un `Text` pour chaque "pixel".
    ## Change la couleur du texte selon les données.
    texts = []
    for i in range(data.shape[0]):
        for j in range(data.shape[1]):
            kw.update(color=textcolors[int(im.norm(data[i, j]) > threshold)])
            text = im.axes.text(j, i, valfmt(data[i, j], None), **kw)
            texts.append(text)

    return texts

Application de la fonction

Maintenant que nous avons les fonctions, nous pouvons les utiliser pour créer une carte thermique avec des annotations. Nous créons un nouvel ensemble de données, donnons des arguments supplémentaires à imshow, utilisons un format entier pour les annotations et fournissons quelques couleurs. Nous masquons également les éléments diagonaux (qui sont tous égaux à 1) en utilisant un matplotlib.ticker.FuncFormatter.

data = np.random.randint(2, 100, size=(7, 7))
y = [f"Livre {i}" for i in range(1, 8)]
x = [f"Boutique {i}" for i in list("ABCDEFG")]

fig, ax = plt.subplots()
im, _ = heatmap(data, y, x, ax=ax, vmin=0, cmap="magma_r", cbarlabel="exemplaires vendus hebdomadaires")
annotate_heatmap(im, valfmt="{x:d}", size=7, threshold=20, textcolors=("rouge", "blanc"))

def func(x, pos):
    return f"{x:.2f}".replace("0.", ".").replace("1.00", "")

annotate_heatmap(im, valfmt=matplotlib.ticker.FuncFormatter(func), size=7)

Exemples de cartes thermiques plus complexes

Dans ce qui suit, nous montrons la polyvalence des fonctions créées précédemment en les appliquant dans différents cas et en utilisant différents arguments.

np.random.seed(19680801)

fig, ((ax, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(8, 6))

## Reproduisez l'exemple ci-dessus avec une taille de police et une carte de couleur différentes.

im, _ = heatmap(récolte, légumes, fermiers, ax=ax, cmap="Wistia", cbarlabel="récolte [t/an]")
annotate_heatmap(im, valfmt="{x:.1f}", size=7)

## Parfois, même les données elles-mêmes sont catégorielles. Ici, nous utilisons un `matplotlib.colors.BoundaryNorm` pour classer les données et les utiliser pour colorier le tracé, mais également pour obtenir les étiquettes de classe à partir d'un tableau de classes.

data = np.random.randn(6, 6)
y = [f"Prod. {i}" for i in range(10, 70, 10)]
x = [f"Cycle {i}" for i in range(1, 7)]

qrates = list("ABCDEFG")
norm = matplotlib.colors.BoundaryNorm(np.linspace(-3.5, 3.5, 8), 7)
fmt = matplotlib.ticker.FuncFormatter(lambda x, pos: qrates[::-1][norm(x)])

im, _ = heatmap(data, y, x, ax=ax3, cmap=mpl.colormaps["PiYG"].resampled(7), norm=norm, cbar_kw=dict(ticks=np.arange(-3, 4), format=fmt), cbarlabel="Note de qualité")
annotate_heatmap(im, valfmt=fmt, size=9, fontweight="bold", threshold=-1, textcolors=("rouge", "noir"))

## Nous pouvons tracer agréablement une matrice de corrélation. Puisque celle-ci est limitée entre -1 et 1, nous utilisons ces valeurs comme vmin et vmax.

corr_matrix = np.corrcoef(récolte)
im, _ = heatmap(corr_matrix, légumes, légumes, ax=ax4, cmap="PuOr", vmin=-1, vmax=1, cbarlabel="coeff. de corrélation")
annotate_heatmap(im, valfmt=matplotlib.ticker.FuncFormatter(func), size=7)

plt.tight_layout()
plt.show()

Sommaire

Dans ce laboratoire, nous avons appris à créer des cartes thermiques annotées en Python en utilisant la fonction imshow de Matplotlib. Nous avons commencé par créer une carte thermique catégorielle simple puis l'avons étendue pour en faire une fonction réutilisable. Enfin, nous avons exploré quelques exemples de cartes thermiques plus complexes en utilisant différents arguments.