J'ai un ensemble de points de données X, Y (environ 10k) qu'il est facile de tracer sous forme de nuage de points, mais que j'aimerais représenter sous forme de carte thermique.
J'ai parcouru les exemples de MatPlotLib et ils semblent tous déjà commencer par les valeurs de cellules heatmap pour générer l'image.
Existe-t-il une méthode qui convertit un groupe de x, y, tous différents, en cartes thermiques (où les zones avec une fréquence plus élevée de x, y seraient "plus chaudes")?
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 un heatmap 50x50. Si vous voulez, disons 512x384, vous pouvez mettre bins=(512, 384)
dans l'appel à histogram2d
.
Exemple:
Dans Matplotlib Lexicon, je pense que vous voulez un tracé hexbin .
Si vous n'êtes pas familiarisé avec ce type de tracé, il ne s'agit que d'un histogramme à deux variables dans lequel le plan xy est quadrillé par une grille régulière d'hexagones.
Ainsi, à partir d'un histogramme, il vous suffit de compter le nombre de points situés dans chaque hexagone, de discrétiser la région de traçage en tant qu'ensemble de fenêtres , d'affecter chaque point à l'une de ces fenêtres. Enfin, mappez les fenêtres sur un tableau de couleurs et vous obtenez un diagramme hexbin.
Bien qu’ils soient moins utilisés que les cercles ou les carrés, par exemple, les hexagones constituent un meilleur choix pour la géométrie du conteneur de classement est intuitif:
les hexagones ont la symétrie du plus proche voisin (par exemple, les cases carrées ne le sont 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 égal partout) et
hexagone est le n-polygone le plus élevé qui donne une tessellation plane régulière (c.-à-d. que vous pouvez remodeler en toute sécurité le sol de votre cuisine avec des carreaux de forme hexagonale, car vous ne disposerez d'aucun espace vide. entre les tuiles lorsque vous avez terminé - pas vrai pour tous les autres polygones supérieurs-n, n> = 7).
( Matplotlib utilise le terme hexbin plot; il en va de même (autant que je sache) de 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 suppose que c'est probablement étant donné que hexbin est l'abréviation de binning hexagonal , qui 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()
Au lieu d’utiliser np.hist2d, qui produit en général des histogrammes assez laids, j’aimerais recycler py-sphviewer , un package python permettant de rendre des simulations de particules à l’aide d’un noyau de lissage adaptatif et pouvant être facilement installé à partir de documentation de la page Web). Considérez le code suivant, 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:
Comme vous le voyez, les images ont une belle apparence et nous pouvons identifier différentes sous-structures. Ces images sont construites en répartissant un poids donné pour chaque point d’un domaine donné, défini par la longueur de lissage, qui est à son tour donnée par la distance au voisin le plus proche nb (j’ai choisi 16, 32 et 64 pour les exemples). Ainsi, les régions à haute densité sont généralement réparties sur des régions plus petites par rapport aux régions à faible densité.
La fonction myplot est juste une fonction très simple que j'ai écrite afin de donner les 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()
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 à la réponse d'Alejandro: si vous voulez une image lissée de Nice sans utiliser py-sphviewer, vous pouvez utiliser plutôt np.histogram2d
et appliquer un filtre gaussien (à partir 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:
Le diagramme de dispersion et s = 16 superposés pour Agape Gal'lo (cliquez pour avoir une meilleure vue):
Une différence que j'ai remarquée avec mon approche du filtre gaussien et celle d'Alejandro était que sa méthode montrait des structures locales bien meilleures que les miennes. Par conséquent, j'ai implémenté une méthode simple voisin le plus proche au niveau des pixels. Cette méthode calcule pour chaque pixel la somme inverse des distances des points n
les plus proches dans les données. Cette méthode est à haute résolution assez coûteuse en calcul et je pense qu’il ya un moyen plus rapide, alors 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:
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()
et la question initiale était de savoir comment convertir les valeurs de dispersion en valeurs de grille, non? histogram2d
compte la fréquence par cellule. Toutefois, si vous avez d'autres données par cellule que la fréquence, vous aurez besoin de plus de données. Travail à 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 jeu de données avec Z-résultats pour les coordonnées X et Y. Cependant, je calculais peu de 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. 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 d’aujourd’hui, cm est une gamme de cartes de couleurs offrant un choix très intéressant.
Le dernier est important, en particulier 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 correspondant à mes données et une taille de grille arbitraire. Les données d'origine ont des points de données également 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 de 500 pixels entre les valeurs minimale et maximale de x et y.
Dans mes données, il y a beaucoup plus que les 500 valeurs disponibles dans la zone de grand intérêt; alors que dans la zone de faible intérêt, il n'y a même pas 200 valeurs dans le réseau total; 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 des valeurs d’intérêt élevé et de combler les lacunes ailleurs.
Je définis ma grille maintenant. Pour chaque paire de 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, selon une méthode prédéfinie ..__ Je choisis "le plus proche" - les points de grille vides seront remplis avec les valeurs du voisin le plus proche. Cela donne à penser 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", les zones contenant moins d’informations semblant 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 passons à 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 point idéal, alors que les parties les moins intéressantes ont presque partout une résolution inférieure.
Créez un tableau à 2 dimensions qui correspond aux cellules de votre image finale, appelé say heatmap_cells
et instanciez-le en tant que zéros.
Choisissez deux facteurs de mise à l'échelle qui définissent la différence entre chaque élément du 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
J'ai bien peur d'être un peu en retard à la fête, mais j'avais une question similaire il y a quelque temps. La réponse acceptée (par @ptomato) m'a aidée, mais je voudrais aussi poster ceci au cas où cela serait utile à 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)
Très similaire à @ Piti's answer , 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()
Sortie:
En voici une que j'ai faite sur un ensemble 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)