Plot 3D Grid Data as Heat Map using matplotlib - python

I tried to visualize data as a cube but want to plot each cell inside the cube.
This is a cube not divided by cell but I want this 3D heatmap
I need to visualize the predicted result in a 3D cube with a heatmap
Here is the code:
%matplotlib notebook
%matplotlib inline
from mpl_toolkits.mplot3d import Axes3D
matplotlib.rcParams['backend'] = "Qt4Agg"
import numpy as np
#each sell has its value
y_pred = ([-3.4942558e+01, -1.0922590e+01, -9.4551229e+00, -2.6156654e+01,
1.1722648e+01, 6.4425749e-01, -1.1141028e+01, 5.9728470e+01,
-4.5249408e-01, -8.7741699e+00, 9.9452820e+00, -2.8595707e+00,
2.6979692e+00, 7.9802217e+00, -1.1204488e+00, -4.5213123e+01,
-7.1909481e-01, -8.4901733e+00, -9.2283611e+00, 5.0730385e+01,
-7.9520082e-01, 1.4589276e+02, 8.4267479e+01, -6.7399621e+00,
2.1536992e+02, 5.0958019e+01, -7.7071385e+00, 8.4650040e+01,
2.5421507e+01, -4.1403370e+00, -8.5559702e+00, 5.5478176e+01,
-1.0955868e+01, 3.1425345e+02, 1.9285686e+02, 4.6105843e+00,
6.6680554e+02, 2.7745572e+02, 1.2241451e+01, 5.1578967e+02,
1.8129390e+02, 7.3322144e+00, 1.2733205e+02, 4.4435711e+01,
-2.0441423e-01, 8.3673248e+01, 1.7386259e+02, -1.6146477e-01,
5.9598431e+02, 5.7501422e+02, 4.7413929e+01, 7.6495886e+02,
6.5124884e+02, 7.9399048e+01, 4.0769174e+02, 3.2358469e+02,
3.6726327e+00, 7.5896362e+01, 9.8999245e+01, -1.7699455e+00,
1.0632815e+02, 2.3123619e+02, 4.5826878e+01, 3.6063211e+02,
5.6004309e+02, 1.3757048e+02, 3.7029037e+02, 5.5426331e+02,
1.0625824e+02, 1.7360068e+02, 2.6583237e+02, 2.0798336e+01,
4.7036118e+01, 7.3350151e+01, 9.1420832e+00])
ex1 = np.trunc(y_pred).reshape(5,5,3)
max_value = np.amax(ex1)
min_value = np.amin(ex1)
average = 1/2*(min_value + max_value)
mean_value = np.mean(ex1)
print(f"max: {max_value}\nmin: {min_value}\naverage: {average}\nmean: {mean_value}")
x = 5
y = 5
z = 3
axes = [5, 5, 3]
data = np.ones(axes, dtype=bool)
alpha = 0.5
colors = np.empty(axes + [4])
for x1 in range(x):
for y1 in range(y):
for z1 in range(z):
if ex1[x1][y1][z1] >= average:
colors[x1][y1][z1] = [1, 0, 0, alpha]
elif ex1[x1][y1][z1] > mean_value:
colors[x1][y1][z1] = [1, 1, 0, alpha]
elif ex1[x1][y1][z1] <= mean_value:
colors[x1][y1][z1] = [0, 0, 1, alpha]
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.voxels(data, facecolors=colors, edgecolors='grey')
ax.axis('on')
ax.set_aspect('auto')
plt.show()
I would really appreciate it if you help me with this

Related

colormap scatter plot dependant on cluster membership

Im conducting soft clustering on a data set and I wanted to create a cool graphic that looks similar to the image posted. I want to show a data points membership between two (or more clusters) in graphical form. Im not really sure how to go about this however. Ive used criteria to assign colours to a data point, but am unsure how to create a more dynamic sort of graphic seen below. Any help appreciated.
I think markers are just the thing your looking for:
x1 = y1 = 1
x2 = y2 = 2
dx = np.random.rand(10)
dy = np.random.rand(10)
x = np.array([x1 + dx, x2 + dx]).ravel()
y = np.array([y1 + dy, y2 + dy]).ravel()
threshold = 4
markers = np.array(["o" if xy > threshold else "h" for xy in x + y])
fig, ax = plt.subplots()
for marker in np.unique(markers):
index = markers == marker
ax.scatter(x[index], y[index], marker=marker)
Adding someaditional code to control color and transparency (alpha)
import numpy as np
import matplotlib.pyplot as plt
x1 = y1 = 1
x2 = y2 = 2
dx = np.random.rand(10)
dy = np.random.rand(10)
x = np.array([x1 + dx, x2 + dx]).ravel()
y = np.array([y1 + dy, y2 + dy]).ravel()
threshold = 4
markers = np.array(["o" if xy > threshold else "h" for xy in x + y])
blue_color = "midnightblue" # predefined
pink_color = "orchid"
colors = [blue_color if marker == "o" else pink_color for marker in markers]
alphas = np.array([abs(xy - threshold) for xy in x + y])
alphas = 1 - alphas/np.max(alphas)
fig, ax = plt.subplots()
for i in range(len(x)):
ax.scatter(x[i], y[i], marker=markers[i], color=colors[i], alpha=alphas[i])
The GaussianMixture in scikit-learn does something close to what the question asks.
Specifically, predict_proba(X) returns an array with the probability of each point in X belonging to the component. In the example below we fit two mixture components, so the last two plots should be opposites of each other:
from sklearn.mixture import GaussianMixture
from sklearn.datasets import make_moons
import matplotlib.pyplot as plt
X, _ = make_moons(noise=0.05)
mix = GaussianMixture(n_components=2).fit(X)
probs = mix.predict_proba(X)
fig, ax = plt.subplots(1, 3, sharey=True)
ax[0].scatter(X[:, 0], X[:, 1])
ax[1].scatter(X[:, 0], X[:, 1], c=probs[:, 0])
ax[2].scatter(X[:, 0], X[:, 1], c=probs[:, 1])
plt.show()

Way to contour outer edge of selected grid region in Python

I have the following code:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-np.pi/2, np.pi/2, 30)
y = np.linspace(-np.pi/2, np.pi/2, 30)
x,y = np.meshgrid(x,y)
z = np.sin(x**2+y**2)[:-1,:-1]
fig,ax = plt.subplots()
ax.pcolormesh(x,y,z)
Which gives this image:
Now lets say I want to highlight the edge certain grid boxes:
highlight = (z > 0.9)
I could use the contour function, but this would result in a "smoothed" contour. I just want to highlight the edge of a region, following the edge of the grid boxes.
The closest I've come is adding something like this:
highlight = np.ma.masked_less(highlight, 1)
ax.pcolormesh(x, y, highlight, facecolor = 'None', edgecolors = 'w')
Which gives this plot:
Which is close, but what I really want is for only the outer and inner edges of that "donut" to be highlighted.
So essentially I am looking for some hybrid of the contour and pcolormesh functions - something that follows the contour of some value, but follows grid bins in "steps" rather than connecting point-to-point. Does that make sense?
Side note: In the pcolormesh arguments, I have edgecolors = 'w', but the edges still come out to be blue. Whats going on there?
EDIT:
JohanC's initial answer using add_iso_line() works for the question as posed. However, the actual data I'm using is a very irregular x,y grid, which cannot be converted to 1D (as is required for add_iso_line().
I am using data which has been converted from polar coordinates (rho, phi) to cartesian (x,y). The 2D solution posed by JohanC does not appear to work for the following case:
import numpy as np
import matplotlib.pyplot as plt
from scipy import ndimage
def pol2cart(rho, phi):
x = rho * np.cos(phi)
y = rho * np.sin(phi)
return(x, y)
phi = np.linspace(0,2*np.pi,30)
rho = np.linspace(0,2,30)
pp, rr = np.meshgrid(phi,rho)
xx,yy = pol2cart(rr, pp)
z = np.sin(xx**2 + yy**2)
scale = 5
zz = ndimage.zoom(z, scale, order=0)
fig,ax = plt.subplots()
ax.pcolormesh(xx,yy,z[:-1, :-1])
xlim = ax.get_xlim()
ylim = ax.get_ylim()
xmin, xmax = xx.min(), xx.max()
ymin, ymax = yy.min(), yy.max()
ax.contour(np.linspace(xmin,xmax, zz.shape[1]) + (xmax-xmin)/z.shape[1]/2,
np.linspace(ymin,ymax, zz.shape[0]) + (ymax-ymin)/z.shape[0]/2,
np.where(zz < 0.9, 0, 1), levels=[0.5], colors='red')
ax.set_xlim(*xlim)
ax.set_ylim(*ylim)
This post shows a way to draw such lines. As it is not straightforward to adapt to the current pcolormesh, the following code demonstrates a possible adaption.
Note that the 2d versions of x and y have been renamed, as the 1d versions are needed for the line segments.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
x = np.linspace(-np.pi / 2, np.pi / 2, 30)
y = np.linspace(-np.pi / 2, np.pi / 2, 30)
xx, yy = np.meshgrid(x, y)
z = np.sin(xx ** 2 + yy ** 2)[:-1, :-1]
fig, ax = plt.subplots()
ax.pcolormesh(x, y, z)
def add_iso_line(ax, value, color):
v = np.diff(z > value, axis=1)
h = np.diff(z > value, axis=0)
l = np.argwhere(v.T)
vlines = np.array(list(zip(np.stack((x[l[:, 0] + 1], y[l[:, 1]])).T,
np.stack((x[l[:, 0] + 1], y[l[:, 1] + 1])).T)))
l = np.argwhere(h.T)
hlines = np.array(list(zip(np.stack((x[l[:, 0]], y[l[:, 1] + 1])).T,
np.stack((x[l[:, 0] + 1], y[l[:, 1] + 1])).T)))
lines = np.vstack((vlines, hlines))
ax.add_collection(LineCollection(lines, lw=1, colors=color))
add_iso_line(ax, 0.9, 'r')
plt.show()
Here is an adaption of the second answer, which can work with only 2d arrays:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from scipy import ndimage
x = np.linspace(-np.pi / 2, np.pi / 2, 30)
y = np.linspace(-np.pi / 2, np.pi / 2, 30)
x, y = np.meshgrid(x, y)
z = np.sin(x ** 2 + y ** 2)
scale = 5
zz = ndimage.zoom(z, scale, order=0)
fig, ax = plt.subplots()
ax.pcolormesh(x, y, z[:-1, :-1] )
xlim = ax.get_xlim()
ylim = ax.get_ylim()
xmin, xmax = x.min(), x.max()
ymin, ymax = y.min(), y.max()
ax.contour(np.linspace(xmin,xmax, zz.shape[1]) + (xmax-xmin)/z.shape[1]/2,
np.linspace(ymin,ymax, zz.shape[0]) + (ymax-ymin)/z.shape[0]/2,
np.where(zz < 0.9, 0, 1), levels=[0.5], colors='red')
ax.set_xlim(*xlim)
ax.set_ylim(*ylim)
plt.show()
I'll try to refactor add_iso_line method in order to make it more clear an open for optimisations. So, at first, there comes a must-do part:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
x = np.linspace(-np.pi/2, np.pi/2, 30)
y = np.linspace(-np.pi/2, np.pi/2, 30)
x, y = np.meshgrid(x,y)
z = np.sin(x**2+y**2)[:-1,:-1]
fig, ax = plt.subplots()
ax.pcolormesh(x,y,z)
xlim, ylim = ax.get_xlim(), ax.get_ylim()
highlight = (z > 0.9)
Now highlight is a binary array that looks like this:
After that we can extract indexes of True cells, look for False neighbourhoods and identify positions of 'red' lines. I'm not comfortable enough with doing it in a vectorised manner (like here in add_iso_line method) so just using simple loop:
lines = []
cells = zip(*np.where(highlight))
for x, y in cells:
if x == 0 or highlight[x - 1, y] == 0: lines.append(([x, y], [x, y + 1]))
if x == highlight.shape[0] or highlight[x + 1, y] == 0: lines.append(([x + 1, y], [x + 1, y + 1]))
if y == 0 or highlight[x, y - 1] == 0: lines.append(([x, y], [x + 1, y]))
if y == highlight.shape[1] or highlight[x, y + 1] == 0: lines.append(([x, y + 1], [x + 1, y + 1]))
And, finally, I resize and center coordinates of lines in order to fit with pcolormesh:
lines = (np.array(lines) / highlight.shape - [0.5, 0.5]) * [xlim[1] - xlim[0], ylim[1] - ylim[0]]
ax.add_collection(LineCollection(lines, colors='r'))
plt.show()
In conclusion, this is very similar to JohanC solution and, in general, slower. Fortunately, we can reduce amount of cells significantly, extracting contours only using python-opencv package:
import cv2
highlight = highlight.astype(np.uint8)
contours, hierarchy = cv2.findContours(highlight, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cells = np.vstack(contours).squeeze()
This is an illustration of cells being checked:

Plot unstructured triangular surfaces Python

I am trying to plot a surface created with >10000 unstructured triangles. I have the coordinates of the triangle points and the each triangle points list. My data is as follows,
0.1 0.2 0.1
0.2 0.4 0.6
0.4 0.6 0.4
.
.
.
1 2 3
.
.
.
The first three lines are coordinates (-X,Y,Z COORDINATES-) of the points (point 1 in line 1, point 2 in line 2 and etc). The number of points are more than 10000.
The "1 2 3" says that we have a triangle in which its corner points are 1, 2 and 3.
So, I want to plot the surface by starting from the 1st triangle and plotting them one by one. I have tried to follow the above procedure but I do not get the right figure and finally I get the following error message.
Figure size 432x288 with 0 Axes
I have tried the following code.
import matplotlib
import numpy as np
import matplotlib.cm as cm
import matplotlib.pyplot as plt
from matplotlib.tri import Triangulation
# from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits import mplot3d
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
fileName = open('surface.txt','r')
print(fileName.readline())
dummy = fileName.readline().split()
npo = int(dummy[2])
nel = int(dummy[4])
xp = np.zeros([npo])
yp = np.zeros([npo])
zp = np.zeros([npo])
el1 = np.zeros([nel])
el2 = np.zeros([nel])
el3 = np.zeros([nel])
for i in range(0,npo):
dummy = fileName.readline().split()
xp[i] = float(dummy[0])
yp[i] = float(dummy[1])
zp[i] = float(dummy[2])
# print(i,xp[i],yp[i],zp[i])
for i in range(0,nel):
dummy = fileName.readline().split()
el1[i] = int(dummy[0])
el2[i] = int(dummy[1])
el3[i] = int(dummy[2])
fig2 = plt.figure()
ax2 = fig2.add_subplot(111, projection='3d')
for i in range(0,nel):
x1 = xp[int(el1[i])-1]
y1 = yp[int(el1[i])-1]
z1 = zp[int(el1[i])-1]
x2 = xp[int(el2[i])-1]
y2 = yp[int(el2[i])-1]
z2 = zp[int(el2[i])-1]
x3 = xp[int(el3[i])-1]
y3 = yp[int(el3[i])-1]
z3 = zp[int(el3[i])-1]
xarr = [x1,x2,x3,x1]
yarr = [y1,y2,y3,y1]
zarr = [z1,z2,z3,z1]
verts = [list(zip(xarr,yarr,zarr))]
ax2.add_collection3d(Poly3DCollection(verts))
ax2.set_xbound(0,1)
ax2.set_ybound(0,1)
ax2.set_zbound(0,3)
I will appreciate to hear your opinion.
The function plo_trisurf does exactly what you want.
x, y, z are the nodes of your triangles
tri containes the indices of your triangle nodes
A small example:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
x = np.array([0, -1, 1, 1])
y = np.array([0, 1, -1, 1])
z = np.array([0, 1, 1, -1])
tri = np.array([[0, 1, 2],
[0, 1, 3],
[0, 2, 3]])
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.plot_trisurf(x, y, z, triangles=tri)

How can I plot quadrilateral patches with pcolor in matplotlib?

I am running into a problem with pcolor() from matplotlib. I want to plot patches which have a quadrilateral shape. (They do have a non regular shape, but do have 4 corners and are not overlapping).
To demonstrate my problem, here is a minimal code which produces unexpected behavior.
import numpy as np
from matplotlib import pyplot as plt
x = [0, 1]
y = [0, 2]
val = [[1]]
xx, yy = np.meshgrid(x, y)
fig, ax = plt.subplots(1, 2, figsize=[10, 10])
ax = plt.subplot(2, 1, 1)
yy[1,0] =2.9
#displays a 1x2 rectangle
ax.pcolor(xx, yy, val, edgecolors='black')
ax = plt.subplot(2, 1, 2)
yy[1,0] =3
#displays the expected 1x3(x2) trapezoid
ax.pcolor(xx, yy, val, edgecolors='black')
plt.show()
(We are expecting a trapezoid with height 2.9 but it outputs a rectangle with 2x1. The second example with height 3 works fine)
Here is the output
Also, using pcolormesh, the same problem occurs.
Is this a bug or am I just blind?
The problem is the data type of xx and yy is int64. So when you attempt the assignment yy[1, 0] = 2.9 it is cast to an int, resulting in yy[1, 0] == 2.
The solution is to change the datatype to float, simplest way to do that is using numpy.ndarray.astype:
import numpy as np
from matplotlib import pyplot as plt
x = [0, 1]
y = [0, 2]
val = [[1]]
xx, yy = np.meshgrid(x, y)
xx = xx.astype('float64')
yy = yy.astype('float64')
fig, ax = plt.subplots(1, 2, figsize=[10, 10])
ax = plt.subplot(2, 1, 1)
yy[1,0] = 2.9
ax.pcolor(xx, yy, val, edgecolors='black')
ax = plt.subplot(2, 1, 2)
yy[1,0] = 3
ax.pcolor(xx, yy, val, edgecolors='black')
This will give the expected result

How to add colors to each individual face of a cylinder using matplotlib

I am trying to color each individual face of a cylinder, however I am not sure how to go about it, I have tried the following:
for i in range(10):
col.append([])
for i in range(10):
for j in range(20):
col[i].append(plt.cm.Blues(0.4))
ax.plot_surface(X, Y, Z,facecolors = col,edgecolor = "red")
I want each face to be assigned its own color, so I would think I would supply an array of colors for each of the faces in a 2d array.
But this gives an error:
in plot_surface
colset.append(fcolors[rs][cs])
IndexError: list index out of range
Here is the full code:
import numpy as np
from matplotlib import cm
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.linalg import norm
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
origin = np.array([0, 0, 0])
#axis and radius
p0 = np.array([1, 3, 2])
p1 = np.array([8, 5, 9])
R = 5
#vector in direction of axis
v = p1 - p0
#find magnitude of vector
mag = norm(v)
#unit vector in direction of axis
v = v / mag
#make some vector not in the same direction as v
not_v = np.array([1, 0, 0])
if (v == not_v).all():
not_v = np.array([0, 1, 0])
#make vector perpendicular to v
n1 = np.cross(v, not_v)
#normalize n1
n1 /= norm(n1)
#make unit vector perpendicular to v and n1
n2 = np.cross(v, n1)
#surface ranges over t from 0 to length of axis and 0 to 2*pi
t = np.linspace(0, mag, 200)
theta = np.linspace(0, 2 * np.pi, 100)
#use meshgrid to make 2d arrays
t, theta = np.meshgrid(t, theta)
#generate coordinates for surface
X, Y, Z = [p0[i] + v[i] * t + R * np.sin(theta) * n1[i] + R * np.cos(theta) * n2[i] for i in [0, 1, 2]]
col = []
for i in range(10):
col.append([])
for i in range(10):
for j in range(20):
col[i].append(plt.cm.Blues(0.4))
ax.plot_surface(X, Y, Z,facecolors = col,edgecolor = "red")
#plot axis
ax.plot(*zip(p0, p1), color = 'red')
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.set_zlim(0, 10)
plt.axis('off')
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
plt.show()
Your Z array is of size 100x200, yet you are only specifying 10x20 colors. A quicker way to make col (with the right dimensions) might be something like:
col1 = plt.cm.Blues(np.linspace(0,1,200)) # linear gradient along the t-axis
col1 = np.repeat(col1[np.newaxis,:, :], 100, axis=0) # expand over the theta-axis
col2 = plt.cm.Blues(np.linspace(0,1,100)) # linear gradient along the theta-axis
col2 = np.repeat(col2[:, np.newaxis, :], 200, axis=1) # expand over the t-axis
ax=plt.subplot(121, projection='3d')
ax.plot_surface(X, Y, Z, facecolors=col1)
ax=plt.subplot(122, projection='3d')
ax.plot_surface(X, Y, Z, facecolors=col2)
Which produces:

Categories