Problem with 3D wireframe visualization in matplotlib - python

I need to plot 3D data of the form z_i as function of (x_i, y_i) using a wireframe. I wrote the code below:
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import axes3d
import scipy.interpolate as spint
## Data to plot
sim_data = np.array([[ 20, 1, 8],
[ 20, 2, 7],
[ 20, 4, 7],
[ 20, 6, 6],
[ 20, 10, 6],
[ 50, 0.4, 15],
[ 50, 0.8, 11],
[ 50, 1, 10],
[ 50, 2, 8],
[ 50, 4, 7],
[ 50, 6, 7],
[ 50, 10, 7],
[100, 0.4, 22],
[100, 0.8, 15],
[100, 1, 13],
[100, 2, 10],
[100, 4, 8],
[100, 6, 7],
[100, 10, 7]])
x = sim_data[:, 0]
y = sim_data[:, 1]
z = sim_data[:, 2]
# Do trisurf plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_trisurf(x, y, z)
ax.set_xlabel('Air flow')
ax.set_ylabel('Fuel rate')
ax.set_zlabel('Temp.')
ax.text2D(0.05, 0.95, "Trisurf plot", transform=ax.transAxes)
# Transform from vector to grid
X, Y = np.meshgrid(x, y)
xi = (X, Y)
Z = spint.griddata((x,y), z, xi)
# Do wireframe plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(X, Y, Z)
ax.set_xlabel('Air flow')
ax.set_ylabel('Fuel rate')
ax.set_zlabel('Temp.')
ax.text2D(0.05, 0.95, "Wireframe plot", transform=ax.transAxes)
# Do surface plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z)
ax.set_xlabel('Air flow')
ax.set_ylabel('Fuel rate')
ax.set_zlabel('Temp.')
ax.text2D(0.05, 0.95, "Surface plot", transform=ax.transAxes)
But I get some annoying extra wires (marked with a red arrow):
How can I get rid of this arrow? I have the same problem while trying a surface plot by the way:
My goal is to have a plot similar to a trisurf plot like the one below, but with a wireframe visualization.
Many thanks in advance.

What's wrong with wireframe?
I am not sure, but I think the problem is in your data. It's small and if you look carefully you will see that it looks like a stack of three different lines (observations). Look at this plot:
it's definitely there are three parallel lines over there. I suppose, that's might cause confusion with plot_wireframe, as well as with plot from the previous image. I see 3 possible solutions:
Solution 1: Use plot
Ok, so the first solution is not to use plot_wireframe at all. Let's use good old one plot to build our own wires. But first, let's break our data into 3 lines data:
line1 = sim_data[0:5][::-1] # NOTE: the first line is shorter
line2 = sim_data[5:12][::-1]
line3 = sim_data[12:][::-1]
Plot them all!
# a helper function
def prepare_fig(fw=7, fh=7, view = (25, 30)):
fig = plt.figure(figsize=(fw, fh))
ax = fig.add_subplot(111, projection='3d')
ax.view_init(view[0], view[-1])
return ax
ax = prepare_fig()
ax.title.set_text('3 Lines')
for line in [line1, line2, line3]:
x, y, z = line[:, 0], line[:, 1], line[:, 2]
ax.plot(x, y, z, c='tab:blue', linewidth=3)
Ok, we fixed undesired links, now let's add parallel links (lines) to connect our main lines:
ax = prepare_fig()
ax.title.set_text('Paralel links')
for i in range(len(line3)):
x, y, z = [], [], []
if i < len(line1):
x.append(line1[:, 0][i])
y.append(line1[:, 1][i])
z.append(line1[:, 2][i])
else:
# line1 is shorter so we will put nan here (for now)
x.append(np.nan)
y.append(np.nan)
z.append(np.nan)
x.extend([line2[:, 0][i], line3[:, 0][i]])
y.extend([line2[:, 1][i], line3[:, 1][i]])
z.extend([line2[:, 2][i], line3[:, 2][i]])
ax.plot(x, y, z, c='tab:blue', linewidth=3)
Now all in one:
Final Code:
ax = prepare_fig()
ax.title.set_text('Handmade Wireframe (enclosed)')
line1 = sim_data[0:5][::-1]
line2 = sim_data[5:12][::-1]
line3 = sim_data[12:][::-1]
for line in [line1, line2, line3]:
x, y, z = line[:, 0], line[:, 1], line[:, 2]
ax.plot(x, y, z, c='tab:blue', linewidth=3)
for i in range(len(line3)):
x, y, z = [], [], []
if i < len(line1):
x.append(line1[:, 0][i])
y.append(line1[:, 1][i])
z.append(line1[:, 2][i])
else:
# put nan because line1 is shorter
# x.append(np.nan)
# y.append(np.nan)
# z.append(np.nan)
# Or you can just replace it with last line1 value
x.append(line1[:, 0][-1])
y.append(line1[:, 1][-1])
z.append(line1[:, 2][-1])
x.extend([line2[:, 0][i], line3[:, 0][i]])
y.extend([line2[:, 1][i], line3[:, 1][i]])
z.extend([line2[:, 2][i], line3[:, 2][i]])
ax.plot(x, y, z, c='tab:blue', linewidth=3)
Solution 2: Use plot_trisurf.
If triangles are acceptable, another solution is to transform trisurf to wireframe-like by some tweaking.
x = sim_data[:, 0]
y = sim_data[:, 1]
z = sim_data[:, 2]
ax = prepare_fig()
ax.title.set_text('Trisurf Wireframe')
trisurf = ax.plot_trisurf(x, y, z)
# turn of surface color, you can control it with alpha here:
trisurf.set_facecolor(mpl.colors.colorConverter.to_rgba('w', alpha=0.0))
# setting wire color
trisurf.set_edgecolor('tab:blue')
#setting wire width
trisurf.set_linewidth(3)
Solution 3: Use plot_wireframe and interpolation at linspace grid.
This might be solution if you want good looking smooth surface. You just need to generate new grid and then using scipy's spint.griddata to perform interpolation:
import scipy.interpolate as spint
x = sim_data[:, 0]
y = sim_data[:, 1]
z = sim_data[:, 2]
# generate new linear grid based on previous
X, Y = np.meshgrid(np.linspace(min(x), max(x), len(x)),
np.linspace(min(y), max(y), len(y)))
Z = spint.griddata((x, y), z, (X, Y))
ax = prepare_fig()
ax.title.set_text('Interpotation on Linspace Grid')
# ax.plot_wireframe(X, Y, Z, rstride=3, cstride=3)
ax.plot_surface(X, Y, Z, rstride=3, cstride=3)
And you will get something like this:

Related

Confused about plotting interpolated 2D data with matplotlib

I have some unstructured 2D data that I would like to interpolate on a unit offset grid (ie grid indices start at 1 not 0) using scipy and plot using matplotlib. The code is below
import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate
# X Y Z
data = [[ 9, 2, 2.0],
[ 3, 3, 5.0],
[ 6, 4, 1.0],
[ 2, 6, 3.0],
[10, 7, 4.5],
[ 5, 8, 2.0]]
data = np.array(data)
coords = data[:, 0:2]
zvals = data[:, 2]
# Create the grid on which to interpolate (unit offset)
nx = 10
ny = 10
x = np.arange(nx)
x += 1
y = np.arange(ny)
y += 1
grid_x, grid_y = np.meshgrid(x, y, indexing='xy')
# Interpolate
grid_z1 = scipy.interpolate.griddata(coords, zvals, (grid_x, grid_y), method='linear')
# Plot the results
fig, axs = plt.subplots()
plt.imshow(grid_z1)
plt.plot(coords[:,0], coords[:,1], 'k.', ms=10)
plt.show()
The point data seem to be in the right place but matplotlib seems to be plotting the gridded data as zero-offset not unit-offset. I am obviously missing something - just not sure what. Thanks in advance!
Update
fig, axs = plt.subplots()
plt.imshow(grid_z1, origin='lower', extent=[1, 10, 1, 10])
plt.plot(coords[:,0], coords[:,1], 'k.', ms=10)
plt.show()
IIUC, you want to define xlim and ylim equal to the plot and not the heatmap:
plt.plot(coords[:,0], coords[:,1], 'k.', ms=10)
ax = plt.gca()
xlim, ylim = ax.get_xlim(), ax.get_ylim()
plt.imshow(grid_z1)
ax.set_xlim(xlim)
ax.set_ylim(ylim)
Output:

Pyplot 3D scatter z axis issue

Is there something wrong with the 3d plt.scatter(x,y,z) method?
Plots all z values at zero:
x = [1, 1]
y = [1, 1]
z = [-10, 10]
fig = plt.figure(figsize=(16, 18))
plt.axes(projection ="3d")
plt.scatter(x, y, z, color='k')
plt.show()
Working correctly:
x = [1, 1]
y = [1, 1]
z = [-10, 10]
fig = plt.figure(figsize=(16, 18))
ax = plt.axes(projection ="3d")
ax.scatter(x, y, z, color='k')
plt.show()
In your above examples you used the two matplotlib's interfaces: pyplot vs object oriented.
If you'll look at the source code of pyplot.scatter you'll see that even if you are going to provide 3 arguments plt.scatter(x, y, z, color='k'), it is actually going to call the 2D version, with x, y, s=z, s being the marker size.
So, it appears that you have to use the object oriented approach to achieve your goals.

3d matplotlib zaxis font

using this code :
data=np.genfromtxt('jpdfomegal2_90.dat')
x_len= len(np.unique(data[:, 0]))
y_len= len(np.unique(data[:, 1]))
#reshape X, Y, and Z into 2D arrays
X = data[:, 0].reshape(x_len, y_len)
Y = data[:, 1].reshape(x_len, y_len)
Z = data[:, 2].reshape(x_len, y_len)
Zmin = np.where(Z > 0, Z, np.inf).min()
Zmax = Z.max()
Z[Z==0] = 0.9 * Zmin
Zlog = np.log10(Z)
fig = plt.figure(figsize=(12,8))
ax = fig.add_subplot(projection='3d')
rc('font',family='palatino')
rc('font',size=14)
ax.set_xlim3d(0,15)
ax.set_zlim3d(np.floor(np.log10(Zmin))-2, np.ceil(np.log10(10))) #,font:'palatino')
ax.zaxis.set_major_formatter(mticker.FuncFormatter(log_tick_formatter))
ax.zaxis.set_major_locator(mticker.MaxNLocator(integer=True))
ax.contour(X, Y, np.log10(Z), 4, lw=0.1, colors="k", linestyles="--", offset=np.floor(np.log10(Zmin))-2)
ax.plot_surface(X, Y, np.log10(Z), cmap="binary", lw=0.1,alpha=0.5)
ax.plot_wireframe(X, Y, np.log10(Z),linewidth=0.5,color='k')
ax.contour(X, Y, np.log10(Z), 4, lw=0.1, colors="k", linestyles="solid")
ax.view_init(elev=17, azim=-60)
for spine in ax.spines.values():
spine.set_visible(False)
plt.tight_layout()
plt.savefig('jpdf_lambda2_90.pdf', bbox_inches='tight')
plt.show()
I obtain a palatino font in x,y axis numerations but not in Z like. in the piucture :
how can obtain the same font (everywhere) ?
and how can I define the z-axis to obtain sub-thicks like in the classical logarithmic scale? thanks

How to have 2 different scales on same Y axis in Python using Matplotlib

I need to draw 4 X vs Y plots, where X is constant but different Y Values. I used below code to get the plots but need to show the Y scale on either side of the Secondary Y axes (Y Axis 2 in the image), the way Primary Y Axis has (both inward and outward). Right now, it comes on same side of Secondary Y Axis. How to modify the below code to get this done.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
fig.subplots_adjust(left=0.1,right=0.9)
twin2 = ax.twinx()
twin3 = ax.twinx()
twin4 = ax.twinx()
twin3.spines['right'].set_position(("axes", 0.0))
twin4.spines['right'].set_position(('axes', 1.0))
p1, = ax.plot([0, 1, 2], [0, 1, 2], "b-", label="plot1")
p2, = twin2.plot([0, 1, 2], [0, 3, 2], "r-", label="plot2")
p3, = twin3.plot([0, 1, 2], [50, 30, 15], "g-", label="plot3")
p4, = twin4.plot([0, 1, 2], [5, 3, 1], "y-", label="plot4")
ax.set_xlim(0, 2)
ax.set_ylim(0, 2)
ax.set_xlabel("X Axis")
ax.set_ylabel("Y Axis")
twin2.set_ylabel("Y Axis 2")
plt.show()
On your 4th axes, set tick_left and move the left spine to the right-hand side:
twin4.yaxis.tick_left()
twin4.spines['left'].set_position(('axes', 1.0))

Wrong scatter label color in pyplot legend

I am making this bar plot:
... using this code segment:
my_cmap = plt.get_cmap('copper')
plt.figure()
plt.set_cmap(my_cmap)
plt.pcolormesh(xx, yy, Z)
labels = ['Negative', 'Negative (doubtful)', 'Positive (doubtful)', 'Positive' ]
for i in [0, 1, 2, 3] :
plt.scatter(clustered_training_data[y==i, 0], clustered_training_data[y==i, 1], c=my_cmap(i / 3.0), label=labels[i], s=50, marker='o', edgecolor='white', alpha=0.7)
plt.scatter(lda_trans_eval[q == -1, 0], lda_trans_eval[q == -1, 1], c='green', label='Your patient', s=80, marker='h', edgecolor='white')
plt.legend(prop={'size':8})
Only one (second) color is always blue, regardless of chosen color map. Corresponding data points are correctly colored in the plot and I can't see the reason why pyplot colors the second label differently.
I can't reproduce it with dummy data. Does this have the problem when you run it?
import matplotlib.pyplot as plt
import numpy as np
my_cmap = plt.get_cmap('copper')
fig = plt.figure(figsize=(5,5))
plt.set_cmap(my_cmap)
X = np.linspace(-1,5,100)
Y = np.linspace(-1,5,100)
X,Y = np.meshgrid(X,Y)
Z = (X**2 + Y**2)
Z = Z.astype(int)
Z += (X**2 + Y**2) < .5
ax = plt.pcolormesh(X, Y, Z)
for i in [0,1,2,3]:
plt.scatter([i],[i],c=my_cmap(i / 3.0),label='i=%s'%str(i),
edgecolor='white', alpha=0.7)
plt.scatter([],[],c=my_cmap(1/3.0), label='empty data')
plt.scatter([3],[1],c='green',label='Force color')
plt.legend(loc=2, prop={'size':8})
from os.path import realpath, basename
s = basename(realpath(__file__))
fig.savefig(s.split('.')[0])
plt.show()
This happened to me. I fixed it by using color instead of c.
plt.scatter(clustered_training_data[y==i, 0], clustered_training_data[y==i, 1], color=my_cmap(i / 3.0), label=labels[i], s=50, marker='o', edgecolor='white', alpha=0.7)

Categories

Resources