Related
I want to add constant x, y, z lines into a matplotlib 3D scatter plot in Python which extended from this limit point, may I know how could I do so?
x_limit = [-0.5] y_limit = [151] z_limit = [1090]
Example code:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import pandas as pd
from scipy.stats import multivariate_normal
fig = plt.figure(figsize=(8,8)) # size 4 inches X 4 inches
ax = fig.add_subplot(111, projection='3d')
np.random.seed(42)
xs = np.random.random(100)*-0.8
ys = np.random.random(100)*300
zs = np.random.random(100)*10500
plot = ax.scatter(xs,ys,zs)
ax.set_title("3D plot with limit")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
x_limit = [-0.5]
y_limit = [151]
z_limit = [1090]
ax.scatter(x_limit, y_limit, z_limit, c = 'g', marker = "s", s = 50)
plt.show()
This code should do the trick. There are a couple important things to note though. First, the mesh grid created only worked because each of the three axes share the same limits. Second, while the x_limit and y_limit values work as the X and Y arguments it appears that the Z argument is expected to be of a higher dimensionality (hence why I used full_like to fill an array of the same shape as x_1 and x_2 with the Z limit).
x_1, x_2 = np.meshgrid(np.arange(0, 1.1, 0.1), np.arange(0, 1.1, 0.1))
ax.plot_surface(x_limit, x_1, x_2, color='r', alpha=0.5)
ax.plot_surface(x_1, y_limit, x_2, color='g', alpha=0.5)
ax.plot_surface(x_1, x_2, np.full_like(x_1, z_limit), color='b', alpha=0.5)
I have the given sample data and interpolated spline:
import matplotlib.pyplot as plt
import numpy as np
from scipy import interpolate
x = [0.1, 1.5, 2.3, 5.5, 6.7, 7]
y = [5, 4, 5, 2, 2, 3]
s = interpolate.UnivariateSpline(x, y, s=0)
xs = np.linspace(min(x), max(x), 10000) #values for x axis
ys = s(xs)
plt.figure()
plt.plot(xs, ys, label='spline')
plt.plot(x, y, 'x', label='collected data')
plt.legend()
I would like to pull the x values that correspond to the integer y values of the spline, but am not sure how to do this. I assume I will be using np.where() and have tried (to no avail):
root_index = np.where(ys == ys.round())
You could use the find_roots function from this post to find the exact interpolated x-values:
import matplotlib.pyplot as plt
import numpy as np
from scipy import interpolate
def find_roots(x, y):
s = np.abs(np.diff(np.sign(y))).astype(bool)
return x[:-1][s] + np.diff(x)[s] / (np.abs(y[1:][s] / y[:-1][s]) + 1)
x = [0.1, 1.5, 2.3, 5.5, 6.7, 7]
y = [5, 4, 5, 2, 2, 3]
s = interpolate.UnivariateSpline(x, y, s=0)
xs = np.linspace(min(x), max(x), 500) # values for x axis
ys = s(xs)
plt.figure()
plt.plot(xs, ys, label='spline')
for y0 in range(0, 7):
r = find_roots(xs, ys - y0)
if len(r) > 0:
plt.scatter(r, np.repeat(y0, len(r)), label=f'y0 = {y0}')
for ri in r:
plt.text(ri, y0, f' {ri:.3f}', ha='left', va='center')
plt.legend()
plt.xlim(min(x) - 0.2, max(x) + 1)
plt.show()
PS: with much less points for the x's, e.g. xs = np.linspace(min(x), max(x), 50), the curve would look a bit bumpy, and the interpolation would be slightly different:
It's not a good idea comparing two floats with equal. Use:
# play with `thresh`
thresh = 1e-4
root_index = np.where(np.abs(ys - ys.round())<1e-4)
printt(xs[root_index])
print(ys[root_index])
Output:
array([0.1 , 0.44779478, 2.29993999, 4.83732373, 7. ])
array([5. , 4.00009501, 4.99994831, 3.00006626, 3. ])
Numpy also has np.isclose. So something like:
root_index = np.isclose(ys, ys.round(), rtol=1e-5, atol=1e-4)
ys[root_index]
And you have the same output.
The object returned by scipy.interpolate.UnivariateSpline is a wrapper about fitpack interpolation routines. You can get an identical interpolation using the CubicSpline class: replace s = interpolation.UnivariateSpline(x, y, s=0) with s = interpolation.CubicSpline(x, y). The results are identical, as you can see in the figure at the end.
The advantage to using CubicSpline is that it returns a PPoly object which has working roots and solve methods. You can use this to compute the roots with integer offsets.
To compute the range of possible integers, you can use the derivative method coupled with roots:
x_extrema = np.concatenate((x[:1], s.derivative().roots(), x[-1:]))
y_extrema = s(x_extrema)
i_min = np.ceil(y_extrema.min())
i_max = np.floor(y_extrema.max())
Now you can compute the roots of the offset spline:
x_ints = [s.solve(i) for i in np.arange(i_min, i_max + 1)]
x_ints = np.concatenate(x_ints)
x_ints.sort()
Here are some additional plotting commands and their output:
plt.figure()
plt.plot(xs, interpolate.UnivariateSpline(x, y, s=0)(xs), label='Original Spline')
plt.plot(xs, ys, 'r:', label='CubicSpline')
plt.plot(x, y, 'x', label = 'Collected Data')
plt.plot(x_extrema, y_extrema, 's', label='Extrema')
plt.hlines([i_min, i_max], xs[0], xs[-1], label='Integer Limits')
plt.plot(x_ints, s(x_ints), '^', label='Integer Y')
plt.legend()
You can verify the numbers:
>>> s(x_ints)
array([5., 4., 4., 5., 5., 4., 3., 2., 2., 3., 4., 5.])
I have a similar problem to this one:
Animate a python pyplot by moving a point plotted via scatter.
The given values are positions (q) with corresponding velocities (v). Now I want to animate the shift in position over time for each point. My attempt
from matplotlib import pyplot as plt
import numpy as np
import mpl_toolkits.mplot3d.axes3d as p3
from matplotlib import animation
fig = plt.figure()
ax = p3.Axes3D(fig)
q = [[-4.32, -2.17, -2.25, 4.72, 2.97, 1.74],
[ 2.45, 9.73, 7.45,4.01,3.42, 1.80],[-1.40, -1.76, -3.08,-9.94,-3.13,-1.13]]
v = [[ 0.0068,0.024, -0.014,-0.013, -0.0068,-0.04],[ 0.012,
0.056, -0.022,0.016, 0.0045, 0.039],
[-0.0045, 0.031, 0.077,0.0016, -0.015,-0.00012]]
t=np.arange(0, 1000, 2)
x=q[0]
y=q[1]
z=q[2]
s=v[0]
u=v[1]
w=v[2]
points, = ax.plot(x, y, z, '*')
def update_points(t, x, y, z, points):
point = []
for i in range(0,len(x)-1,1):
points.set_data(np.array([x[i]+s[i]*t*5, y[i]+u[i]*t*5]))
points.set_3d_properties(z[i]+w[i]*t*5, 'z')
return point
ani=animation.FuncAnimation(fig, update_points, 10, fargs=(x, y, z, points))
plt.show()
does not work properly. The first image of the animation shows all points, but afterward, only the movement of the last point is simulated. The error happens in the defined update_points function because it seems like only the values for the last I are stored. Does anybody know how I have to change the code that all points are moving simultaneously?
Your problem is that you are only passing one set of coordinates to set_data()/set_3d_properties, and therefore only one point remains. You need to update all the coordinates of all your points, and then update points with those arrays.
I could not figure out exactly how you were doing your math in the update function, so here's an example using random fluctuations:
from matplotlib import pyplot as plt
import numpy as np
import mpl_toolkits.mplot3d.axes3d as p3
from matplotlib import animation
fig = plt.figure()
ax = p3.Axes3D(fig)
q = [[-4.32, -2.17, -2.25, 4.72, 2.97, 1.74],
[ 2.45, 9.73, 7.45,4.01,3.42, 1.80],[-1.40, -1.76, -3.08,-9.94,-3.13,-1.13]]
v = [[ 0.0068,0.024, -0.014,-0.013, -0.0068,-0.04],[ 0.012,
0.056, -0.022,0.016, 0.0045, 0.039],
[-0.0045, 0.031, 0.077,0.0016, -0.015,-0.00012]]
x=np.array(q[0])
y=np.array(q[1])
z=np.array(q[2])
s=np.array(v[0])
u=np.array(v[1])
w=np.array(v[2])
points, = ax.plot(x, y, z, '*')
txt = fig.suptitle('')
def update_points(num, x, y, z, points):
txt.set_text('num={:d}'.format(num)) # for debug purposes
# calculate the new sets of coordinates here. The resulting arrays should have the same shape
# as the original x,y,z
new_x = x+np.random.normal(1,0.1, size=(len(x),))
new_y = y+np.random.normal(1,0.1, size=(len(y),))
new_z = z+np.random.normal(1,0.1, size=(len(z),))
# update properties
points.set_data(new_x,new_y)
points.set_3d_properties(new_z, 'z')
# return modified artists
return points,txt
ani=animation.FuncAnimation(fig, update_points, frames=10, fargs=(x, y, z, points))
plt.show()
Thank you very much! It works now
from matplotlib import pyplot as plt
import numpy as np
import mpl_toolkits.mplot3d.axes3d as p3
from matplotlib import animation
fig = plt.figure()
ax = p3.Axes3D(fig)
q = [[-4.32, -2.17, -2.25, 4.72, 2.97, 1.74],
[ 2.45, 9.73, 7.45,4.01,3.42, 1.80],[-1.40, -1.76, -3.08,-9.94,-3.13,-1.13]]
v = [[ 0.0068,0.024, -0.014,-0.013, -0.0068,-0.04],[ 0.012,
0.056, -0.022,0.016, 0.0045, 0.039],
[-0.0045, 0.031, 0.077,0.0016, -0.015,-0.00012]]
x=np.array(q[0])
y=np.array(q[1])
z=np.array(q[2])
s=np.array(v[0])
u=np.array(v[1])
w=np.array(v[2])
points, = ax.plot(x, y, z, '*')
txt = fig.suptitle('')
def update_points(t, x, y, z, points):
txt.set_text('num={:d}'.format(t))
new_x = x + s * t
new_y = y + u * t
new_z = z + w * t
print('t:', t)
# update properties
points.set_data(new_x,new_y)
points.set_3d_properties(new_z, 'z')
# return modified artists
return points,txt
ani=animation.FuncAnimation(fig, update_points, frames=15, fargs=(x, y, z, points))
ax.set_xlabel("x [pc]")
ax.set_ylabel("y [pc]")
ax.set_zlabel('z [pc]')
plt.show()
I'm having two lists x, y representing coordinates in 2D. For example x = [1,4,0.5,2,5,10,33,0.04] and y = [2,5,44,0.33,2,14,20,0.03]. x[i] and y[i] represent one point in 2D. Now I also have a list representing "heat" values for each (x,y) point, for example z = [0.77, 0.88, 0.65, 0.55, 0.89, 0.9, 0.8,0.95]. Of course x,y and z are much higher dimensional than the example.
Now I would like to plot a heat map in 2D where x and y represents the axis coordinates and z represents the color. How can this be done in python?
This code produces a heat map. With a few more data points, the plot starts looking pretty nice and I've found it to be very quick in general even for >100k points.
import matplotlib.pyplot as plt
import matplotlib.tri as tri
import numpy as np
import math
x = [1,4,0.5,2,5,10,33,0.04]
y = [2,5,44,0.33,2,14,20,0.03]
z = [0.77, 0.88, 0.65, 0.55, 0.89, 0.9, 0.8, 0.95]
levels = [0.7, 0.75, 0.8, 0.85, 0.9]
plt.figure()
ax = plt.gca()
ax.set_aspect('equal')
CS = ax.tricontourf(x, y, z, levels, cmap=plt.get_cmap('jet'))
cbar = plt.colorbar(CS, ticks=np.sort(np.array(levels)),ax=ax, orientation='horizontal', shrink=.75, pad=.09, aspect=40,fraction=0.05)
cbar.ax.set_xticklabels(list(map(str,np.sort(np.array(levels))))) # horizontal colorbar
cbar.ax.tick_params(labelsize=8)
plt.title('Heat Map')
plt.xlabel('X Label')
plt.ylabel('Y Label')
plt.show()
Produces this image:
or if you're looking for a more gradual color change, change the tricontourf line to this:
CS = ax.tricontourf(x, y, z, np.linspace(min(levels),max(levels),256), cmap=cmap)
and then the plot will change to:
Based on this answer, you might want to do something like:
import numpy as np
from matplotlib.mlab import griddata
import matplotlib.pyplot as plt
xs0 = [1,4,0.5,2,5,10,33,0.04]
ys0 = [2,5,44,0.33,2,14,20,0.03]
zs0 = [0.77, 0.88, 0.65, 0.55, 0.89, 0.9, 0.8,0.95]
N = 30j
extent = (np.min(xs0),np.max(xs0),np.min(ys0),np.max(ys0))
xs,ys = np.mgrid[extent[0]:extent[1]:N, extent[2]:extent[3]:N]
resampled = griddata(xs0, ys0, zs0, xs, ys, interp='linear')
plt.imshow(np.fliplr(resampled).T, extent=extent,interpolation='none')
plt.colorbar()
The example here might also help: http://matplotlib.org/examples/pylab_examples/griddata_demo.html
I have two problems with the contour plot in matplotlib:
How can I render an arbitrary meshgrid as a regular one?
I would like the position of the ticks on both axes to be evenly distributed while still reflecting the position of my nodes.
How can I highlight the position of my data's highest value with a colored marker?
Here is my code:
import numpy as np
import pylab as pl
def plot_s(data, xlist, ylist):
pl.subplot(111)
x = np.array(xlist)
y = np.array(ylist)
X, Y = np.meshgrid(x, y)
CS = pl.contour(X, Y, data, colors='k')
pl.clabel(CS, inline = 1, fontsize=10)
pl.xlabel('x list')
pl.ylabel('y list')
pl.xticks(xlist)
pl.yticks(ylist)
pl.title('Contour plot')
pl.show()
def main():
data = np.array([[ 0.56555019, 0.57933922, 0.58266252, 0.58067285, 0.57660236, 0.57185625, 0.56711252, 0.55557035, 0.55027705, 0.54480605],
[ 0.55486559, 0.57349717, 0.57940478, 0.57843897, 0.57463271, 0.56963449, 0.5643922 , 0.55095598, 0.54452534, 0.53762606],
[ 0.53529358, 0.56254991, 0.57328105, 0.57409218, 0.57066168, 0.5654082 , 0.55956853, 0.5432474 , 0.53501127, 0.52601203],
[ 0.50110483, 0.54004071, 0.55800178, 0.56173719, 0.55894846, 0.55328279, 0.54642887, 0.52598388, 0.51533094, 0.50354147]])
xlist = [10., 20., 30., 40., 50., 60., 70., 100., 120., 150.]
ylist = [50, 70, 90, 100]
plot_s(data, xlist, ylist)
if __name__ == '__main__':
main()
How can I render an arbitrary meshgrid as a regular one?
One suggestion is to create a regular meshgrid, by first creating arrays of evenly spaced values between your minimum and maximum x and y. Further you could use custom ticks to reflect the fact your data-points are not equidistant. See comments in the codes about how I implemented that.
How can I highlight the position of my data's highest value with a colored marker?
To retrieve the highest value, you could use np.max() and then find the position of this value in the data-array with np.where. Simply plot a marker on this location.
Alternatively, using plt.contour you could create a contour with a level sufficiently close to your maximum value's position, to create a ring around it, or even a point on it:
epsillon = 0.0001
levels = np.arange(max_value - epsillon, max_value + epsillon)
CS2 = plt.contour(X,Y,data, levels,
origin='lower',
linewidths=2,
extent=(-3,3,-2,2))
Note that with the first method, the dot will end up on the top of an existing grid node, while plt.contour interpolates your data, and depending on the interpolation algorithm used, it may result in a somewhat different location. Yet here it appear to concur.
The code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
def plot_s(data, x, y, xlist, ylist):
ax = plt.gca()
########### create your uniform meshgrid..... ############
X, Y = np.meshgrid(x, y)
CS = ax.contour(X, Y, data, colors='k')
###### ... and let ticks indicate that your new space is not linear
# assign tick positions according to the regular array
ax.set_yticks(y)
# Assign the label to reflect your original nodes position
ax.set_yticklabels(ylist)
# and same for x
ax.set_xticks(x)
ax.set_xticklabels(xlist)
#############################################################
########### GET MAXIMUM AND MARK IT WITH A POINT ########
# get maximum value in your data
max_value = np.max(data)
# get position index of this calue in your data array
local_max_index = np.where(data==max_value)
## retrieve position of your
max_x = X[local_max_index[0], local_max_index[1]]
max_y = Y[local_max_index[0], local_max_index[1]]
# plot one marker on this position
plt.plot(max_x, max_y, color="red", marker = "o", zorder = 10,
markersize=15, clip_on=False)
##############################################################
plt.title('Contour plot')
plt.show()
def main():
# Your data: 4 x 10 array
data = np.array([[ 0.56555019, 0.57933922, 0.58266252, 0.58067285, 0.57660236,
0.57185625, 0.56711252, 0.55557035, 0.55027705, 0.54480605],
[ 0.55486559, 0.57349717, 0.57940478, 0.57843897, 0.57463271,
0.56963449, 0.5643922 , 0.55095598, 0.54452534, 0.53762606],
[ 0.53529358, 0.56254991, 0.57328105, 0.57409218, 0.57066168,
0.5654082 , 0.55956853, 0.5432474 , 0.53501127, 0.52601203],
[ 0.50110483, 0.54004071, 0.55800178, 0.56173719, 0.55894846,
0.55328279, 0.54642887, 0.52598388, 0.51533094, 0.50354147]])
# create a list values with regular interval for the mesh grid
x = np.array([10 + i * (150.-10.)/9 for i in range(10)])
y = np.array([50 + i * (100.-50.)/4 for i in range(4)])
# create arrays with values to be displayed as ticks
xlist = np.array([10., 20., 30., 40., 50., 60., 70., 100., 120., 150.])
ylist = np.array([50, 70, 90, 100])
plot_s(data, x, y, xlist, ylist)
if __name__ == '__main__':
main()
voilĂ :
Here with the meshgrid in the background to show the deformation/mapping:
Below is essentially the same but a slightly more compact version of what has been proposed by snake_charmer. However, I am not sure if I understood your question correctly. If your points in xlist and ylist are not too irregularly spaced, a more elegant solution could be to keep the irregular grid but to highlight the location of data points using ax.grid(). This depends on what exactly you want to show in the figure, though.
import numpy as np
from matplotlib import pyplot as plt
def plot_s(data, xlist, ylist):
fig, ax = plt.subplots()
x = np.arange(len(xlist))
y = np.arange(len(ylist))
X, Y = np.meshgrid(x, y)
CS = ax.contour(X, Y, data, colors='k')
ax.clabel(CS, inline = 1, fontsize=10)
ax.set_xlabel('x list')
ax.set_ylabel('y list')
ax.set_xticks(x)
ax.set_yticks(y)
ax.set_xticklabels(xlist)
ax.set_yticklabels(ylist)
jmax, imax = np.unravel_index(np.argmax(data), data.shape)
ax.plot(imax, jmax, 'ro')
ax.set_title('Contour plot')
plt.show()
def main():
data = np.array([[ 0.56555019, 0.57933922, 0.58266252, 0.58067285,
0.57660236, 0.57185625, 0.56711252, 0.55557035,
0.55027705, 0.54480605],
[ 0.55486559, 0.57349717, 0.57940478, 0.57843897,
0.57463271, 0.56963449, 0.5643922 , 0.55095598,
0.54452534, 0.53762606],
[ 0.53529358, 0.56254991, 0.57328105, 0.57409218,
0.57066168, 0.5654082 , 0.55956853, 0.5432474 ,
0.53501127, 0.52601203],
[ 0.50110483, 0.54004071, 0.55800178, 0.56173719,
0.55894846, 0.55328279, 0.54642887, 0.52598388,
0.51533094, 0.50354147]])
xlist = [10., 20., 30., 40., 50., 60., 70., 100., 120., 150.]
ylist = [50, 70, 90, 100]
plot_s(data, xlist, ylist)
if __name__ == '__main__':
main()