Générer une carte thermique dans Matplotlib à l'aide d'un ensemble de données de dispersion

StackOverflow https://stackoverflow.com/questions/2369492

Question

J'ai un ensemble de points de données x, y (environ 10k) qui sont faciles à tracer sous forme de tracé de dispersion mais que je voudrais représenter en tant que carte thermique.

J'ai examiné les exemples de Matplotlib et ils semblent tous commencer déjà avec des valeurs de cellules de la map thermique pour générer l'image.

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

Était-ce utile?

La solution

Si vous ne voulez pas d'hexagones, vous pouvez utiliser Numpy histogram2d fonction:

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 carte thermique 50x50. Si vous voulez, disons, 512x384, vous pouvez mettre bins=(512, 384) dans l'appel à histogram2d.

Exemple: Matplotlib heat map example

Autres conseils

Dans Matplotlib Lexique, je pense que vous voulez un hexbin terrain.

Si vous n'êtes pas familier avec ce type d'intrigue, c'est juste un histogramme bivarié dans lequel le plan Xy est époustouflé 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étisé la région de tracé comme un ensemble de les fenêtres, attribuez chaque point à l'une de ces fenêtres; Enfin, mappez les fenêtres sur un tableau de couleurs, et vous avez un diagramme d'hexbine.

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

  • les hexagones ont Symétrie la plus proche de Neighbor (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

  • L'hexagone est le plus haut N-polygone qui donne Tessellation d'avion ordinaire (c'est-à-dire, vous pouvez ré-modéliser en toute sécurité votre plancher de cuisine avec des carreaux de forme hexagonale parce que vous n'aurez pas d'espace vide entre les carreaux lorsque vous avez terminé - pas vrai pour tous les autres N> = 7, les polygones supérieurs ).

(Matplotlib utilise le terme hexbin terrain; Alors faites (afaik) tous les tracer des bibliothèques pour R; Je ne sais toujours pas si c'est le terme généralement accepté pour les parcelles de ce type, bien que je soupçonne qu'il est probablement donné que hexbin est court pour Binning hexagonal, qui décrit l'étape essentielle dans la préparation des données d'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()   

enter image description here

Au lieu d'utiliser np.hist2d, qui en général produit des histogrammes assez laids, je voudrais recycler py-sphiewer, un package Python pour le rendu des simulations de particules à l'aide d'un noyau de lissage adaptatif et qui peut être facilement installé à partir de PIP (voir la documentation de la page Web). Considérez 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:

enter image description here

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

La fonction MyPlot est juste une fonction très simple que j'ai écrite afin de donner aux données x, y à Py-SphViewer pour faire la magie.

Si vous utilisez 1.2.x

import numpy as np
import matplotlib.pyplot as plt

x = np.random.randn(100000)
y = np.random.randn(100000)
plt.hist2d(x,y,bins=100)
plt.show()

gaussian_2d_heat_map

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 à Anwser d'Alejandro: si vous voulez une belle image lissée sans utiliser Py-SphViewer, vous pouvez plutôt utiliser np.histogram2d et appliquer un filtre gaussien (de scipy.ndimage.filters) à la carte thermique:

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:

Output images

Le tracé de dispersion et S = 16 tracé les uns sur les autres pour Agape Gal'lo (cliquez pour une meilleure vue):

On top of eachother


Une différence que j'ai remarquée avec mon approche filtrante gaussienne et l'approche d'Alejandro était que sa méthode montre des structures locales beaucoup mieux que la mienne. J'ai donc implémenté une méthode du voisin le plus proche au niveau des pixels. Cette méthode calcule pour chaque pixel la somme inverse des distances du n points les plus proches des données. Cette méthode est à une haute résolution assez coûteuse par calcul et je pense qu'il y a un moyen plus rapide, alors faites-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:

Nearest Neighbour Smoothing

Seaborn a maintenant le Fonction JointPlot Ce 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()

demo image

Et la question initiale était ... comment convertir les valeurs de diffusion 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 auriez besoin de travaux supplémentaires à 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

J'ai donc un ensemble de données avec des résults 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, cela devient plus difficile mais aussi plus amusant. Certaines 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 ingétres. Numpy pour les calculs et griddata pour fixer les valeurs à une grille fixe.

Le dernier est important surtout parce que la fréquence des points XY n'est pas également distribuée 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 ces limites x et y.

#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 avec 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 le domaine de l'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 consiste à obtenir une moyenne pour les valeurs d'intérêt élevées et à 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 la forme étrange? scipy.griddata veut une forme de (n, d).

Griddata calcule une valeur par point de la grille, par une méthode prédéfinie. Je choisis "le plus proche" - les points de grille vides seront remplis de valeurs du voisin le plus proche. Cela semble que les zones avec moins d'informations aient des cellules plus grandes (même si ce n'est pas le cas). On pourrait choisir d'interpoler "linéaire", puis les zones avec moins d'informations semblent moins tranchantes. Matter 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 nous remettons à 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 V, vous voyez que j'ai fait beaucoup de calculs pendant ma recherche du point idéal, tandis que les parties les moins intéressantes presque partout ailleurs ont une résolution plus faible.

Heatmap of a SVC in high resolution

Faites un tableau bidimensionnel qui correspond aux cellules de votre image finale, appelée Say heatmap_cells et instanciez-le comme tous les zéros.

Choisissez deux facteurs de mise à l'échelle qui définissent la différence entre chaque élément de tableau dans les unités réelles, pour chaque dimension, disons x_scale et y_scale. Choisissez-les de telle sorte que tous vos points de données se situeront dans les limites du tableau de la carte thermique.

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

Très similaire à La réponse de @ Piti, mais en utilisant 1 appel au lieu de 2 pour générer les points:

import numpy as np
import matplotlib.pyplot as plt

pts = 1000000
mean = [0.0, 0.0]
cov = [[1.0,0.0],[0.0,1.0]]

x,y = np.random.multivariate_normal(mean, cov, pts).T
plt.hist2d(x, y, bins=50, cmap=plt.cm.jet)
plt.show()

Production:

2d_gaussian_heatmap

J'ai peur d'être un peu en retard à la fête, mais j'ai eu une question similaire il y a quelque temps. La réponse acceptée (par @ptomato) m'a aidé, mais je voudrais également le publier au cas où il serait utile pour quelqu'un.


''' I wanted to create a heatmap resembling a football pitch which would show the different actions performed '''

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

#fixing random state for reproducibility
np.random.seed(1234324)

fig = plt.figure(12)
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)

#Ratio of the pitch with respect to UEFA standards 
hmap= np.full((6, 10), 0)
#print(hmap)

xlist = np.random.uniform(low=0.0, high=100.0, size=(20))
ylist = np.random.uniform(low=0.0, high =100.0, size =(20))

#UEFA Pitch Standards are 105m x 68m
xlist = (xlist/100)*10.5
ylist = (ylist/100)*6.5

ax1.scatter(xlist,ylist)

#int of the co-ordinates to populate the array
xlist_int = xlist.astype (int)
ylist_int = ylist.astype (int)

#print(xlist_int, ylist_int)

for i, j in zip(xlist_int, ylist_int):
    #this populates the array according to the x,y co-ordinate values it encounters 
    hmap[j][i]= hmap[j][i] + 1   

#Reversing the rows is necessary 
hmap = hmap[::-1]

#print(hmap)
im = ax2.imshow(hmap)


Voici le résultatenter image description here

enter image description here

En voici un que j'ai fait sur un set de 1 million de points avec 3 catégories (rouge, vert et bleu). Voici un lien vers le référentiel si vous souhaitez essayer la fonction. GitHub Repo

histplot(
    X,
    Y,
    labels,
    bins=2000,
    range=((-3,3),(-3,3)),
    normalize_each_label=True,
    colors = [
        [1,0,0],
        [0,1,0],
        [0,0,1]],
    gain=50)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top