How do I highlight a slice on a matplotlib 3D surface plot? - python

I have a 3D surface plot. I would also like to plot slices of this plot in 2D, and somehow indicate on the 3D plot where the slices came from (such as coloring the points along the slice to 'highlight' the slice, or plotting an intersecting plane or something).
Following is an example where I am simply setting a particular row to 0 so I can see where the slice is on the 3D plot.
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
# Grid and test function
N = 29;
x,y = np.linspace(-1,1, N*2), np.linspace(-1,1, N)
X,Y = np.meshgrid(x,y)
F = lambda X,Y : np.sin(10*X)/(1+5*(X**2+Y**2))
Z = F(X,Y)
# 3D Surface plot
plt.figure(figsize = (5,6))
Z2 = Z.copy(); Z2[10,:] = 0 # <----- Replace this code
ax = plt.subplot(211, projection='3d')
ax.plot_surface(X,Y,Z2)
# 2D Plot of slice of 3D plot
plt.subplot(212)
plt.plot(x,Z[10,:])
plt.show()
plt.savefig('surfacePlotHighlight.png')

You can color slices in the X or Y directions using the facecoloroptions in plot_surface, and a similar setting of the color in plot. E.g.
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
# Grid and test function
N = 29;
x,y = np.linspace(-1,1, N*2), np.linspace(-1,1, N)
X,Y = np.meshgrid(x,y)
F = lambda X,Y : np.sin(10*X)/(1+5*(X**2+Y**2))
Z = F(X,Y)
# 3D Surface plot
plt.figure(figsize = (5,6))
ax = plt.subplot(211, projection='3d')
# Normalise Y for calling in the cmap.
Ys = Y/Y.max()
cmap = plt.cm.viridis
ax.plot_surface(X, Y, Z2, facecolors=cmap(Ys))
# 2D Plot of slice of 3D plot
# Normalise y for calling in the cmap.
ys = y/y.max()
plt.subplot(212)
plt.plot(x,Z[10,:], color=cmap(ys[10]))
plt.plot(x,Z[20,:], color=cmap(ys[20]))
plt.show()
plt.savefig('surfacePlotHighlight.png')
EDIT:
This can be used to highlight a single row (or column, or arbitrary set of points) by editing the color array to call out specific cells, such as:
# 3D Surface plot
plt.figure(1,figsize = (5,6))
ax = plt.subplot(211, projection='3d')
# Create array to specify color of each pixel on surface
Ys = Y*0
Ys[:,:] = .3
Ys[10] = 1
Ys[20] = .7
cmap = plt.cm.viridis
ax.plot_surface(X, Y, Z, facecolors=cmap(Ys))
# 2D Plot of slice of 3D plot
# Normalise y for calling in the cmap.
ys = Ys[:,0]
plt.subplot(212)
plt.plot(x,Z[10,:], color=cmap(ys[10]))
plt.plot(x,Z[20,:], color=cmap(ys[20]))
plt.show()
plt.savefig('surfacePlotHighlight.png')

You may colorize the row that is shown in a different color than the rest.
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
# Grid and test function
N = 29;
x,y = np.linspace(-1,1, N*2), np.linspace(-1,1, N)
X,Y = np.meshgrid(x,y)
F = lambda X,Y : np.sin(10*X)/(1+5*(X**2+Y**2))
Z = F(X,Y)
y0 = 10
norm=plt.Normalize(Z.min(), Z.max())
C = plt.cm.Blues_r(norm(Z)/2)
C[y0] = plt.cm.Reds_r(norm(Z[y0])/2)
# 3D Surface plot
plt.figure(figsize = (5,6))
ax = plt.subplot(211, projection='3d')
ax.plot_surface(X,Y,Z, facecolors=C)
# 2D Plot of slice of 3D plot
plt.subplot(212)
plt.plot(x,Z[y0,:], color=plt.cm.Reds(.7))
plt.show()

Related

How to convert a matrix to heatmap image in torch [duplicate]

Using Matplotlib, I want to plot a 2D heat map. My data is an n-by-n Numpy array, each with a value between 0 and 1. So for the (i, j) element of this array, I want to plot a square at the (i, j) coordinate in my heat map, whose color is proportional to the element's value in the array.
How can I do this?
The imshow() function with parameters interpolation='nearest' and cmap='hot' should do what you want.
Please review the interpolation parameter details, and see Interpolations for imshow and Image antialiasing.
import matplotlib.pyplot as plt
import numpy as np
a = np.random.random((16, 16))
plt.imshow(a, cmap='hot', interpolation='nearest')
plt.show()
Seaborn is a high-level API for matplotlib, which takes care of a lot of the manual work.
seaborn.heatmap automatically plots a gradient at the side of the chart etc.
import numpy as np
import seaborn as sns
import matplotlib.pylab as plt
uniform_data = np.random.rand(10, 12)
ax = sns.heatmap(uniform_data, linewidth=0.5)
plt.show()
You can even plot upper / lower left / right triangles of square matrices. For example, a correlation matrix, which is square and is symmetric, so plotting all values would be redundant.
corr = np.corrcoef(np.random.randn(10, 200))
mask = np.zeros_like(corr)
mask[np.triu_indices_from(mask)] = True
with sns.axes_style("white"):
ax = sns.heatmap(corr, mask=mask, vmax=.3, square=True, cmap="YlGnBu")
plt.show()
I would use matplotlib's pcolor/pcolormesh function since it allows nonuniform spacing of the data.
Example taken from matplotlib:
import matplotlib.pyplot as plt
import numpy as np
# generate 2 2d grids for the x & y bounds
y, x = np.meshgrid(np.linspace(-3, 3, 100), np.linspace(-3, 3, 100))
z = (1 - x / 2. + x ** 5 + y ** 3) * np.exp(-x ** 2 - y ** 2)
# x and y are bounds, so z should be the value *inside* those bounds.
# Therefore, remove the last value from the z array.
z = z[:-1, :-1]
z_min, z_max = -np.abs(z).max(), np.abs(z).max()
fig, ax = plt.subplots()
c = ax.pcolormesh(x, y, z, cmap='RdBu', vmin=z_min, vmax=z_max)
ax.set_title('pcolormesh')
# set the limits of the plot to the limits of the data
ax.axis([x.min(), x.max(), y.min(), y.max()])
fig.colorbar(c, ax=ax)
plt.show()
For a 2d numpy array, simply use imshow() may help you:
import matplotlib.pyplot as plt
import numpy as np
def heatmap2d(arr: np.ndarray):
plt.imshow(arr, cmap='viridis')
plt.colorbar()
plt.show()
test_array = np.arange(100 * 100).reshape(100, 100)
heatmap2d(test_array)
This code produces a continuous heatmap.
You can choose another built-in colormap from here.
Here's how to do it from a csv:
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import griddata
# Load data from CSV
dat = np.genfromtxt('dat.xyz', delimiter=' ',skip_header=0)
X_dat = dat[:,0]
Y_dat = dat[:,1]
Z_dat = dat[:,2]
# Convert from pandas dataframes to numpy arrays
X, Y, Z, = np.array([]), np.array([]), np.array([])
for i in range(len(X_dat)):
X = np.append(X, X_dat[i])
Y = np.append(Y, Y_dat[i])
Z = np.append(Z, Z_dat[i])
# create x-y points to be used in heatmap
xi = np.linspace(X.min(), X.max(), 1000)
yi = np.linspace(Y.min(), Y.max(), 1000)
# Interpolate for plotting
zi = griddata((X, Y), Z, (xi[None,:], yi[:,None]), method='cubic')
# I control the range of my colorbar by removing data
# outside of my range of interest
zmin = 3
zmax = 12
zi[(zi<zmin) | (zi>zmax)] = None
# Create the contour plot
CS = plt.contourf(xi, yi, zi, 15, cmap=plt.cm.rainbow,
vmax=zmax, vmin=zmin)
plt.colorbar()
plt.show()
where dat.xyz is in the form
x1 y1 z1
x2 y2 z2
...
Use matshow() which is a wrapper around imshow to set useful defaults for displaying a matrix.
a = np.diag(range(15))
plt.matshow(a)
https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.matshow.html
This is just a convenience function wrapping imshow to set useful defaults for displaying a matrix. In particular:
Set origin='upper'.
Set interpolation='nearest'.
Set aspect='equal'.
Ticks are placed to the left and above.
Ticks are formatted to show integer indices.
Here is a new python package to plot complex heatmaps with different kinds of row/columns annotations in Python: https://github.com/DingWB/PyComplexHeatmap

Associating a colormap based on a Nx1 array to a 3D voxel plot

I have a problem very similar to this question. The answer works very well for plotting the voxels. However, I need to find a way to colour the voxels according to a colormap (of type 'jet') which is based on the 5x1 array called "variable". I also need to associate a logarithmic colorbar with that 3D plot.
Thanks in advance!
I found a solution myself. I will post the code here in case somebody has the same problem.
I added two changes to the problem conditions:
The voxels are rectangular prisms of custom dimensions (a,b,c) instead of simple cubes.
Instead of "variable", i defined an array called "Ivec", which has more suitable values ​​for displaying the logarithmic colormap.
If one wants to display a linear colormap, he/she can simply uncomment the line commented as "linear scale colormap" and comment/delete the line commented as "log scale colormap"
import numpy as np
import matplotlib
import matplotlib.cm as cmx
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import pandas as pd
df = pd.DataFrame({"x": [14630, 14630, 14360, 14360, 14360], "y" : [21750, 21770, 21790, 21930, 21950], "z" : [4690, 4690, 4690, 5290, 5270]})
Ivec = np.array([1, 10, 100, 1000, 10000])
def get_cube():
phi = np.arange(1,10,2)*np.pi/4
Phi, Theta = np.meshgrid(phi, phi)
x = np.cos(Phi)*np.sin(Theta)
y = np.sin(Phi)*np.sin(Theta)
z = np.cos(Theta)/np.sqrt(2)
return x,y,z
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
a = 25
b = 8
c = 14
ax.view_init(azim=0, elev=0)
cm = plt.get_cmap('jet')
#cNorm = matplotlib.colors.Normalize(vmin=min(Ivec), vmax=max(Ivec))#linear scale colormap
cNorm = matplotlib.colors.LogNorm(vmin=min(Ivec), vmax=max(Ivec)) #log scale colormap
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=cm)
scalarMap.set_array(Ivec)
fig.colorbar(scalarMap)
cmapRgba=scalarMap.to_rgba(Ivec)
for i in df.index:
x,y,z = get_cube()
# Change the centroid of the cube from zero to values in data frame
x = x*a + df.x[i]
y = y*b + df.y[i]
z = z*c + df.z[i]
ax.plot_surface(x, y, z, color = cmapRgba[i])
ax.set_zlabel("z")
plt.xlabel("x")
plt.ylabel("y")
plt.show()

Plot 4D Contour in Python (X,Y,Z + Data)

I have a large set of measurements that I want to visualize in 4D using matplotlib in Python.
Currently, my variables are arranged in this way:
x = np.array(range(0, v1))
y = np.array(range(0, v2))
z = np.array(range(0, v3))
I have C which is a 3D array containing measurement values for each combination of the previous variables. So it has a dimension of v1*v2*v3.
Currently, I visualize my measurements using contourf function and I plot that for each z value. This results in 3D contour plot i.e. 2D + color map for the values. Now, I want to combine all the variables and look at the measurements in 4D dimensions (x, y, z, and color corresponding to the measurement value). What is the most efficient way to do this in python?
Regarding to #Sameeresque answer, I think the question was about a 4D graph like this (three coordinates x, y, z and a color as the fourth coordinate):
import numpy as np
import matplotlib.pyplot as plt
# only for example, use your grid
z = np.linspace(0, 1, 15)
x = np.linspace(0, 1, 15)
y = np.linspace(0, 1, 15)
X, Y, Z = np.meshgrid(x, y, z)
# Your 4dimension, only for example (use yours)
U = np.exp(-(X/2) ** 2 - (Y/3) ** 2 - Z ** 2)
# Creating figure
fig = plt.figure()
ax = plt.axes(projection="3d")
# Creating plot
ax.scatter3D(X, Y, Z, c=U, alpha=0.7, marker='.')
plt.show()
A 4D plot with (x,y,z) on the axis and the fourth being color can be obtained like so:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = np.array(range(0, 50))
y = np.array(range(0, 50))
z = np.array(range(0, 50))
colors = np.random.standard_normal(len(x))
img = ax.scatter(x, y, z, c=colors, cmap=plt.hot())
fig.colorbar(img)
plt.show()
A simple way to visualize your 4D function, call it W(x, y, z), could be producing a gif of the cross-section contour plots along the z-axis.
Package plot4d could help you do it. An example plotting an isotropic 4D function:
from plot4d import plotter
import numpy as np
plotter.plot4d(lambda x,y,z:x**2+y**2+z**2, np.linspace(0,1,20), wbounds=(0,3), fps=5)
The code above generates this gif:

Get a 2d contour plot from a 3d surface plot

I use matplotlib plot_surface() function and plot a complex function in 3D. Here is my code:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
point_num = 200
x = np.linspace(-5, 3, point_num)
y = np.linspace(-5, 5, point_num)
# Real axis and imaginary axis-----------------------------
Re, Im = np.meshgrid(x, y)
#----------------------------------------------------------
# here is the complex function I need evaluate-------------
z = Re + Im * 1j
R3 = 1 + z + 1/2 * z**2 + 1/6 * pow(z, 3)
#----------------------------------------------------------
# my 3d surface plot----------------------------------------
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.plot_surface(Re, Im, np.abs(R1))
ax.set_xlabel("Im(z)")
ax.set_ylabel("Re(z)")
ax.set_zlabel('R(z)')
plt.show()
#----------------------------------------------------------
I can successfully get the surface plot:
The surface looks like a cone. Now I want to "slice" the cone with plane R(z)=1 and get a 2D contour plot. I can get the contour on the 3D surface like this:
ax.contour(Re, Im, np.abs(R3), [1], colors='r')
Then I get:
I want to plot the red line contour in an independent 2D plot. It should be in the real and imaginary axis. Moreover, can we get the coordinates of the intersection points of the contour line with the axis? Like the figure below:
Thank you very much!
So the issue here is that a plt.Axes() object can't be in the 2D and 3D projections simultaneously. In my version, I have commented out your 3D plots, so that we can focus on the independent 2D plot. It's then only this:
fig = plt.figure()
ax = plt.axes()
ax.contour(Re, Im, np.abs(R3), [1], c='r')
Full version:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
point_num = 200
x = np.linspace(-5, 3, point_num)
y = np.linspace(-5, 5, point_num)
# Real axis and imaginary axis-----------------------------
Re, Im = np.meshgrid(x, y)
#----------------------------------------------------------
# here is the complex function I need evaluate-------------
z = Re + Im * 1j
R3 = 1 + z + 1/2 * z**2 + 1/6 * pow(z, 3)
#----------------------------------------------------------
# my 3d surface plot----------------------------------------
fig = plt.figure()
# ax = plt.axes(projection='3d')
# ax.plot_surface(Re, Im, np.abs(R3))
# ax.set_xlabel("Im(z)")
# ax.set_ylabel("Re(z)")
# ax.set_zlabel('R(z)')
# ax.contour(Re, Im, np.abs(R3), [1], colors='r')
ax = plt.axes()
ax.contour(Re, Im, np.abs(R3), [1], c='r')
plt.show()
#----------------------------------------------------------

Python, Matplotlib: Drawing vertical lines in 3d plot, when data is independent

I have a random walker in the (x,y) plane and a -log(bivariate gaussian) in the (x,y,z) plane. These two datasets are essentially independent.
I want to sample, say 5 (x,y) pairs of the random walker and draw vertical lines up the z-axis and terminate the vertical line when it "meets" the bivariate gaussian.
This is my code so far:
import matplotlib as mpl
import matplotlib.pyplot as plt
import random
import numpy as np
import seaborn as sns
import scipy
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.mlab import bivariate_normal
%matplotlib inline
# Data for random walk
def randomwalk():
mpl.rcParams['legend.fontsize'] = 10
xyz = []
cur = [0, 0]
for _ in range(40):
axis = random.randrange(0, 2)
cur[axis] += random.choice([-1, 1])
xyz.append(cur[:])
# Get density
x, y = zip(*xyz)
data = np.vstack([x,y])
kde = scipy.stats.gaussian_kde(data)
density = kde(data)
# Data for bivariate gaussian
a = np.linspace(-7.5, 7.5, 40)
b = a
X,Y = np.meshgrid(a, b)
Z = bivariate_normal(X, Y)
surprise_Z = -np.log(Z)
# Get random points from walker and plot up z-axis to the gaussian
M = data[:,np.random.choice(20,5)].T
# Plot figure
fig = plt.figure(figsize=(10, 7))
ax = fig.gca(projection='3d')
ax.plot(x, y, 'grey', label='Random walk') # Walker
ax.scatter(x[-1], y[-1], c='k', marker='o') # End point
ax.legend()
surf = ax.plot_surface(X, Y, surprise_Z, rstride=1, cstride=1,
cmap = plt.cm.gist_heat_r, alpha=0.1, linewidth=0.1)
#fig.colorbar(surf, shrink=0.5, aspect=7, cmap=plt.cm.gray_r)
for i in range(5):
ax.plot([M[i,0], M[i,0]],[M[i,1], M[i,1]], [0,10],'k--',alpha=0.8, linewidth=0.5)
ax.set_zlim(0, 50)
ax.set_xlim(-10, 10)
ax.set_ylim(-10, 10)
Which produces
As you can see the only thing I'm struggling with is how to terminate the vertical lines when they meet the appropriate Z-value. Any ideas are welcome!
You're currently only letting those lines get to a height of 10 by using [0,10] as the z coordinates. You can change your loop to the following:
for i in range(5):
x = [M[i,0], M[i,0]]
y = [M[i,1], M[i,1]]
z = [0,-np.log(bivariate_normal(M[i,0],M[i,1]))]
ax.plot(x,y,z,'k--',alpha=0.8, linewidth=0.5)
This takes the x and y coordinates for each point you loop over and calculates the height of overlying Gaussian for that point and plots to there. Here is a plot with the linestyle changed to emphasize the lines relevant to the question:

Categories