Pergunta

Eu tenho um conjunto de pontos de dados x, y (cerca de 10k) que são fáceis de plotar como um gráfico de dispersão, mas que eu gostaria de representar como um mapa de calor.

Eu olhei através dos exemplos em Matplotlib e eles parecem já começar com os valores das células do mapa de calor para gerar a imagem.

Existe um método que converte um monte de x, y, todos diferentes, em um mapa de calor (onde as zonas com maior frequência de x, y seriam "mais quentes")?

Foi útil?

Solução

Se você não quer hexágonos, você pode usar o Numpy's histogram2d função:

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

Isso faz um mapa de calor de 50x50. Se você quiser, digamos, 512x384, você pode colocar bins=(512, 384) na chamada para histogram2d.

Exemplo: Matplotlib heat map example

Outras dicas

Dentro Matplotlib léxico, acho que você quer um hexbina enredo.

Se você não está familiarizado com esse tipo de enredo, é apenas um Histograma bivariado em que o plano XY é tesado por uma grade regular de hexágonos.

Assim, de um histograma, você pode contar o número de pontos caindo em cada hexágono, discretiize a região da plotagem como um conjunto de janelas, atribua cada ponto a uma dessas janelas; Finalmente, mapeie as janelas para um Array de cores, e você tem um diagrama de hexbina.

Embora menos comumente usado que, por exemplo, círculos ou quadrados, que os hexágonos são uma escolha melhor para a geometria do recipiente de binning é intuitivo:

  • Hexágonos tem Simetria de vizinho mais próximo (por exemplo, caixas quadradas não, por exemplo, a distância a partir de Um ponto na fronteira de um quadrado para um ponto dentro daquele quadrado não é em toda parte igual) e

  • Hexagon é o mais alto n-polígono que dá Planeamento de avião regular (ou seja, você pode modelar novamente com segurança o chão da cozinha com telhas em forma de hexagonal, porque você não terá nenhum espaço vazio entre os ladrilhos quando terminar-não é verdade para todos os outros mais altos n, n> = 7, polígonos ).

(Matplotlib usa o termo hexbina enredo; assim (afaik) todos os plotando bibliotecas por R; Ainda não sei se esse é o termo geralmente aceito para parcelas desse tipo, embora eu suspeite que provavelmente seja dado que hexbina é curto para Binning hexagonal, que descreve a etapa essencial na preparação dos dados para exibição.)


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

Em vez de usar o np.hist2d, que em geral produz histogramas bastante feios, gostaria de reciclar py-sphviewer, um pacote Python para renderizar simulações de partículas usando um kernel de suavização adaptável e que pode ser facilmente instalado a partir do PIP (consulte a documentação da página da web). Considere o seguinte código, que é baseado no exemplo:

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 produz a seguinte imagem:

enter image description here

Como você vê, as imagens parecem muito boas e somos capazes de identificar diferentes subestruturas nela. Essas imagens são construídas espalhando um determinado peso para cada ponto dentro de um determinado domínio, definido pelo comprimento da suavização, que por sua vez é dado pela distância para o mais próximo nb vizinho (eu escolhi 16, 32 e 64 para os exemplos). Portanto, regiões de densidade mais alta normalmente se espalham por regiões menores em comparação com regiões de menor densidade.

A função MyPlot é apenas uma função muito simples que eu escrevi para fornecer aos dados x, y ao py-sphviewer para fazer a mágica.

Se você estiver 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

EDIT: Para uma melhor aproximação da resposta de Alejandro, veja abaixo.

Eu sei que essa é uma pergunta antiga, mas queria adicionar algo ao ANWSER de Alejandro: se você quiser uma boa imagem suavizada sem usar py-sphviewer, você pode usar np.histogram2d e aplicar um filtro gaussiano (de scipy.ndimage.filters) para o 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()

Produz:

Output images

O gráfico de dispersão e S = 16 plotados no topo um do outro para agape Gal'lo (clique para melhor visualizar):

On top of eachother


Uma diferença que notei com minha abordagem de filtro gaussiana e a abordagem de Alejandro foi que seu método mostra estruturas locais muito melhores que as minhas. Portanto, implementei um método simples de vizinho mais próximo no nível do pixel. Este método calcula para cada pixel a soma inversa das distâncias do n Pontos mais próximos nos dados. Esse método está em uma alta resolução bastante caro e acho que há uma maneira mais rápida, então deixe -me saber se você tiver alguma melhoria. Enfim, aqui está o 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

Seaborn agora tem o Função de gráficos de junta que deve funcionar bem aqui:

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

E a pergunta inicial era ... como converter valores de dispersão em valores da grade, certo?histogram2d Conta a frequência por célula, no entanto, se você tiver outros dados por célula do que apenas a frequência, precisará de algum trabalho adicional para fazer.

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

Então, eu tenho um conjunto de dados com resultados z para coordenadas X e Y. No entanto, eu estava calculando alguns pontos fora da área de interesse (grandes lacunas) e montes de pontos em uma pequena área de interesse.

Sim, aqui se torna mais difícil, mas também mais divertido. Algumas bibliotecas (desculpe):

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

O PyPlot é o meu mecanismo gráfico hoje, o CM é uma variedade de mapas de cores com algumas opções de inferência. Numpy para os cálculos e griddata para anexar valores a uma grade fixa.

O último é importante, especialmente porque a frequência dos pontos XY não é igualmente distribuída em meus dados. Primeiro, vamos começar com alguns limites adequados aos meus dados e um tamanho de grade arbitrária. Os dados originais possuem pontos de dados também fora desses limites X e Y.

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

Por isso, definimos uma grade com 500 pixels entre os valores mínimos e max de x e y.

Nos meus dados, existem muito mais do que os 500 valores disponíveis na área de alto interesse; Enquanto na área de juros baixos, não existem nem 200 valores na grade total; entre os limites gráficos de x_min e x_max Existem ainda menos.

Portanto, para obter uma bela imagem, a tarefa é obter uma média para os altos valores de juros e preencher as lacunas em outros lugares.

Eu defino minha grade agora. Para cada par xx-yy, quero ter uma cor.

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 que a forma estranha? scipy.GridData quer uma forma de (n, d).

O GridData calcula um valor por ponto na grade, por um método predefinido. Eu escolho "mais próximo" - os pontos de grade vazios serão preenchidos com valores do vizinho mais próximo. Parece que as áreas com menos informações têm células maiores (mesmo que não sejam o caso). Pode -se optar por interpolar "lineares"; em seguida, as áreas com menos informações parecem menos nítidas. Matéria de gosto, 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])

E salt, entregamos a Matplotlib para exibir o enredo

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

Na parte pontuda da forma de V, você vê que fiz muitos cálculos durante minha busca pelo ponto ideal, enquanto as partes menos interessantes em quase todos os lugares têm uma resolução mais baixa.

Heatmap of a SVC in high resolution

Faça uma matriz bidimensional que corresponda às células em sua imagem final, chamada Say heatmap_cells e instanciá -lo como todos os zeros.

Escolha dois fatores de escala que definem a diferença entre cada elemento da matriz em unidades reais, para cada dimensão, digamos x_scale e y_scale. Escolha isso de modo que todos os seus pontos de dados se enquadrem nos limites da matriz de mapa de calor.

Para cada ponto de dados bruto com x_value e y_value:

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

Muito semelhante a @Resposta de piti, mas usando 1 chamada em vez de 2 para gerar os pontos:

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

Resultado:

2d_gaussian_heatmap

Receio estar um pouco atrasado para a festa, mas tive uma pergunta semelhante há algum tempo. A resposta aceita (de @ptomato) me ajudou, mas eu também gostaria de postar isso, caso seja útil para alguém.


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


Aqui está o resultadoenter image description here

enter image description here

Aqui está um que fiz em um conjunto de 1 milhão de pontos com 3 categorias (vermelho colorido, verde e azul). Aqui está um link para o repositório se você quiser experimentar a função. Repo Github

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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top