Heat Map half-sphere plot - python

I want to plot 720 x 180 values of theta and phi into
theta range = (-180 to 180 with 0.5 step)
phi range = (0 to -90 with 0.5 step)
This is the example of dataset that I have:
Theta Phi Values
-180 0 0.2
-180 0.5 0.5
... ... ...
-180 -90 1.1
-179.5 0 0.92
... ... ...
0 -90 0.6
... ... ...
180 -89.5 0.17
180 -90 0.12
So eventually, I want to get a similar plot like this one:
I know how to create the half sphere with the code below, but how can assign the values from my dataframe?
import matplotlib.pyplot as plt
from matplotlib import cm, colors
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
# Create a sphere
r = 2
pi = np.pi
cos = np.cos
sin = np.sin
altitude
phi, theta = np.mgrid[0.0:0.5*pi:180j, 0.0:2.0*pi:720j] # phi = alti, theta = azi
x = r*sin(phi)*cos(theta)
y = r*sin(phi)*sin(theta)
z = r*cos(phi)
#Set colours and render
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(
x, y, z, rstride=4, cstride=4, color='w', alpha=0.1, linewidth=0)
ax.set_xlim([-2.2,2.2])
ax.set_ylim([-2.2,2.2])
ax.set_zlim([0,3])
ax.set_aspect("equal")
ax.plot_wireframe(x, y, z, color="k")
the code generate this one

Axes3D.plot_surface accepts 2D arrays as inputs. It provides the facecolors argument, which accepts an array of the same shape as the input arrays. This array should have the color for each face as rgba tuple in it. One can therefore normalize the array values to the range up to 1 and supply it the a colormap from matplotlib.cm.
The remaining problem is then to obtain this array from the 3 column list which is provided. Given a the datatable of length n*m where the first column denotes x values, second y values and the third some value, and where the sorting is first by x and then by y. One can then reshape the last column to an (n,m) array, where n is the number of x values and m of y values, using .reshape((m,n)).T.
Some further remarks:
In the solution below, I needed to mimic this array and directly used angles in radiant, instead of degrees.
The number of points, 180*720 seems a bit high. In order for the window not to take ages to rotate, I decreased that number.
I renamed the angles, such that they match with the usual textbook definition, phi = azimuthal angle, theta=inclination angle (from z axis).
The use of plot_wireframe may not make too much sense, since it will hide the surface below. If a wireframe is desired, one can play with the number of points to be drawn and the linewidth keyword argument. Setting linewidth to something big, like 3 or 5 makes the surface look nice, setting it to 1 leaves some wireframe look.
Here is the complete solution.
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
#theta inclination angle
#phi azimuthal angle
n_theta = 50 # number of values for theta
n_phi = 200 # number of values for phi
r = 2 #radius of sphere
theta, phi = np.mgrid[0.0:0.5*np.pi:n_theta*1j, 0.0:2.0*np.pi:n_phi*1j]
x = r*np.sin(theta)*np.cos(phi)
y = r*np.sin(theta)*np.sin(phi)
z = r*np.cos(theta)
# mimic the input array
# array columns phi, theta, value
# first n_theta entries: phi=0, second n_theta entries: phi=0.0315..
inp = []
for j in phi[0,:]:
for i in theta[:,0]:
val = 0.7+np.cos(j)*np.sin(i+np.pi/4.)# put something useful here
inp.append([j, i, val])
inp = np.array(inp)
print inp.shape
print inp[49:60, :]
#reshape the input array to the shape of the x,y,z arrays.
c = inp[:,2].reshape((n_phi,n_theta)).T
print z.shape
print c.shape
#Set colours and render
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
#use facecolors argument, provide array of same shape as z
# cm.<cmapname>() allows to get rgba color from array.
# array must be normalized between 0 and 1
ax.plot_surface(
x,y,z, rstride=1, cstride=1, facecolors=cm.hot(c/c.max()), alpha=0.9, linewidth=1)
ax.set_xlim([-2.2,2.2])
ax.set_ylim([-2.2,2.2])
ax.set_zlim([0,4.4])
ax.set_aspect("equal")
#ax.plot_wireframe(x, y, z, color="k") #not needed?!
plt.savefig(__file__+".png")
plt.show()

Related

Plot a 1D array on 3 radii in a polar heat map

I have a 3 1D arrays: radius, angle and temperature. Together they form a 2D temperature map of a ring.
The arrays take the form:
r = [0,0,0,1,1,1,2,2,2]
th = [0.,0.78539816,1.57079633,2.35619449,3.14159265,3.92699082,4.71238898,5.49778714,6.28318531]
z = [-1,2,5,2,4,-1,3,2,3]
I don't understand how I can make those z data fall on the right coordinates.
I can make it work, with random numbers, using the following simple code:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
fig = plt.figure()
ax = Axes3D(fig)
rad = np.linspace(.2, 1, 4)
azm = np.linspace(0, 2 * np.pi, 9)
r, th = np.meshgrid(rad, azm)
z = np.random.rand(9,4) ** th * r
ax0 = plt.subplot(projection="polar")
im = plt.pcolormesh(th, r, z, cmap='bwr')
plt.plot(azm, r, color='k', ls='none')
plt.axis('off')
cbar = fig.colorbar(im)
ax0.set_title('3 radii polar heat map')
This is how my example code comes out
I ended splitting the list z into 3 lists and made a matrix out of them using z_mat = np.array([z1,z2,z3]). I took care that the lists for rad and azm contained one item more than z, which is a requirement for pcolormesh. After that I transposed the matrix to have the same dimensions as r and th using z_mat_trans = z_mat.transpose()

How do I plot a vector field within an arbitrary plane using Python?

I have a 3d velocity vector field in a numpy array of shape (zlength, ylength, xlength, 3). The '3' contains the velocity components (u,v,w).
I can quite easily plot the vector field in the orthogonal x-y, x-z, and y-z planes using quiver, e.g.
X, Y = np.meshgrid(xvalues, yvalues)
xyfieldfig = plt.figure()
xyfieldax = xyfieldfig.add_subplot(111)
Q1 = xyfieldax.quiver(X, Y, velocity_field[zslice,:,:,0], velocity_field[zslice,:,:,1])
However, I'd like to be able to view the velocity field within an arbitrary plane.
I tried to project the velocity field onto a plane by doing:
projected_field = np.zeros(zlength,ylength,xlength,3)
normal = (nx,ny,nz) #normalised normal to the plane
for i in range(zlength):
for j in range(ylength):
for k in range(xlength):
projected_field[i,j,m] = velocity_field[i,j,m] - np.dot(velocity_field[i,j,m], normal)*normal
However, this (of course) still leaves me with a 3d numpy array with the same shape: (zlength, ylength, xlength, 3). The projected_field now contains velocity vectors at each (x,y,z) position that lie within planes at each local (x,y,z) position.
How do I project velocity_field onto a single plane? Or, how do I now plot my projected_field along one plane?
Thanks in advance!
You're close. Daniel F's suggestion was right, you just need to know how to do the interpolation. Here's a worked example
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import numpy as np
import scipy.interpolate
def norm(v,axis=0):
return np.sqrt(np.sum(v**2,axis=axis))
#Original velocity field
xpoints = np.arange(-.2, .21, 0.05)
ypoints = np.arange(-.2, .21, 0.05)
zpoints = np.arange(-.2, .21, 0.05)
x, y, z = np.meshgrid(xpoints,ypoints,zpoints,indexing='ij')
#Simple example
#(u,v,w) are the components of your velocity field
u = x
v = y
w = z
#Setup a template for the projection plane. z-axis will be rotated to point
#along the plane normal
planex, planey, planez =
np.meshgrid(np.arange(-.2,.2001,.1),
np.arange(-.2,.2001,.1), [0.1],
indexing='ij')
planeNormal = np.array([0.1,0.4,.4])
planeNormal /= norm(planeNormal)
#pick an arbirtrary vector for projection x-axis
u0 = np.array([-(planeNormal[2] + planeNormal[1])/planeNormal[0], 1, 1])
u1 = -np.cross(planeNormal,u0)
u0 /= norm(u0)
u1 /= norm(u1)
#rotation matrix
rotation = np.array([u0,u1,planeNormal]).T
#Rotate plane to get projection vertices
rotatedVertices = rotation.dot( np.array( [planex.flatten(), planey.flatten(), planez.flatten()]) ).T
#Now you can interpolate gridded vector field to rotated vertices
uprime = scipy.interpolate.interpn( (xpoints,ypoints,zpoints), u, rotatedVertices, bounds_error=False )
vprime = scipy.interpolate.interpn( (xpoints,ypoints,zpoints), v, rotatedVertices, bounds_error=False )
wprime = scipy.interpolate.interpn( (xpoints,ypoints,zpoints), w, rotatedVertices, bounds_error=False )
#Projections
cosineMagnitudes = planeNormal.dot( np.array([uprime,vprime,wprime]) )
uProjected = uprime - planeNormal[0]*cosineMagnitudes
vProjected = vprime - planeNormal[1]*cosineMagnitudes
wProjected = wprime - planeNormal[2]*cosineMagnitudes
The number of lines could be reduced using some tensordot operations if you wanted to get fancy. Also this or some close variant it would work without indexing='ij' in meshgrid.
Original field:
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.quiver(x, y, z, u, v, w, length=0.1, normalize=True)
Projected field:
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.quiver(rotatedVertices[:,0], rotatedVertices[:,1], rotatedVertices[:,2],
uprime, vprime,wprime, length=0.5, color='blue', label='Interpolation only')
ax.quiver(rotatedVertices[:,0], rotatedVertices[:,1], rotatedVertices[:,2],
uProjected, vProjected, wProjected, length=0.5, color='red', label='Interpolation + Projection')
plt.legend()

Heat map on unit sphere

I would like to plot a heat map on the unit sphere using the matplotlib library of python. There are several places where this question is discussed. Just like this: Heat Map half-sphere plot
I can do this partially. I can creat the sphere and the heatplot. I have coordinate matrices X,Y and Z, which have the same size. I have another variable of the same size as X, Y and Z, which contains scalars used to creat the heat map. However in case c contains scalars differ from zero in its first and last rows, just one polar cap will be colored but not the other. The code generates the above mentioned result is the next:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
#Creating the theta and phi values.
theta = np.linspace(0,np.pi,100,endpoint=True)
phi = np.linspace(0,np.pi*2,100,endpoint=True)
#Creating the coordinate grid for the unit sphere.
X = np.outer(np.sin(theta),np.cos(phi))
Y = np.outer(np.sin(theta),np.sin(phi))
Z = np.outer(np.cos(theta),np.ones(100))
#Creating a 2D matrix contains the values used to color the unit sphere.
c = np.zeros((100,100))
for i in range(100):
c[0,i] = 100
c[99,i] = 100
#Creat the plot.
fig = plt.figure()
ax = fig.add_subplot(111,projection='3d')
ax.set_axis_off()
ax.plot_surface(X,Y,Z, rstride=1, cstride=1, facecolors=cm.plasma(c/np.amax(c)), alpha=0.22, linewidth=1)
m = cm.ScalarMappable(cmap=cm.plasma)
m.set_array(c)
plt.colorbar(m)
#Show the plot.
plt.show()
The plot which was generated:
Could somebody help me what's going on here?
Thank you for your help in advance!
There are a number of small differences with your example but an
important one, namely the shape of the values array c.
As mentioned in another
answer the grid that
defines the surface is larger (by one in both dimensions) than the
grid that defines the value in each quadrangular patch, so that by
using a smaller array for c it is possible to choose correctly the
bands to color not only with respect to the beginnings of the c
array but also with respect to its ends, as I tried to demonstrate in
the following.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
# Creating the theta and phi values.
intervals = 8
ntheta = intervals
nphi = 2*intervals
theta = np.linspace(0, np.pi*1, ntheta+1)
phi = np.linspace(0, np.pi*2, nphi+1)
# Creating the coordinate grid for the unit sphere.
X = np.outer(np.sin(theta), np.cos(phi))
Y = np.outer(np.sin(theta), np.sin(phi))
Z = np.outer(np.cos(theta), np.ones(nphi+1))
# Creating a 2D array to be color-mapped on the unit sphere.
# {X, Y, Z}.shape → (ntheta+1, nphi+1) but c.shape → (ntheta, nphi)
c = np.zeros((ntheta, nphi)) + 0.4
# The poles are different
c[ :1, :] = 0.8
c[-1:, :] = 0.8
# as well as the zones across Greenwich
c[:, :1] = 0.0
c[:, -1:] = 0.0
# Creating the colormap thingies.
cm = mpl.cm.inferno
sm = mpl.cm.ScalarMappable(cmap=cm)
sm.set_array([])
# Creating the plot.
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors=cm(c), alpha=0.3)
plt.colorbar(m)
# Showing the plot.
plt.show()
The values in the arrays define the edges of the grid. The color of the ith face is determined by the ith value in the color array. However, for n edges you only have n-1 faces, such that the last value is ignored.
E.g. if you have 4 grid values and 4 colors, the plot will have only the first three colors in the grid.
Thus a solution for the above would be to use a color array with one color less than gridpoints in each dimension.
c = np.zeros((99,99))
c[[0,98],:] = 100

Matplotlib - contour and quiver plot in projected polar coordinates

I need to plot contour and quiver plots of scalar and vector fields defined on an uneven grid in (r,theta) coordinates.
As a minimal example of the problem I have, consider the contour plot of a Stream function for a magnetic dipole, contours of such a function are streamlines of the corresponeding vector field (in this case, the magnetic field).
The code below takes an uneven grid in (r,theta) coordinates, maps it to the cartesian plane and plots a contour plot of the stream function.
import numpy as np
import matplotlib.pyplot as plt
r = np.logspace(0,1,200)
theta = np.linspace(0,np.pi/2,100)
N_r = len(r)
N_theta = len(theta)
# Polar to cartesian coordinates
theta_matrix, r_matrix = np.meshgrid(theta, r)
x = r_matrix * np.cos(theta_matrix)
y = r_matrix * np.sin(theta_matrix)
m = 5
psi = np.zeros((N_r, N_theta))
# Stream function for a magnetic dipole
psi = m * np.sin(theta_matrix)**2 / r_matrix
contour_levels = m * np.sin(np.linspace(0, np.pi/2,40))**2.
fig, ax = plt.subplots()
# ax.plot(x,y,'b.') # plot grid points
ax.set_aspect('equal')
ax.contour(x, y, psi, 100, colors='black',levels=contour_levels)
plt.show()
For some reason though, the plot I get doesn't look right:
If I interchange x and y in the contour function call, I get the desired result:
Same thing happens when I try to make a quiver plot of a vector field defined on the same grid and mapped to the x-y plane, except that interchanging x and y in the function call no longer works.
It seems like I made a stupid mistake somewhere but I can't figure out what it is.
If psi = m * np.sin(theta_matrix)**2 / r_matrix
then psi increases as theta goes from 0 to pi/2 and psi decreases as r increases.
So a contour line for psi should increase in r as theta increases. That results
in a curve that goes counterclockwise as it radiates out from the center. This is
consistent with the first plot you posted, and the result returned by the first version of your code with
ax.contour(x, y, psi, 100, colors='black',levels=contour_levels)
An alternative way to confirm the plausibility of the result is to look at a surface plot of psi:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as axes3d
r = np.logspace(0,1,200)
theta = np.linspace(0,np.pi/2,100)
N_r = len(r)
N_theta = len(theta)
# Polar to cartesian coordinates
theta_matrix, r_matrix = np.meshgrid(theta, r)
x = r_matrix * np.cos(theta_matrix)
y = r_matrix * np.sin(theta_matrix)
m = 5
# Stream function for a magnetic dipole
psi = m * np.sin(theta_matrix)**2 / r_matrix
contour_levels = m * np.sin(np.linspace(0, np.pi/2,40))**2.
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
ax.set_aspect('equal')
ax.plot_surface(x, y, psi, rstride=8, cstride=8, alpha=0.3)
ax.contour(x, y, psi, colors='black',levels=contour_levels)
plt.show()

Generate a heatmap using a scatter data set

I have a set of X,Y data points (about 10k) that are easy to plot as a scatter plot but that I would like to represent as a heatmap.
I looked through the examples in Matplotlib and they all seem to already start with heatmap cell values to generate the image.
Is there a method that converts a bunch of x, y, all different, to a heatmap (where zones with higher frequency of x, y would be "warmer")?
If you don't want hexagons, you can use numpy's histogram2d function:
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()
This makes a 50x50 heatmap. If you want, say, 512x384, you can put bins=(512, 384) in the call to histogram2d.
Example:
In Matplotlib lexicon, i think you want a hexbin plot.
If you're not familiar with this type of plot, it's just a bivariate histogram in which the xy-plane is tessellated by a regular grid of hexagons.
So from a histogram, you can just count the number of points falling in each hexagon, discretiize the plotting region as a set of windows, assign each point to one of these windows; finally, map the windows onto a color array, and you've got a hexbin diagram.
Though less commonly used than e.g., circles, or squares, that hexagons are a better choice for the geometry of the binning container is intuitive:
hexagons have nearest-neighbor symmetry (e.g., square bins don't,
e.g., the distance from a point on a square's border to a point
inside that square is not everywhere equal) and
hexagon is the highest n-polygon that gives regular plane
tessellation (i.e., you can safely re-model your kitchen floor with hexagonal-shaped tiles because you won't have any void space between the tiles when you are finished--not true for all other higher-n, n >= 7, polygons).
(Matplotlib uses the term hexbin plot; so do (AFAIK) all of the plotting libraries for R; still i don't know if this is the generally accepted term for plots of this type, though i suspect it's likely given that hexbin is short for hexagonal binning, which is describes the essential step in preparing the data for display.)
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()
Edit: For a better approximation of Alejandro's answer, see below.
I know this is an old question, but wanted to add something to Alejandro's anwser: If you want a nice smoothed image without using py-sphviewer you can instead use np.histogram2d and apply a gaussian filter (from scipy.ndimage.filters) to the heatmap:
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()
Produces:
The scatter plot and s=16 plotted on top of eachother for Agape Gal'lo (click for better view):
One difference I noticed with my gaussian filter approach and Alejandro's approach was that his method shows local structures much better than mine. Therefore I implemented a simple nearest neighbour method at pixel level. This method calculates for each pixel the inverse sum of the distances of the n closest points in the data. This method is at a high resolution pretty computationally expensive and I think there's a quicker way, so let me know if you have any improvements.
Update: As I suspected, there's a much faster method using Scipy's scipy.cKDTree. See Gabriel's answer for the implementation.
Anyway, here's my 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()
Result:
Instead of using np.hist2d, which in general produces quite ugly histograms, I would like to recycle py-sphviewer, a python package for rendering particle simulations using an adaptive smoothing kernel and that can be easily installed from pip (see webpage documentation). Consider the following code, which is based on the example:
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([len(x),3])
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()
which produces the following image:
As you see, the images look pretty nice, and we are able to identify different substructures on it. These images are constructed spreading a given weight for every point within a certain domain, defined by the smoothing length, which in turns is given by the distance to the closer nb neighbor (I've chosen 16, 32 and 64 for the examples). So, higher density regions typically are spread over smaller regions compared to lower density regions.
The function myplot is just a very simple function that I've written in order to give the x,y data to py-sphviewer to do the magic.
If you are using 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()
Seaborn now has the jointplot function which should work nicely here:
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()
Here's Jurgy's great nearest neighbour approach but implemented using scipy.cKDTree. In my tests it's about 100x faster.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from scipy.spatial import cKDTree
def data_coord2view_coord(p, resolution, pmin, pmax):
dp = pmax - pmin
dv = (p - pmin) / dp * resolution
return dv
n = 1000
xs = np.random.randn(n)
ys = np.random.randn(n)
resolution = 250
extent = [np.min(xs), np.max(xs), np.min(ys), np.max(ys)]
xv = data_coord2view_coord(xs, resolution, extent[0], extent[1])
yv = data_coord2view_coord(ys, resolution, extent[2], extent[3])
def kNN2DDens(xv, yv, resolution, neighbours, dim=2):
"""
"""
# Create the tree
tree = cKDTree(np.array([xv, yv]).T)
# Find the closest nnmax-1 neighbors (first entry is the point itself)
grid = np.mgrid[0:resolution, 0:resolution].T.reshape(resolution**2, dim)
dists = tree.query(grid, neighbours)
# Inverse of the sum of distances to each grid point.
inv_sum_dists = 1. / dists[0].sum(1)
# Reshape
im = inv_sum_dists.reshape(resolution, resolution)
return im
fig, axes = plt.subplots(2, 2, figsize=(15, 15))
for ax, neighbours in zip(axes.flatten(), [0, 16, 32, 63]):
if neighbours == 0:
ax.plot(xs, ys, 'k.', markersize=5)
ax.set_aspect('equal')
ax.set_title("Scatter Plot")
else:
im = kNN2DDens(xv, yv, resolution, neighbours)
ax.imshow(im, origin='lower', extent=extent, cmap=cm.Blues)
ax.set_title("Smoothing over %d neighbours" % neighbours)
ax.set_xlim(extent[0], extent[1])
ax.set_ylim(extent[2], extent[3])
plt.savefig('new.png', dpi=150, bbox_inches='tight')
and the initial question was... how to convert scatter values to grid values, right?
histogram2d does count the frequency per cell, however, if you have other data per cell than just the frequency, you'd need some additional work to do.
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
So, I have a dataset with Z-results for X and Y coordinates. However, I was calculating few points outside the area of interest (large gaps), and heaps of points in a small area of interest.
Yes here it becomes more difficult but also more fun. Some libraries (sorry):
from matplotlib import pyplot as plt
from matplotlib import cm
import numpy as np
from scipy.interpolate import griddata
pyplot is my graphic engine today,
cm is a range of color maps with some initeresting choice.
numpy for the calculations,
and griddata for attaching values to a fixed grid.
The last one is important especially because the frequency of xy points is not equally distributed in my data. First, let's start with some boundaries fitting to my data and an arbitrary grid size. The original data has datapoints also outside those x and y boundaries.
#determine grid boundaries
gridsize = 500
x_min = -8
x_max = 2.5
y_min = -2
y_max = 7
So we have defined a grid with 500 pixels between the min and max values of x and y.
In my data, there are lots more than the 500 values available in the area of high interest; whereas in the low-interest-area, there are not even 200 values in the total grid; between the graphic boundaries of x_min and x_max there are even less.
So for getting a nice picture, the task is to get an average for the high interest values and to fill the gaps elsewhere.
I define my grid now. For each xx-yy pair, i want to have a 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
Why the strange shape? scipy.griddata wants a shape of (n, D).
Griddata calculates one value per point in the grid, by a predefined method.
I choose "nearest" - empty grid points will be filled with values from the nearest neighbor. This looks as if the areas with less information have bigger cells (even if it is not the case). One could choose to interpolate "linear", then areas with less information look less sharp. Matter of taste, really.
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])
And hop, we hand over to matplotlib to display the plot
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()
Around the pointy part of the V-Shape, you see I did a lot of calculations during my search for the sweet spot, whereas the less interesting parts almost everywhere else have a lower resolution.
Make a 2-dimensional array that corresponds to the cells in your final image, called say heatmap_cells and instantiate it as all zeroes.
Choose two scaling factors that define the difference between each array element in real units, for each dimension, say x_scale and y_scale. Choose these such that all your datapoints will fall within the bounds of the heatmap array.
For each raw datapoint with x_value and y_value:
heatmap_cells[floor(x_value/x_scale),floor(y_value/y_scale)]+=1
Very similar to #Piti's answer, but using 1 call instead of 2 to generate the 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()
Output:
Here's one I made on a 1 Million point set with 3 categories (colored Red, Green, and Blue). Here's a link to the repository if you'd like to try the function. 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)
I'm afraid I'm a little late to the party but I had a similar question a while ago. The accepted answer (by #ptomato) helped me out but I'd also want to post this in case it's of use to someone.
''' 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)
Here's the result
None of these solutions worked for my application, so this is what I came up with. Essentially I am placing a 2D Gaussian at every single point:
import cv2
import numpy as np
import matplotlib.pyplot as plt
def getGaussian2D(ksize, sigma, norm=True):
oneD = cv2.getGaussianKernel(ksize=ksize, sigma=sigma)
twoD = np.outer(oneD.T, oneD)
return twoD / np.sum(twoD) if norm else twoD
def pt2heat(pts, shape, kernel=16, sigma=5):
heat = np.zeros(shape)
k = getGaussian2D(kernel, sigma)
for y,x in pts:
x, y = int(x), int(y)
for i in range(-kernel//2, kernel//2):
for j in range(-kernel//2, kernel//2):
if 0 <= x+i < shape[0] and 0 <= y+j < shape[1]:
heat[x+i, y+j] = heat[x+i, y+j] + k[i+kernel//2, j+kernel//2]
return heat
heat = pts2heat(pts, img.shape[:2])
plt.imshow(heat, cmap='heat')
Here are the points overlayed ontop of it's associated image, along with the resulting heat map:

Categories

Resources