python - pyplot hist2d
Genere un mapa de calor en MatPlotLib usando un conjunto de datos de dispersiĆ³n (8)
En el léxico de Matplotlib , creo que quieres un diagrama de hexbin .
Si no está familiarizado con este tipo de trama, es simplemente un histograma bivariable en el que el plano xy está teselado por una cuadrícula regular de hexágonos.
Entonces, a partir de un histograma, puede contar el número de puntos que caen en cada hexágono, discretizar la región de trazado como un conjunto de ventanas , asignar cada punto a una de estas ventanas; finalmente, mapea las ventanas en una matriz de colores , y tienes un diagrama hexbin.
Aunque es menos utilizado que, por ejemplo, círculos o cuadrados, los hexágonos son una mejor opción para la geometría del contenedor de binning es intuitivo:
los hexágonos tienen simetría del vecino más cercano (p. ej., los contenedores cuadrados no, por ejemplo, la distancia desde un punto en el borde de un cuadrado hasta un punto dentro de ese cuadrado no es igual en todas partes) y
el hexágono es el n polígono más alto que proporciona teselación plana regular (es decir, puedes remodelar el suelo de tu cocina de forma segura con mosaicos de forma hexagonal porque no tendrás ningún espacio vacío entre los mosaicos cuando hayas terminado; no es cierto para todos los otros polos superiores n, n> = 7).
( Matplotlib usa el término gráfico de hexbin , así que haz (AFAIK) todas las librerías de trazado para R ; aún no sé si este es el término generalmente aceptado para las gráficas de este tipo, aunque sospecho que es probable dado que hexbin es corto para el agrupamiento 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()
Tengo un conjunto de puntos de datos X, Y (alrededor de 10k) que son fáciles de trazar como un diagrama de dispersión, pero que me gustaría representar como un mapa de calor.
Miré a través de los ejemplos en MatPlotLib y todos parecen comenzar con los valores de las celdas de mapa de calor para generar la imagen.
¿Hay algún método que convierta un grupo de x, y, todos diferentes, en un mapa de calor (donde las zonas con mayor frecuencia de x, y serían "más cálidas")?
En lugar de usar np.hist2d, que en general produce histogramas bastante desagradables, me gustaría reciclar py-sphviewer , un paquete de python para simulaciones de partículas utilizando un kernel de suavizado adaptable y que puede instalarse 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:
Como puede ver, las imágenes se ven muy bien y podemos identificar diferentes subestructuras en él. Estas imágenes se construyen extendiendo un peso dado para cada punto dentro de un dominio determinado, definido por la longitud de suavizado, que a su vez viene dada por la distancia al vecino nb más cercano (he elegido 16, 32 y 64 para los ejemplos). Por lo tanto, las regiones de mayor densidad generalmente se extienden por regiones más pequeñas en comparación con las regiones de menor densidad.
La función myplot es simplemente una función muy simple que he escrito para dar los datos x, y a py-sphviewer para hacer la magia.
Haz una matriz bidimensional que corresponda a las celdas de tu imagen final, llamadas di heatmap_cells
y ejemplifica como todos los ceros.
Elija dos factores de escala que definan la diferencia entre cada elemento del conjunto en unidades reales, para cada dimensión, por ejemplo, x_scale
e y_scale
. Elija estos de modo que todos sus puntos de datos caigan dentro de los límites de la matriz de mapas 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
Sé que esta es una vieja pregunta, pero quería agregar algo al guion de Alejandro: si quieres una imagen suavizada sin usar py-sphviewer puedes usar np.histogram2d
y aplicar un filtro gaussiano (desde scipy.ndimage.filters
) a el 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:
Seaborn ahora tiene la función de diagrama compartido 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()
Si está utilizando 1.2.x
x = randn(100000) y = randn(100000) hist2d(x,y,bins=100);
y la pregunta inicial fue ... cómo convertir los valores de dispersión a los valores de la grilla, ¿verdad? histogram2d
sí cuenta la frecuencia por celda, sin embargo, si tiene otros datos por celda que solo la frecuencia, necesitaría un trabajo adicional para 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
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 en día, cm es una gama de mapas de color con algunas opciones inherentes. numpy para los cálculos, y griddata para unir valores a una grilla fija.
El último es importante especialmente porque la frecuencia de los puntos xy no se distribuye por igual en mis datos. Primero, comencemos con algunos límites que se ajusten a mis datos y a un tamaño de cuadrícula arbitrario.
#determine grid boundaries
gridsize = 500
x_min = -8
x_max = 2.5
y_min = -2
y_max = 7
En mis datos, hay mucho más que los 500 valores para esta grilla en el área de alto interés; mientras que en el área de bajo interés, hay un máximo de 200 valores en la grilla total; entre los límites gráficos de x_min
y x_max
hay incluso menos.
Yo defino mi grilla ahora Para cada par xx-yy, quiero tener un color.
xx = np.linspace(x_min, x_max, gridsize)
yy = np.linspace(y_min, y_max, gridsize)
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 rellenarán con los valores del vecino más cercano. Parece que las áreas con menos información tienen celdas más grandes (incluso si no es el caso). Uno puede elegir interpolar "lineal", luego las áreas con menos información se ven menos nítidas. La materia del gusto, realmente.
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 hop, 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, verá que tuve muchos cálculos durante mi búsqueda del punto óptimo, mientras que las partes menos interesantes en casi todos los demás tienen una resolución más baja.
Si no quieres hexágonos, puedes usar la función 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()
Esto hace un mapa de calor de 50x50. Si quiere, digamos, 512x384, puede poner bins=(512, 384)
en la llamada a histogram2d
.
Ejemplo: