I have a 3D polygon plot and want to smooth the plot on the y axis (i.e. I want it to look like 'slices of a surface plot').
Consider this MWE (taken from here):
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.collections import PolyCollection
import matplotlib.pyplot as plt
from matplotlib import colors as mcolors
import numpy as np
from scipy.stats import norm
fig = plt.figure()
ax = fig.gca(projection='3d')
xs = np.arange(-10, 10, 2)
verts = []
zs = [0.0, 1.0, 2.0, 3.0]
for z in zs:
ys = np.random.rand(len(xs))
ys[0], ys[-1] = 0, 0
verts.append(list(zip(xs, ys)))
poly = PolyCollection(verts, facecolors=[mcolors.to_rgba('r', alpha=0.6),
mcolors.to_rgba('g', alpha=0.6),
mcolors.to_rgba('b', alpha=0.6),
mcolors.to_rgba('y', alpha=0.6)])
poly.set_alpha(0.7)
ax.add_collection3d(poly, zs=zs, zdir='y')
ax.set_xlabel('X')
ax.set_xlim3d(-10, 10)
ax.set_ylabel('Y')
ax.set_ylim3d(-1, 4)
ax.set_zlabel('Z')
ax.set_zlim3d(0, 1)
plt.show()
Now, I want to replace the four plots with normal distributions (to ideally form continuous lines).
I have created the distributions here:
def get_xs(lwr_bound = -4, upr_bound = 4, n = 80):
""" generates the x space betwee lwr_bound and upr_bound so that it has n intermediary steps """
xs = np.arange(lwr_bound, upr_bound, (upr_bound - lwr_bound) / n) # x space -- number of points on l/r dimension
return(xs)
xs = get_xs()
dists = [1, 2, 3, 4]
def get_distribution_params(list_):
""" generates the distribution parameters (mu and sigma) for len(list_) distributions"""
mus = []
sigmas = []
for i in range(len(dists)):
mus.append(round((i + 1) + 0.1 * np.random.randint(0,10), 3))
sigmas.append(round((i + 1) * .01 * np.random.randint(0,10), 3))
return mus, sigmas
mus, sigmas = get_distribution_params(dists)
def get_distributions(list_, xs, mus, sigmas):
""" generates len(list_) normal distributions, with different mu and sigma values """
distributions = [] # distributions
for i in range(len(list_)):
x_ = xs
z_ = norm.pdf(xs, loc = mus[i], scale = sigmas[0])
distributions.append(list(zip(x_, z_)))
#print(x_[60], z_[60])
return distributions
distributions = get_distributions(list_ = dists, xs = xs, mus = mus, sigmas = sigmas)
But adding them to the code (with poly = PolyCollection(distributions, ...) and ax.add_collection3d(poly, zs=distributions, zdir='z') throws a ValueError (ValueError: input operand has more dimensions than allowed by the axis remapping) I cannot resolve.
The error is caused by passing
distributionstozswherezsexpects that whenvertsinPolyCollectionhas shape MxNx2 the object passed tozshas shape M. So when it reaches this checkin the underlying numpy code, it fails. I believe this occurs because the number of dimensions expected (
array.ndim) is less than the number of dimensions ofzs(len(shape)). It is expecting an array of shape(4,)but receives an array of shape(4, 80, 2).This error can be resolved by using an array of the correct shape - e.g.
zsfrom the original example ordistsfrom your code. Usingzs=distsand adjusting the axis limits to[0,5]forx,y, andzgivesThis looks a bit odd for two reasons:
z_ = norm.pdf(xs, loc = mus[i], scale = sigmas[0])which gives all the distributions the same sigma, it should bez_ = norm.pdf(xs, loc = mus[i], scale = sigmas[i])xzplane as their base, this is also the plane we are looking through.Changing the viewing geometry via
ax.view_initwill yield a clearer plot:Edit
Here is the complete code which generates the plot shown,
I based it off the code you provide in the question, but made some modifications for brevity and to be more consistent with the usual styling.
Note - The example code you have given will fail depending on the
np.random.seed(), in order to ensure it works I have added a check in the call tonorm.pdfwhich ensures the scale is non-zero:scale = sigma[i] if sigma[i] != 0.0 else 0.1.