Adding errorbars to 3D plot in matplotlib - python

I can't find a way to draw errorbars in a 3D scatter plot in matplotlib.
Basically, for the following piece of code
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X, Y, Z = axes3d.get_test_data(1)
ax.scatter(X, Y, zs = Z, zdir = 'z')
I am looking for something like
ax.errorbar(X,Y, zs = Z, dY, dX, zserr = dZ)
Is there a way to do this in mplot3d? If not, are there other libraries with this function?

There is clearly example on forum http://mple.m-artwork.eu/home/posts/simple3dplotwith3derrorbars
Here is the code but is not built-in functionality:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as axes3d
fig = plt.figure(dpi=100)
ax = fig.add_subplot(111, projection='3d')
#data
fx = [0.673574075,0.727952994,0.6746285]
fy = [0.331657721,0.447817839,0.37733386]
fz = [18.13629648,8.620699842,9.807536512]
#error data
xerror = [0.041504064,0.02402152,0.059383144]
yerror = [0.015649804,0.12643117,0.068676131]
zerror = [3.677693713,1.345712547,0.724095592]
#plot points
ax.plot(fx, fy, fz, linestyle="None", marker="o")
#plot errorbars
for i in np.arange(0, len(fx)):
ax.plot([fx[i]+xerror[i], fx[i]-xerror[i]], [fy[i], fy[i]], [fz[i], fz[i]], marker="_")
ax.plot([fx[i], fx[i]], [fy[i]+yerror[i], fy[i]-yerror[i]], [fz[i], fz[i]], marker="_")
ax.plot([fx[i], fx[i]], [fy[i], fy[i]], [fz[i]+zerror[i], fz[i]-zerror[i]], marker="_")
#configure axes
ax.set_xlim3d(0.55, 0.8)
ax.set_ylim3d(0.2, 0.5)
ax.set_zlim3d(8, 19)
plt.show()

I ended up writing the method for matplotlib: official example for 3D errorbars:
import matplotlib.pyplot as plt
import numpy as np
ax = plt.figure().add_subplot(projection='3d')
# setting up a parametric curve
t = np.arange(0, 2*np.pi+.1, 0.01)
x, y, z = np.sin(t), np.cos(3*t), np.sin(5*t)
estep = 15
i = np.arange(t.size)
zuplims = (i % estep == 0) & (i // estep % 3 == 0)
zlolims = (i % estep == 0) & (i // estep % 3 == 2)
ax.errorbar(x, y, z, 0.2, zuplims=zuplims, zlolims=zlolims, errorevery=estep)
ax.set_xlabel("X label")
ax.set_ylabel("Y label")
ax.set_zlabel("Z label")
plt.show()

Related

Matplotlib plot contourf on 3d surface

I am trying to use the colormap feature of a 3d-surface plot in matplotlib to color the surface based on values from another array instead of the z-values.
The surface plot is created and displayed as follows:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def gauss(x, y, w_0):
r = np.sqrt(x**2 + y**2)
return np.exp(-2*r**2 / w_0**2)
x = np.linspace(-100, 100, 100)
y = np.linspace(-100, 100, 100)
X, Y = np.meshgrid(x, y)
Z = gauss(X, Y, 50)
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.plot_surface(X, Y, Z, cmap='jet')
Now instead of coloring based on elevation of the 3d-surface, I am looking to supply the color data for the surface in form of another array, here as an example a random one:
color_data = np.random.uniform(0, 1, size=(Z.shape))
However, I did not find a solution to colorize the 3d-surface based on those values. Ideally, it would look like a contourf plot in 3d, just on the 3d surface.
You can use matplotlib.colors.from_levels_and_colors to obtain a colormap and normalization, then apply those to the values to be colormapped.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.colors
x = np.linspace(-100, 100, 101)
y = np.linspace(-100, 100, 101)
X, Y = np.meshgrid(x, y)
Z = np.exp(-2*np.sqrt(X**2 + Y**2)**2 / 50**2)
c = X+50*np.cos(Y/20) # values to be colormapped
N = 11 # Number of level (edges)
levels = np.linspace(-150,150,N)
colors = plt.cm.get_cmap("RdYlGn", N-1)(np.arange(N-1))
cmap, norm = matplotlib.colors.from_levels_and_colors(levels, colors)
color_vals = cmap(norm(c))
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.plot_surface(X, Y, Z, facecolors=color_vals, rstride=1, cstride=1)
plt.show()

I am plotting a 3d plot and i want the colours to be less 'distinct'

My code can be seen here:
import numpy as np
from matplotlib import cm
import mpl_toolkits.mplot3d.axes3d as axes3d
from matplotlib.ticker import LinearLocator, FormatStrFormatter
xlist = [+30,+20,+10,0,-10,-20,-30]
ylist = [0.0008,0.0009, 0.001, 0.0012, 0.0013]
total_costs=[[2084.8771849999903, 17314.19051000003, 26026.73173, 65340.709810000015, 108130.0746, 143560.64033000002, 188387.24033], [2129.155209999997, 17314.301310000024, 26026.996729999984, 65341.17821, 108130.792, 143561.44293000002, 188388.11793], [6637.1766100000095, 17314.412110000034, 26027.26173, 65341.646609999996, 108131.5094, 143562.24553000001, 188388.99553], [6623.21941000002, 17314.63371000004, 26027.791729999997, 65342.58341000001, 108132.9442, 150322.81264000002, 191661.16901], [6637.240810000003, 17314.744510000033, 26028.05673000001, 65343.05181000002, 110971.15911000001, 146393.01711000002, 191661.93621]]
Z = np.array(total_costs)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
X, Y = np.meshgrid(xlist, ylist)
ax.plot_surface(X, Y, Z, cmap=cm.coolwarm,
linewidth=0, antialiased=False,alpha=0.5,
`rstride=1,cstride=1, label='skata')`
ax.set_xlabel('System-1 imbalance')
ax.set_ylabel('Penalization factor [€/MWh]')
ax.set_zlabel('Total balancing costs [€]')
#ax.set_legend('upper left', fontsize=15)
#ax.tick_params(axis='both', labelsize=15)
plt.show()
When i run this i get a figure like this:
What i would like is to get a figure like this:
I guess it has something to do with my result being a list within a list with discrete values. Anyone got an idea?
Thank you in advance
I guess you want a more gradual tone change on the graph - the way I know how to do it is to "simply" increase the number of points being plotted using interpolation:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d
import scipy.interpolate as interp
xlist = np.array([+30, +20, +10, 0, -10, -20, -30])
ylist = np.array([0.0008, 0.0009, 0.001, 0.0012, 0.0013])
total_costs = [[2084.8771849999903, 17314.19051000003, 26026.73173,
65340.709810000015, 108130.0746, 143560.64033000002,
188387.24033],
[2129.155209999997, 17314.301310000024, 26026.996729999984,
65341.17821, 108130.792, 143561.44293000002, 188388.11793],
[6637.1766100000095, 17314.412110000034, 26027.26173,
65341.646609999996, 108131.5094, 143562.24553000001,
188388.99553],
[6623.21941000002, 17314.63371000004, 26027.791729999997,
65342.58341000001, 108132.9442, 150322.81264000002,
191661.16901],
[6637.240810000003, 17314.744510000033, 26028.05673000001,
65343.05181000002, 110971.15911000001, 146393.01711000002,
191661.93621]]
X, Y = np.meshgrid(xlist, ylist)
Z = np.asarray(total_costs)
Zfunc = interp.interp2d(X, Y, Z, kind='cubic', copy=False)
n_points = 100 # change this to change the "resolution"
xnew = np.linspace(start=min(xlist), stop=max(xlist), num=n_points)
ynew = np.linspace(start=min(ylist), stop=max(ylist), num=n_points)
Xnew, Ynew = np.meshgrid(xnew, ynew)
Znew = Zfunc(xnew, ynew)
fig = plt.figure(figsize=(11, 8))
ax = plt.axes([0.05, 0.05, 0.9, 0.9], projection='3d')
surface = ax.plot_surface(Xnew, Ynew, Znew, rstride=1, cstride=1,
cmap='coolwarm', linewidth=0.25)
fig.colorbar(surface, shrink=0.75, aspect=9)
plt.show()
Linear interpolation:
Cubic interpolation:
The faces of the surface plot are colorized according to the Z value.
To get mixed or random colors on the faces you can supply a color array with the facecolors argument instead of a colormap.
colors=np.random.rand(X.shape[0]-1,X.shape[1]-1, 3)
ax.plot_surface(X, Y, Z, facecolors=colors,
linewidth=0, antialiased=False,alpha=0.5,
rstride=1,cstride=1, label='skata')
produces
In order to make the colors appear more close to each other, the solution would be not to use the complete range of the colormap. E.g. you could set vmin=0.5*Z.min(), vmax=2*Z.max(), in your call to plot_surface in order to map the colors to a range much larger than the one shown in the image, such that the actual values only cover part of the colormap.
ax.plot_surface(X, Y, Z, cmap=cm.coolwarm, vmin=0.5*Z.min(), vmax=2*Z.max(),
linewidth=0, antialiased=False,alpha=0.5,
rstride=1,cstride=1, label='skata')
Is that what you mean?
def stackQuestion():
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d
xlist = np.array([+30,+20,+10,0,-10,-20,-30])
ylist = np.array([0.0008,0.0009, 0.001, 0.0012, 0.0013])
total_costs=[[2084.8771849999903, 17314.19051000003, 26026.73173, 65340.709810000015, 108130.0746, 143560.64033000002, 188387.24033],
[2129.155209999997, 17314.301310000024, 26026.996729999984, 65341.17821, 108130.792, 143561.44293000002, 188388.11793],
[6637.1766100000095, 17314.412110000034, 26027.26173, 65341.646609999996, 108131.5094, 143562.24553000001, 188388.99553],
[6623.21941000002, 17314.63371000004, 26027.791729999997, 65342.58341000001, 108132.9442, 150322.81264000002, 191661.16901],
[6637.240810000003, 17314.744510000033, 26028.05673000001, 65343.05181000002, 110971.15911000001, 146393.01711000002, 191661.93621]]
X, Y = np.meshgrid(xlist, ylist)
Z = np.array(total_costs)
fig = plt.figure(figsize = (11, 8))
ax = plt.axes([0.05, 0.05, 0.9, 0.9], projection = '3d')
surface = ax.plot_surface(X, Y, Z, rstride = 1, cstride = 1,\
cmap = 'coolwarm', linewidth = 0.25)
fig.colorbar(surface, shrink = 0.75, aspect = 9)
plt.show()

Matplotlib animation save does not create animation for surface plots

I am trying to use matplotlib.animation to animate the time evolution of a surface. A working example is found on this stackexchange question/answer. Using plt.show() I can see the animation fine. The problem is when I try to save it. When saving as either a gif or mp4 I get only one from of the animation. I do not get this problem if I am doing 1d animations, for example using plt.plot(). Below is what I am trying:
from mpl_toolkits.mplot3d import axes3d
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import cm
def generate(X, Y, phi):
R = 1 - np.sqrt(X**2 + Y**2)
return np.cos(2 * np.pi * X + phi) * R
fig = plt.figure()
ax = axes3d.Axes3D(fig)
xs = np.linspace(-1, 1, 50)
ys = np.linspace(-1, 1, 50)
X, Y = np.meshgrid(xs, ys)
Z = generate(X, Y, 0.0)
wframe = ax.plot_surface(X, Y, Z, rstride=2, cstride=2, cmap=cm.coolwarm )
ax.set_zlim(-1,1)
def update(i, ax, fig):
ax.cla()
phi = i * 360 / 2 / np.pi / 100
Z = generate(X, Y, phi)
wframe = ax.plot_surface( X, Y, Z, rstride=2,
cstride=2, cmap=cm.coolwarm )
ax.set_zlim(-1,1)
return wframe,
ani = animation.FuncAnimation( fig, update, frames=10,
fargs=(ax, fig), interval=100 )
ani.save('plottest3d2.mp4', fps=30)
ani.save('plottest3d3.gif', fps=30, writer='imagemagick')
plt.show()
Any help explaining the discrepancy between showing the plot and saving would be great.

Contourf on the faces of a Matplotlib cube

I am trying to 'paint' the faces of a cube with a contourf function using Python Matplotlib. Is this possible?
This is similar idea to what was done here but obviously I cannot use patches. Similarly, I don't think I can use add_collection3d like this as it only supports PolyCollection, LineColleciton and PatchCollection.
I have been trying to use contourf on a fig.gca(projection='3d'). Toy example below.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
plt.close('all')
fig = plt.figure()
ax = fig.gca(projection='3d')
############################################
# plotting the 'top' layer works okay... #
############################################
X = np.linspace(-5, 5, 43)
Y = np.linspace(-5, 5, 28)
X, Y = np.meshgrid(X, Y)
varone=np.random.rand(75,28,43)
Z=varone[0,:,:]
cset = ax.contourf(X, Y, Z, zdir='z', offset=1,
levels=np.linspace(np.min(Z),np.max(Z),30),cmap='jet')
#see [1]
plt.show()
#################################################
# but now trying to plot a vertical slice.... #
#################################################
plt.close('all')
fig = plt.figure()
ax = fig.gca(projection='3d')
Z=varone[::-1,:,-1]
X = np.linspace(-5, 5, 28)
Y = np.linspace(-5, 5, 75)
X, Y = np.meshgrid(X, Y)
#this 'projection' doesn't result in what I want, I really just want to rotate it
cset = ax.contourf(X, Y, Z, offset=5,zdir='x',
levels=np.linspace(np.min(Z),np.max(Z),30),cmap='jet')
#here's what it should look like....
ax=fig.add_subplot(1, 2,1)
cs1=ax.contourf(X,Y,Z,levels=np.linspace(np.min(Z),np.max(Z),30),cmap='jet')
#see [2]
plt.show()
1 From the example, the top surface comes easily:
2 But I'm not sure how to do the sides. Left side of this plot is what the section should look like (but rotated)...
Open to other python approaches. The data I'm actually plotting are geophysical netcdf files.
You have to assign the data to the right axis. The zig-zag results from the fact that now you are at x = const and have your oscillation in the z-direction (from the random data, which is generated between 0 and 1).
If you you assign the matrixes differently in your example, you end up with the desired result:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
plt.close('all')
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.linspace(-5, 5, 43)
Y = np.linspace(-5, 5, 28)
X, Y = np.meshgrid(X, Y)
varone=np.random.rand(75,28,43) * 5.0 - 10.0
Z=varone[0,:,:]
cset = [[],[],[]]
# this is the example that worked for you:
cset[0] = ax.contourf(X, Y, Z, zdir='z', offset=5,
levels=np.linspace(np.min(Z),np.max(Z),30),cmap='jet')
# now, for the x-constant face, assign the contour to the x-plot-variable:
cset[1] = ax.contourf(Z, Y, X, zdir='x', offset=5,
levels=np.linspace(np.min(Z),np.max(Z),30),cmap='jet')
# likewise, for the y-constant face, assign the contour to the y-plot-variable:
cset[2] = ax.contourf(X, Z, Y, zdir='y', offset=-5,
levels=np.linspace(np.min(Z),np.max(Z),30),cmap='jet')
# setting 3D-axis-limits:
ax.set_xlim3d(-5,5)
ax.set_ylim3d(-5,5)
ax.set_zlim3d(-5,5)
plt.show()
The result looks like this:
The answer given below is not fully satisfying. Indeed, planes in x, y and z direction reproduce the same field.
Hereafter, a function that allows to represent the correct field in each of the planes.
import numpy as np
import matplotlib.pyplot as plt
def plot_cube_faces(arr, ax):
"""
External faces representation of a 3D array with matplotlib
Parameters
----------
arr: numpy.ndarray()
3D array to handle
ax: Axes3D object
Axis to work with
"""
x0 = np.arange(arr.shape[0])
y0 = np.arange(arr.shape[1])
z0 = np.arange(arr.shape[2])
x, y, z = np.meshgrid(x0, y0, z0)
xmax, ymax, zmax = max(x0), max(y0), max(z0)
vmin, vmax = np.min(arr), np.max(arr)
ax.contourf(x[:, :, 0], y[:, :, 0], arr[:, :, -1].T,
zdir='z', offset=zmax, vmin=vmin, vmax=vmax)
ax.contourf(x[0, :, :].T, arr[:, 0, :].T, z[0, :, :].T,
zdir='y', offset=0, vmin=vmin, vmax=vmax)
ax.contourf(arr[-1, :, :].T, y[:, 0, :].T, z[:, 0, :].T,
zdir='x', offset=xmax, vmin=vmin, vmax=vmax)
x0 = np.arange(30)
y0 = np.arange(20)
z0 = np.arange(10)
x, y, z = np.meshgrid(x0, y0, z0)
arr = (x + y + z) // 10
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
plot_cube_faces(arr, ax)
plt.show()

Animate a segment in a 3d space

I got a .dat file which contains the coordinates of a segment in 3d space.
The file has several lines, each single line characterizes the position at a particular time.
I tried this code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.mplot3d import Axes3D
dati = np.loadtxt('dati.dat')
t=0
p1=[dati[t,1],dati[t,2],dati[t,3]]
p2=[dati[t,4],dati[t,5],dati[t,6]]
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
seg,=ax.plot(p1,p2)
def updateFigure(t,dati,seg):
p1=[dati[t,1],dati[t,2],dati[t,3]]
p2=[dati[t,4],dati[t,5],dati[t,6]]
seg.set_data(p1,p2)
return seg,
ani=animation.FuncAnimation(fig, updateFigure,iMax, fargs=(dati,seg), interval=100, blit=True)
plt.show()
The program doesn't report errors but the figure doesn't move.
The same code, a bit modified, in the 2d space works..
Instead of calling set_data, you could set seg._verts3d directly, though note that manipulating the private variable _verts3d is relying on an implementation detail, not part of the Line3D public interface:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.mplot3d import Axes3D
iMax = N = 500
theta = np.linspace(0, 6*np.pi, N)
x = np.cos(theta)
y = np.sin(theta)
z = np.linspace(0, 1, N)
step = 10
dati = np.column_stack(
[theta, x, np.roll(x, -step), np.roll(x, -2*step)
, y, np.roll(y, -step), np.roll(y, -2*step)
, z, np.roll(z, -step), np.roll(z, -2*step)])
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
seg, = plt.plot([], [])
ax.set_xlim3d(-1, 1)
ax.set_ylim3d(-1, 1)
ax.set_zlim3d(0, 1)
def init():
return seg,
def updateFigure(t):
p1 = dati[t, 1:4]
p2 = dati[t, 4:7]
p3 = dati[t, 7:10]
seg._verts3d = (p1, p2, p3)
return seg,
ani = animation.FuncAnimation(
fig, updateFigure
, init_func=init
, frames=iMax
, interval=5, blit=True)
plt.show()

Categories

Resources