Générer une heatmap dans MatPlotLib à l'aide d'un jeu de données scatter

J'ai un ensemble de points de données X,Y (environ 10k) qui sont faciles à tracer comme un nuage de points mais que je voudrais représenter comme une heatmap.

J'ai regardé à travers les exemples dans MatPlotLib et ils semblent tous commencer déjà avec des valeurs de cellules heatmap pour générer l'image.

Existe-t-il une méthode qui convertit un tas de x,y, tous différents, en une heatmap (où les zones avec une fréquence plus élevée de x,y seraient "plus chaudes")?

145
demandé sur honza_p 2010-03-03 10:42:17

8 réponses

Si vous ne voulez pas d'hexagones, vous pouvez utiliser la fonction histogram2d de numpy:

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

# Generate some test data
x = np.random.randn(8873)
y = np.random.randn(8873)

heatmap, xedges, yedges = np.histogram2d(x, y, bins=50)
extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]

plt.clf()
plt.imshow(heatmap.T, extent=extent, origin='lower')
plt.show()

Cela fait une heatmap 50x50. Si vous voulez, par exemple, 512x384, vous pouvez mettre bins=(512, 384) dans l'appel de histogram2d.

Exemple: Exemple de carte de chaleur Matplotlib

144
répondu ptomato 2016-10-27 23:21:26

Dans Matplotlib lexique, je pense que vous voulez un hexbin parcelle.

Si vous n'êtes pas familier avec ce type de tracé, c'est juste un histogramme bivarié dans lequel le plan xy est tessellé par une grille régulière d'hexagones.

Ainsi, à partir d'un histogramme, vous pouvez simplement compter le nombre de points tombant dans chaque hexagone, discrétiser la région de traçage comme un ensemble de windows , assigner chaque point à l'une de ces fenêtres; enfin, mapper les fenêtres sur une couleur array , et vous avez un diagramme hexbin.

Bien que moins couramment utilisé que, par exemple, les cercles ou les carrés, que les hexagones sont un meilleur choix pour la géométrie du conteneur binning est intuitive:

  • Les hexagones ont la symétrie du voisin le plus proche (par exemple, les bacs carrés ne le font pas, par exemple, la distance de un point sur la bordure d'un carré à un point à l'intérieur de ce carré n'est pas partout égal) et

  • Hexagone est le N-polygone le plus élevé qui donne plan régulier tessellation (c'est-à-dire que vous pouvez modéliser en toute sécurité votre plancher de cuisine avec des carreaux de forme hexagonale car vous n'aurez aucun espace vide entre les carreaux lorsque vous aurez terminé-ce qui n'est pas vrai pour tous les autres polygones supérieurs-n, n >= 7).

(Matplotlib utilise le terme hexbin plot; alors faites (AFAIK) toutes les bibliothèques de traçage pour R ; Je ne sais toujours pas si c'est le terme généralement accepté pour les tracés de ce type, bien que je soupçonnez il est probable étant donné que hexbin " est l'abréviation de hexagonale binning, ce qui est décrit l'étape essentielle de la préparation des données pour l'affichage.)


from matplotlib import pyplot as PLT
from matplotlib import cm as CM
from matplotlib import mlab as ML
import numpy as NP

n = 1e5
x = y = NP.linspace(-5, 5, 100)
X, Y = NP.meshgrid(x, y)
Z1 = ML.bivariate_normal(X, Y, 2, 2, 0, 0)
Z2 = ML.bivariate_normal(X, Y, 4, 1, 1, 1)
ZD = Z2 - Z1
x = X.ravel()
y = Y.ravel()
z = ZD.ravel()
gridsize=30
PLT.subplot(111)

# if 'bins=None', then color of each hexagon corresponds directly to its count
# 'C' is optional--it maps values to x-y coordinates; if 'C' is None (default) then 
# the result is a pure 2D histogram 

PLT.hexbin(x, y, C=z, gridsize=gridsize, cmap=CM.jet, bins=None)
PLT.axis([x.min(), x.max(), y.min(), y.max()])

cb = PLT.colorbar()
cb.set_label('mean value')
PLT.show()   

entrez la description de l'image ici

99
répondu doug 2014-04-24 02:51:05

Au lieu d'utiliser np.hist2d, qui en général produit des histogrammes assez laids, je voudrais recycler PY-sphviewer , un paquet python pour rendre des simulations de particules en utilisant un noyau de lissage adaptatif et qui peut être facilement installé à partir de pip (voir la documentation de la page Web). Considérons le code suivant, qui est basé sur l'exemple:

import numpy as np
import numpy.random
import matplotlib.pyplot as plt
import sphviewer as sph

def myplot(x, y, nb=32, xsize=500, ysize=500):   
    xmin = np.min(x)
    xmax = np.max(x)
    ymin = np.min(y)
    ymax = np.max(y)

    x0 = (xmin+xmax)/2.
    y0 = (ymin+ymax)/2.

    pos = np.zeros([3, len(x)])
    pos[0,:] = x
    pos[1,:] = y
    w = np.ones(len(x))

    P = sph.Particles(pos, w, nb=nb)
    S = sph.Scene(P)
    S.update_camera(r='infinity', x=x0, y=y0, z=0, 
                    xsize=xsize, ysize=ysize)
    R = sph.Render(S)
    R.set_logscale()
    img = R.get_image()
    extent = R.get_extent()
    for i, j in zip(xrange(4), [x0,x0,y0,y0]):
        extent[i] += j
    print extent
    return img, extent

fig = plt.figure(1, figsize=(10,10))
ax1 = fig.add_subplot(221)
ax2 = fig.add_subplot(222)
ax3 = fig.add_subplot(223)
ax4 = fig.add_subplot(224)


# Generate some test data
x = np.random.randn(1000)
y = np.random.randn(1000)

#Plotting a regular scatter plot
ax1.plot(x,y,'k.', markersize=5)
ax1.set_xlim(-3,3)
ax1.set_ylim(-3,3)

heatmap_16, extent_16 = myplot(x,y, nb=16)
heatmap_32, extent_32 = myplot(x,y, nb=32)
heatmap_64, extent_64 = myplot(x,y, nb=64)

ax2.imshow(heatmap_16, extent=extent_16, origin='lower', aspect='auto')
ax2.set_title("Smoothing over 16 neighbors")

ax3.imshow(heatmap_32, extent=extent_32, origin='lower', aspect='auto')
ax3.set_title("Smoothing over 32 neighbors")

#Make the heatmap using a smoothing over 64 neighbors
ax4.imshow(heatmap_64, extent=extent_64, origin='lower', aspect='auto')
ax4.set_title("Smoothing over 64 neighbors")

plt.show()

Qui produit l'image suivante:

entrez la description de l'image ici

Comme vous le voyez, les images sont plutôt belles, et nous sommes en mesure pour identifier différentes sous-structures sur elle. Ces images sont construites en étalant un poids donné pour chaque point dans un certain domaine, défini par la longueur de lissage, qui est donnée tour à tour par la distance au voisin nb (j'ai choisi 16, 32 et 64 pour les exemples). Ainsi, les régions à densité plus élevée sont généralement réparties sur des régions plus petites par rapport aux régions à densité plus faible.

La fonction myplot est juste une fonction très simple que j'ai écrite pour donner les données x,y à PY-sphviewer pour faire la magie.

24
répondu Alejandro 2016-04-09 10:06:31

Si vous utilisez 1.2.x

x = randn(100000)
y = randn(100000)
hist2d(x,y,bins=100);

entrez la description de l'image ici

20
répondu Piti Ongmongkolkul 2012-11-05 14:19:29

Edit: pour une meilleure approximation de la réponse D'Alejandro, voir ci-dessous.

Je sais que c'est une vieille question, mais je voulais ajouter quelque chose à L'anwser D'Alejandro: si vous voulez une belle image lissée sans utiliser PY-sphviewer, vous pouvez utiliser {[2] } et appliquer un filtre gaussien (de scipy.ndimage.filters) à la heatmap:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from scipy.ndimage.filters import gaussian_filter


def myplot(x, y, s, bins=1000):
    heatmap, xedges, yedges = np.histogram2d(x, y, bins=bins)
    heatmap = gaussian_filter(heatmap, sigma=s)

    extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
    return heatmap.T, extent


fig, axs = plt.subplots(2, 2)

# Generate some test data
x = np.random.randn(1000)
y = np.random.randn(1000)

sigmas = [0, 16, 32, 64]

for ax, s in zip(axs.flatten(), sigmas):
    if s == 0:
        ax.plot(x, y, 'k.', markersize=5)
        ax.set_title("Scatter plot")
    else:
        img, extent = myplot(x, y, s)
        ax.imshow(img, extent=extent, origin='lower', cmap=cm.jet)
        ax.set_title("Smoothing with  $\sigma$ = %d" % s)

plt.show()

Produit:

Les images de sortie

Le diagramme de dispersion et s = 16 tracés les uns sur les autres pour Agape Gal'lo (Cliquez pour mieux voir):

Sur eachother


Une différence que j'ai remarquée avec mon approche de filtre gaussien et L'approche D'Alejandro était que sa méthode montre les structures locales beaucoup mieux que la mienne. Par conséquent, j'ai implémenté une méthode simple plus proche voisin au niveau du pixel. Cette méthode calcule pour chaque pixel la somme inverse des distances des n points les plus proches dans les données. Cette méthode est à haute résolution assez coûteuse en calcul et je pense qu'il y a un moyen plus rapide, donc laissez-moi savoir si vous avez des améliorations. Quoi qu'il en soit, voici le code:

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


def data_coord2view_coord(p, vlen, pmin, pmax):
    dp = pmax - pmin
    dv = (p - pmin) / dp * vlen
    return dv


def nearest_neighbours(xs, ys, reso, n_neighbours):
    im = np.zeros([reso, reso])
    extent = [np.min(xs), np.max(xs), np.min(ys), np.max(ys)]

    xv = data_coord2view_coord(xs, reso, extent[0], extent[1])
    yv = data_coord2view_coord(ys, reso, extent[2], extent[3])
    for x in range(reso):
        for y in range(reso):
            xp = (xv - x)
            yp = (yv - y)

            d = np.sqrt(xp**2 + yp**2)

            im[y][x] = 1 / np.sum(d[np.argpartition(d.ravel(), n_neighbours)[:n_neighbours]])

    return im, extent


n = 1000
xs = np.random.randn(n)
ys = np.random.randn(n)
resolution = 250

fig, axes = plt.subplots(2, 2)

for ax, neighbours in zip(axes.flatten(), [0, 16, 32, 64]):
    if neighbours == 0:
        ax.plot(xs, ys, 'k.', markersize=2)
        ax.set_aspect('equal')
        ax.set_title("Scatter Plot")
    else:
        im, extent = nearest_neighbours(xs, ys, resolution, neighbours)
        ax.imshow(im, origin='lower', extent=extent, cmap=cm.jet)
        ax.set_title("Smoothing over %d neighbours" % neighbours)
        ax.set_xlim(extent[0], extent[1])
        ax.set_ylim(extent[2], extent[3])
plt.show()

Résultat:

Lissage Le Plus Proche Voisin

12
répondu Jurgy 2018-09-11 08:06:27

Seaborn a maintenant la fonction jointplot qui devrait bien fonctionner ici:

import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

# Generate some test data
x = np.random.randn(8873)
y = np.random.randn(8873)

sns.jointplot(x=x, y=y, kind='hex')
plt.show()

image de démonstration

11
répondu wordsforthewise 2017-03-02 23:57:57

Créez un tableau à 2 dimensions qui correspond aux cellules de votre image finale, appelé say heatmap_cells et instanciez-le comme tous les zéros.

Choisissez deux facteurs d'échelle qui définissent la différence entre chaque élément de tableau en unités réelles, pour chaque dimension, par exemple x_scale et y_scale. Choisissez-les de sorte que tous vos points de données tombent dans les limites du tableau heatmap.

Pour chaque point de données brut avec x_value et y_value:

heatmap_cells[floor(x_value/x_scale),floor(y_value/y_scale)]+=1

2
répondu meepmeep 2010-03-03 12:37:50

Et la question initiale était... comment convertir les valeurs de dispersion en valeurs de grille, Non? histogram2d compte la fréquence par cellule, cependant, si vous avez d'autres données par cellule que la fréquence, vous aurez besoin d'un travail supplémentaire à faire.

x = data_x # between -10 and 4, log-gamma of an svc
y = data_y # between -4 and 11, log-C of an svc
z = data_z #between 0 and 0.78, f1-values from a difficult dataset

Donc, j'ai un ensemble de données avec des résultats Z pour les coordonnées X et Y. Cependant, je calculais quelques points en dehors de la zone d'intérêt (grandes lacunes), et des tas de points dans une petite zone d'intérêt.

Oui ici ça devient plus difficile mais aussi plus de plaisir. Quelques bibliothèques (désolé):

from matplotlib import pyplot as plt
from matplotlib import cm
import numpy as np
from scipy.interpolate import griddata

Pyplot est mon moteur graphique aujourd'hui, cm est une gamme de cartes de couleurs avec un choix initeresting. numpy pour les calculs, et griddata pour attacher des valeurs à une grille fixe.

Le dernier est important surtout parce que la fréquence des points xy n'est pas également répartie dans mes données. Tout d'abord, commençons par quelques limites adaptées à mes données et une taille de grille arbitraire. Les données d'origine ont également des points de données en dehors de ceux x et y frontières.

#determine grid boundaries
gridsize = 500
x_min = -8
x_max = 2.5
y_min = -2
y_max = 7

Nous avons donc défini une grille de 500 pixels entre les valeurs min et max de x et Y.

Dans mes données, il y a beaucoup plus que les 500 valeurs disponibles dans la zone d'intérêt élevé; alors que dans la zone à faible intérêt, il n'y a même pas 200 valeurs dans la grille totale; entre les limites graphiques de x_min et x_max Il y en a encore moins.

Donc, pour obtenir une belle image, la tâche est d'obtenir une moyenne pour l'intérêt supérieur de valeurs et de combler les lacunes ailleurs.

Je définis ma grille maintenant. Pour chaque paire xx-yy, je veux avoir une couleur.

xx = np.linspace(x_min, x_max, gridsize) # array of x values
yy = np.linspace(y_min, y_max, gridsize) # array of y values
grid = np.array(np.meshgrid(xx, yy.T))
grid = grid.reshape(2, grid.shape[1]*grid.shape[2]).T

Pourquoi cette forme étrange? scipy.griddata veut une forme de (n, D).

Griddata calcule une valeur par point dans la grille, par une méthode prédéfinie. Je choisis "le plus proche" - les points de grille vides seront remplis avec des valeurs du voisin le plus proche. Cela semble que les zones avec moins d'informations ont des cellules plus grandes (même si ce n'est pas le cas). On pourrait choisir d'interpoler "linéaire", alors les zones avec moins d'informations semblent moins nettes. Question de goût, vraiment.

points = np.array([x, y]).T # because griddata wants it that way
z_grid2 = griddata(points, z, grid, method='nearest')
# you get a 1D vector as result. Reshape to picture format!
z_grid2 = z_grid2.reshape(xx.shape[0], yy.shape[0])

Et hop, nous matplotlib pour afficher l'intrigue

fig = plt.figure(1, figsize=(10, 10))
ax1 = fig.add_subplot(111)
ax1.imshow(z_grid2, extent=[x_min, x_max,y_min, y_max,  ],
            origin='lower', cmap=cm.magma)
ax1.set_title("SVC: empty spots filled by nearest neighbours")
ax1.set_xlabel('log gamma')
ax1.set_ylabel('log C')
plt.show()

Autour de la partie pointue de la forme en V, vous voyez que j'ai fait beaucoup de calculs lors de ma recherche du sweet spot, alors que les parties les moins intéressantes ont presque partout une résolution inférieure.

Heatmap D'un SVC en haute résolution

2
répondu Anderas 2018-07-17 14:00:48