Why don't the contour lines show up on this plot? - python

I'm trying to create a contour plot to show a height field at time 0, represented by h_list[0, :, :]. When I run the code, an empty plot shows up with the correct axes and scales, but no contour lines. The shape of h_list is [250, 99, 99], but I am only calling the first x index, which is shape [99, 99]. This matches the others.
My code is this:
h_list = np.array(h_list)
con = plt.figure()
ax = con.add_subplot(111)
x = np.linspace(0, 1000, 99)
y = np.linspace(0, 1000, 99)
X, Y = np.meshgrid(x, y)
Z = h_list[0, :, :]
ax.contour(X, Y, Z)
ax.set_xlim(0, 1000)
ax.set_ylim(0, 1000)
The shapes of all variables are the same and I've set the appropriate limits so I'm not sure why matplotlib isn't recognizing my data. I do not get any compiling errors, just no graph. Any ideas?
Edit: Z has shape [99, 99], which is the same as X and Y.

Related

How to plot a line in 3 dimensional space with matplotlib

I have two 3D-points, for example a = (100, 100, 10) and b = (0, 100, 60), and would like to fit a line through those points.
I know, the 3D line equation can have different shapes:
Vector-form:
(x,y,z)=(x0,y0,z0)+t(a,b,c)
Parameter-form:
x=x0+ta
y=y0+tb
z=z0+tc
But I have a problem getting the data in the right shape for a numerical function.
The following code should work
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.axes(projection ='3d')
# defining coordinates for the 2 points.
x = np.array([100, 0])
y = np.array([100, 100])
z = np.array([10, 60])
# plotting
ax.plot3D(x, y, z)
plt.show()
Here the ax.plot3D() plots a curve that joins the points (x[i], y[i], z[i]) with straight lines.

Plot three lists using plot_trisurf but got IndexError

I want to plot two surface plots from four lists, which are all independent from each other.
I tried this:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import mpl_toolkits.mplot3d as Axes3D
X = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
Y = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Z1 = [0.735, 0.735, 0.735, 0.735, 0.735, 0.735, 0.735, 0.735, 0.735, 0.735] # the elements here happens to be the same, but could be different
Z2 = [0.8623, 0.9461, 0.9341, 0.976, 0.982, 0.976, 0.976, 0.976, 0.976, 0.976]
X = np.array(X)
Y = np.array(Y)
Z1 = np.array(Z1)
Z2 = np.array(Z2)
X, Y = np.meshgrid(X,Y)
fig = plt.figure(figsize=(8,8))
ax1 = fig.add_subplot(121,projection='3d')
surf1 = ax1.plot_trisurf(X.flatten(),Y.flatten(),Z1.flatten(),cmap = cm.jet, antialiased=True)
ax2 = fig.add_subplot(122,projection='3d')
surf2 = ax2.plot_trisurf(X.flatten(),Y.flatten(),Z2.flatten(),cmap = cm.jet, antialiased=True)
However, an IndexError pops out saying that "index 40 is out of bounds for axis 0 with size 10".
Given that all lists have length 10, I thought the "index 40" may be due to meshgrid, but when I deleted that line, there is another error in "qhull Delauney triangulation calculation".
From my search, it seemed like a majority of 3d plots are function-based, i.e. z=f(x,y). I am not sure if the errors here are because such a relationship does not exist in my case.
Thank you in advance.
Meshgrid changes the shape of X,Y:
>>> X, Y = np.meshgrid(X,Y)
>>> X.shape, Y.shape
(10,10) (10,10)
But Z1 and Z2 still have the shapes (10,). So to provide the same dimensionality along all axes, mesh Z1 and Z2:
X, Y = np.meshgrid(X,Y)
Z1,Z2 = np.meshgrid(Z1, Z2)
And you will get the correct plot:

Plot average of scattered values in 2D bins as a histogram/hexplot

I have 3 dimensional scattered data x, y, z.
I want to plot the average of z in bins of x and y as a hex plot or 2D histogram plot.
Is there any matplotlib function to do this?
I can only come up with some very cumbersome implementations even though this seems to be a common problem.
E.g. something like this:
Except that the color should depend on the average z values for the (x, y) bin (rather than the number of entries in the (x, y) bin as in the default hexplot/2D histogram functionalities).
If binning is what you are asking, then binned_statistic_2d might work for you. Here's an example:
from scipy.stats import binned_statistic_2d
import numpy as np
x = np.random.uniform(0, 10, 1000)
y = np.random.uniform(10, 20, 1000)
z = np.exp(-(x-3)**2/5 - (y-18)**2/5) + np.random.random(1000)
x_bins = np.linspace(0, 10, 10)
y_bins = np.linspace(10, 20, 10)
ret = binned_statistic_2d(x, y, z, statistic=np.mean, bins=[x_bins, y_bins])
fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(12, 4))
ax0.scatter(x, y, c=z)
ax1.imshow(ret.statistic.T, origin='bottom', extent=(0, 10, 10, 20))
#Andrea's answer is very clear and helpful, but I wanted to mention a faster alternative that does not use the scipy library.
The idea is to do a 2d histogram of x and y weighted by the z variable (it has the sum of the z variable in each bin) and then normalize against the histogram without weights (it has the number of counts in each bin). In this way, you will calculate the average of the z variable in each bin.
The code:
import numpy as np
import matplotlib.pyplot as plt
x = np.random.uniform(0, 10, 10**7)
y = np.random.uniform(10, 20, 10**7)
z = np.exp(-(x-3)**2/5 - (y-18)**2/5) + np.random.random(10**7)
x_bins = np.linspace(0, 10, 50)
y_bins = np.linspace(10, 20, 50)
H, xedges, yedges = np.histogram2d(x, y, bins = [x_bins, y_bins], weights = z)
H_counts, xedges, yedges = np.histogram2d(x, y, bins = [x_bins, y_bins])
H = H/H_counts
plt.imshow(H.T, origin='lower', cmap='RdBu',
extent=[xedges[0], xedges[-1], yedges[0], yedges[-1]])
plt.colorbar()
In my computer, this method is approximately a factor 5 faster than using scipy's binned_statistic_2d.

How can I give specific x values to `scipy.interpolate.splev`?

How can I interpolate a hysteresis loop at specific x points? Multiple related questions/answers are available on SOF regarding B-spline interpolation using scipy.interpolate.splprep (other questions here or here). However, I have hundreds of hysteresis loops at very similar (but not exactly same) x positions and I would like to perform B-spline interpolation on all of them at specific x coordinates.
Taking a previous example:
import numpy as np
from scipy import interpolate
from matplotlib import pyplot as plt
x = np.array([23, 24, 24, 25, 25])
y = np.array([13, 12, 13, 12, 13])
# append the starting x,y coordinates
x = np.r_[x, x[0]]
y = np.r_[y, y[0]]
# fit splines to x=f(u) and y=g(u), treating both as periodic. also note that s=0
# is needed in order to force the spline fit to pass through all the input points.
tck, u = interpolate.splprep([x, y], s=0, per=True)
# evaluate the spline fits for 1000 evenly spaced distance values
xi, yi = interpolate.splev(np.linspace(0, 1, 1000), tck)
# plot the result
fig, ax = plt.subplots(1, 1)
ax.plot(x, y, 'or')
ax.plot(xi, yi, '-b')
plt.show()
Is it possible to provide specific x values to interpolate.splev? I get unexpected results:
x2, y2 = interpolate.splev(np.linspace(start=23, stop=25, num=30), tck)
fig, ax = plt.subplots(1, 1)
ax.plot(x, y, 'or')
ax.plot(x2, y2, '-b')
plt.show()
The b-spline gives x and y positions for a given u (between 0 and 1).
Getting y positions for a given x position involves solving for the inverse. As there can be many y's corresponding to one x (in the given example there are places with 4 y's, for example at x=24).
A simple way to get a list of (x,y)'s for x between two limits, is to create a filter:
import numpy as np
from scipy import interpolate
from matplotlib import pyplot as plt
x = np.array([23, 24, 24, 25, 25])
y = np.array([13, 12, 13, 12, 13])
# append the starting x,y coordinates
x = np.r_[x, x[0]]
y = np.r_[y, y[0]]
tck, u = interpolate.splprep([x, y], s=0, per=True)
# evaluate the spline fits for 1000 evenly spaced distance values
xi, yi = interpolate.splev(np.linspace(0, 1, 1000), tck)
# plot the result
fig, ax = plt.subplots(1, 1)
ax.plot(x, y, 'or')
ax.plot(xi, yi, '-b')
filter = (xi >= 24) & (xi <= 25)
x2 = xi[filter]
y2 = yi[filter]
ax.scatter(x2, y2, color='c')
plt.show()

Matplotlib: How to change the color of a LineCollection according to its coordinates?

Consider the folowing plot:
fig, ax = plt.subplots(figsize = (14, 6))
ax.set_facecolor('k')
ax.set_xlim(0, 100)
ax.set_ylim(0, 100)
xs = np.arange(60, 70) # xs = np.linspace(60, 70, 100)
ys = np.arange(0, 100, .5) # ys = np.linspace(0, 100, 100)
v = [[[x, y] for x in xs] for y in ys]
lines = LineCollection(v, linewidth = 1, cmap = plt.cm.Greys_r)
lines.set_array(xs)
ax.add_collection(lines)
How can I change the color of the lines according to their x coordinates (horizontally) so as to create a "shading" effect like this:
Here, the greater x is, the "whiter" the LineCollection is.
Following this reasoning, I thought that specifying lines.set_array(xs) would do the trick but as you can see in my plot the color gradation is still following the y axis. Strangely the pattern is repeating itself, from black to white (every 5) over and over (up to 100).
I think (not sure at all) the problem lies in the v variable that contains the coordinates. The concatenation of x and y might be improper.
The shape of the list v you supply to the LineCollection is indeed not suitable to create a gradient of the desired direction. This is because each line in a LineCollection can only have single color. Here the lines range from x=60 to x=70 and each of those lines has one color.
What you need to do instead is to create a line collection where each line is devided into several segments, each of which can then have its own color.
To this end an array of dimensions (n, m, l), where n is the number of segments, m is the number of points per segment, and l is the dimension (2D, hence l=2) needs to be used.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.collections import LineCollection
fig, ax = plt.subplots(figsize = (14, 6))
ax.set_facecolor('k')
ax.set_xlim(0, 100)
ax.set_ylim(0, 100)
xs = np.linspace(60, 70, 100)
ys = np.linspace(0, 100, 100)
X,Y = np.meshgrid(xs,ys)
s = X.shape
segs = np.empty(((s[0])*(s[1]-1),2,2))
segs[:,0,0] = X[:,:-1].flatten()
segs[:,1,0] = X[:,1:].flatten()
segs[:,0,1] = Y[:,:-1].flatten()
segs[:,1,1] = Y[:,1:].flatten()
lines = LineCollection(segs, linewidth = 1, cmap = plt.cm.Greys_r)
lines.set_array(X[:,:-1].flatten())
ax.add_collection(lines)
plt.show()

Categories

Resources