Pregunta

Tengo un conjunto de puntos de datos X, Y (aproximadamente 10k) que son fáciles de trazar como una trama de dispersión, pero que me gustaría representar como mapa de calor.

Miré a través de los ejemplos en matplotlib y todos parecen comenzar con valores de celda de mapa de calor para generar la imagen.

¿Hay algún método que convierta un montón de X, Y, todo diferente, en un mapa de calor (donde las zonas con mayor frecuencia de x, y serían "más cálidas")?

¿Fue útil?

Solución

Si no quieres hexágonos, puedes usar Numpy's histogram2d función:

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()

Esto hace un mapa de calor de 50x50. Si quieres, digamos, 512x384, puedes poner bins=(512, 384) en la llamada a histogram2d.

Ejemplo: Matplotlib heat map example

Otros consejos

En Mate léxico, creo que quieres un hexbín gráfico.

Si no está familiarizado con este tipo de trama, es solo un histograma bivariado en el que el plano XY se teselará por una cuadrícula regular de hexágonos.

Entonces, de un histograma, puede contar el número de puntos que caen en cada hexágono, discretiando la región de trazado como un conjunto de Windows, asigne cada punto a una de estas ventanas; Finalmente, asigne las ventanas a un matriz de color, y tienes un diagrama de hexbin.

Aunque es menos comúnmente utilizado que por ejemplo, círculos o cuadrados, que los hexágonos son una mejor opción para la geometría del contenedor binning es intuitiva:

  • Los hexágonos tienen simetría del vecino más cercano (por ejemplo, los contenedores cuadrados no, por ejemplo, la distancia de un punto en el borde de una plaza a un punto dentro de ese cuadrado no es igual en todas partes) y

  • Hexagon es el más alto N-Polygon que da Teselación de plano regular (es decir, puedes modelar con seguridad el piso de tu cocina con baldosas de forma hexagonal porque no tendrás ningún espacio vacío entre las baldosas cuando hayas terminado, no es cierto para todos los demás polígonos más altos, n> = 7, ).

(Mate usa el término hexbín gráfico; también (afaik) todo el trazado de bibliotecas por Riñonal; Aún así, no sé si este es el término generalmente aceptado para las tramas de este tipo, aunque sospecho que es probable que sea dado que hexbín es corto para binning hexagonal, que describe el paso esencial para preparar los datos para la visualización).


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

En lugar de usar np.hist2d, que en general produce histogramas bastante feos, me gustaría reciclar py-sphviewer, un paquete Python para representar simulaciones de partículas utilizando un núcleo de suavizado adaptativo y que se puede instalar fácilmente desde PIP (consulte la documentación de la página web). Considere el siguiente código, que se basa en el ejemplo:

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()

que produce la siguiente imagen:

enter image description here

Como puede ver, las imágenes se ven bastante bien, y podemos identificar diferentes subestructuras. Estas imágenes se construyen extendiendo un peso dado por cada punto dentro de un determinado dominio, definido por la longitud de suavizado, que en turnos se da por la distancia a la más cercana nótese bien vecino (he elegido 16, 32 y 64 para los ejemplos). Por lo tanto, las regiones de mayor densidad generalmente se extienden en regiones más pequeñas en comparación con las regiones de menor densidad.

La función MyPlot es solo una función muy simple que he escrito para dar los datos X, Y a Py-Sphviewer para hacer la magia.

Si está usando 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

EDITAR: Para una mejor aproximación de la respuesta de Alejandro, consulte a continuación.

Sé que esta es una pregunta antigua, pero quería agregar algo a Anwser de Alejandro: si desea una buena imagen suavizada sin usar Py-Sphviewer, puede usar en su lugar. np.histogram2d y aplicar un filtro gaussiano (desde scipy.ndimage.filters) al mapa de calor:

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()

Produce:

Output images

El diagrama de dispersión y S = 16 trazados uno encima del otro para Agape Gal'lo (haga clic para una mejor vista):

On top of eachother


Una diferencia que noté con mi enfoque de filtro gaussiano y el enfoque de Alejandro fue que su método muestra estructuras locales mucho mejores que las mías. Por lo tanto, implementé un método simple de vecino más cercano a nivel de píxeles. Este método calcula para cada píxel la suma inversa de las distancias del n puntos más cercanos en los datos. Este método es en una resolución de alta resolución bastante costoso y creo que hay una manera más rápida, así que avíseme si tiene alguna mejoría. De todos modos, aquí está el código:

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()

Resultado:

Nearest Neighbour Smoothing

Seborn ahora tiene el función de la trama conjunta que debería funcionar bien aquí:

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

Y la pregunta inicial era ... cómo convertir los valores de dispersión a los valores de la cuadrícula, ¿verdad?histogram2d Cuenta la frecuencia por celda, sin embargo, si tiene otros datos por celda que solo la frecuencia, necesitaría un trabajo adicional por hacer.

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

Entonces, tengo un conjunto de datos con resultados Z para las coordenadas X e Y. Sin embargo, estaba calculando pocos puntos fuera del área de interés (grandes brechas) y un montón de puntos en un pequeño área de interés.

Sí, aquí se vuelve más difícil pero también más divertido. Algunas bibliotecas (lo siento):

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

Pyplot es mi motor gráfico hoy, CM es una gama de mapas de color con alguna opción inniterante. Numpy para los cálculos y la griddata para unir valores a una cuadrícula fija.

El último es importante, especialmente porque la frecuencia de los puntos XY no se distribuye igualmente en mis datos. Primero, comencemos con algunos límites que se ajusten a mis datos y un tamaño arbitrario de la cuadrícula. Los datos originales tienen puntos de datos también fuera de esos límites X e Y.

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

Por lo tanto, hemos definido una cuadrícula con 500 píxeles entre los valores MIN y MAX de X e Y.

En mis datos, hay muchos más que los 500 valores disponibles en el área de alto interés; Mientras que en el área de bajo interés, ni siquiera hay 200 valores en la cuadrícula total; entre los límites gráficos de x_min y x_max Hay aún menos.

Entonces, para obtener una buena imagen, la tarea es obtener un promedio de los valores de alto interés y llenar los vacíos en otro lugar.

Defino mi cuadrícula ahora. Para cada par de xx de sí, quiero tener un color.

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

¿Por qué la forma extraña? scipy.griddata quiere una forma de (n, d).

GridData calcula un valor por punto en la cuadrícula, por un método predefinido. Elijo "más cercano": los puntos de cuadrícula vacíos se llenarán con valores del vecino más cercano. Esto parece que las áreas con menos información tienen celdas más grandes (incluso si no es el caso). Uno podría optar por interpolar "lineal", luego las áreas con menos información se ven menos nítidas. Materia del gusto, de verdad.

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])

Y saltar, entregamos a matplotlib para mostrar la trama

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()

Alrededor de la parte puntiaguda de la forma de V, ves que hice muchos cálculos durante mi búsqueda del punto óptimo, mientras que las partes menos interesantes en casi cualquier otro lugar tienen una resolución más baja.

Heatmap of a SVC in high resolution

Hacer una matriz bidimensional que corresponde a las celdas en su imagen final, llamada digamos heatmap_cells e instanciarlo como todos ceros.

Elija dos factores de escala que definan la diferencia entre cada elemento de matriz en unidades reales, para cada dimensión, digamos x_scale y y_scale. Elija estos de tal manera que todos sus puntos de datos caigan dentro de los límites de la matriz de mapa de calor.

Para cada punto de datos sin procesar con x_value y y_value:

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

Muy parecido a @Respuesta de Piti, pero usando 1 llamada en lugar de 2 para generar los puntos:

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()

Producción:

2d_gaussian_heatmap

Me temo que llego un poco tarde a la fiesta, pero tuve una pregunta similar hace un tiempo. La respuesta aceptada (por @Ptomato) me ayudó, pero también me gustaría publicar esto en caso de que sea de utilidad para alguien.


''' 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)


Aquí está el resultadoenter image description here

enter image description here

Aquí hay uno que hice en un set de 1 millón con 3 categorías (de color rojo, verde y azul). Aquí hay un enlace al repositorio si desea probar la función. Repositorio

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)
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top