I would like to use pcolormesh within a Lon/Lat
grid of a satellite granule. The satellite has an inclination of
65 degree. So pcolormesh is kind of skewed.
I have prepared 2 snippets below.
Snippet (1) is partly from that questions
Python quiver and pcolormesh not lining up exactly right
In pcolormesh each pixel corresponds to data on its lower left corner,
(see Plot (1)).
Data points are always the scatterplots with the x-marker.
What I afterwards did, is shifting the pixels, so that the pixels are centered on the
actual data points.
First I shifted the pixels with have of the resolution (see Plot(2)).
Then I shifted the pixels with the individual difference of the Y (Latitude) and
X (Longitude) (see Plot(3))
I did the last step (shifting with Lat/Lon) because in my actual example
(snippet 2) the resolution or lets say the difference between latitudes and the difference between
longitudes is not the same and changes. So I have not a regular grid.
I inserted a column of 0 at the beginning, because otherwise I could not use
pcolormesh.
In my case it is not a problem because I do not really need the first column.
import numpy as np
import matplotlib.pyplot as plt
# Snippet (1)
# Note: In the snippets Latitude is convertible with Y
# Longitude is convertible with X
def middle_pc_X(X):
# X convertible with lon
X_half = np.abs(np.diff(X)/2.0)
X_half = np.insert(X_half, 0, 0, axis=1)
return X_half
def middle_pc_Y(Y):
# Y convertible with lat
Y_half = np.abs(np.diff(Y, axis=0)/2.0)
Y_half = Y_half.T
Y_half = np.insert(Y_half, 0, 0, axis=1)
return Y_half
x = np.arange(5)
y = np.arange(5)
X, Y = np.meshgrid(x, y)
C = np.random.rand(len(x), len(y))
res = 1.0 # 1.0 distance between x and y
# combination of X/Y
X_mega = middle_pc_X(X)
Y_mega = middle_pc_Y(Y)
#Plots
# Plot (1)
# Plot Pcolormesh default
plt.figure(figsize=(8, 8))
plt.subplot(2,2,1)
plt.pcolormesh(X, Y, C)
plt.colorbar()
plt.scatter(X,Y, s=20, c='black', marker='x')
# Plot (2)
# Plot with res
plt.subplot(2,2,2)
plt.pcolormesh(X-(res/2.0),
Y-(res/2.0),
C)
plt.colorbar()
plt.scatter(X,Y, s=20, c='black', marker='x')
# Plot(3)
# Plot in combination with X/Y
plt.subplot(2,2,3)
plt.pcolormesh(X-X_mega,
Y-Y_mega,
C)
plt.colorbar()
plt.scatter(X,Y, s=20, c='black', marker='x')
Plot 1-3:
Snippet (2):
Now I come to my second snippet and my question
Here I prepared respectively snippets of a satellite granule.
As I mentioned the satellite has a inclination of 65 degree.
The satellite is here on its descending node (it is flying from North to South).
So in other situations the satellite is on its ascending node (it is flying from
South to North).
The granule has originally the shape of Lon(7933, 24),
Lat(7933, 24), Rain(7933, 24)
In snippet (2) I did a shortcut of the data (4 x 4)
In Plot (4) of snippet (2) you can see the originally data.
I would like to plot the same as in snippet one,
so that the pixels are centered on the actual data points
Sadly a shifting with half of the resolution does not work,
the resolution is not the same.
I think that I have to use the difference method?
import numpy as np
import matplotlib.pyplot as plt
# Snippet (2)
Lon_bin = np.array([[-42.84589005, -42.84976959, -42.85368347, -42.85772705],
[-42.73979187, -42.74386215, -42.74796677, -42.75219727],
[-42.63357925, -42.63784409, -42.64214325, -42.64657211],
[-42.52753067, -42.53199387, -42.53648758, -42.54111099]])
Lat_bin = np.array([[ 65.62378693, 65.57657623, 65.52971649, 65.48249817],
[65.62098694, 65.57377625, 65.52692413, 65.47970581],
[65.61811066, 65.57090759, 65.52405548, 65.47683716],
[65.61515045, 65.56794739, 65.52110291, 65.47389221]])
rain = np.arange(16).reshape(4,4)
# Plots
# Plot (4)
plt.figure()
plt.figure(figsize=(10, 10))
plt.pcolormesh(Lon_bin,
Lat_bin,
rain)
plt.scatter(Lon_bin,
Lat_bin,
s=30, c='black', marker='x')
Plot 4:
Thank you for your help
Markus
Related
I'm trying to draw polygons with Python. Polygons are parcel of land with their actual coordinates. So far I have tried matplotlib and tkinter but no result. Is there a library where I can get these polygons scaled and vector based? The scale will be subject to change according to the size of the plot. Like 1/50, 1/100 or 1/200. As a result, can I have an architectural drawing with real coordinates?
Some example:
def fDraw(self, x, y):
x.append(x[0])
y.append(y[0])
xs = np.array(x)
ys = np.array(y)
plt.plot(xs,ys)
plt.show()
y = [19803.76, 19827.50, 19829.54, 19805.39]
x = [21065.67, 21063.77, 21079.64, 21081.62]
VLand.fDraw(x,y)
You'll want to call set_aspect('equal') so the plotted chart retains a square aspect ratio:
import matplotlib.pyplot as plt
xs = [21065.67, 21063.77, 21079.64, 21081.62]
ys = [19803.76, 19827.50, 19829.54, 19805.39]
plt.fill(xs, ys, edgecolor="r", fill=False)
plt.gca().set_aspect('equal')
plt.show()
This renders
which shows the same visual shape as the original screenshot.
I am trying to plot contours of data that his been binned using numpy.hist2d, except the bins are set using numpy.logscale (equal binning in log space).
Unfortunately, this results in a strange behavior that I can't seem to resolve: the placement of the contours does not match the location of the points in x/y. I plot both the 2d histogram of the data, and the contours, and they do not overlap.
It looks like what is actually happening is the contours are being placed on the physical location of the plot in linear space where I expect them to be placed in log space.
It's a strange phenomenon that I think can be best described by the following plots, using identical data but binned in different ways.:
Here is a minimum working example to produce the logbinned data:
import numpy as np
import matplotlib.pyplot as plt
x = np.random.normal(loc=500, scale=100,size=10000)
y = np.random.normal(loc=600, scale=60, size=10000)
nbins = 50
bins = (np.logspace(np.log10(10),np.log10(1000),nbins),np.logspace(np.log10(10),np.log10(1000),nbins))
HH, xe, ye = np.histogram2d(x,y,bins=bins)
plt.hist2d(x,y,bins=bins,cmin=1);
grid = HH.transpose()
extent = np.array([xe.min(), xe.max(), ye.min(), ye.max()])
cs = plt.contourf(grid,2,extent=extent,extend='max',cmap='plasma',alpha=0.5,zorder=100)
plt.contour(grid,2,extent=extent,colors='k',zorder=100)
plt.yscale('log')
plt.xscale('log')
It's fairly clear what is happening -- the contour is getting misplaced do the scaling of the bins. I'd like to be able to plot the histogram and the contour here together.
If anyone has an idea of how to resolve this, that would be very helpful - thanks!
This is your problem:
cs = plt.contourf(grid,2,extent=extent,...)
You are passing in a single 2d array specifying the values of the histograms, but you aren't passing the x and y coordinates these data correspond to. By only passing in extent there's no way for pyplot to do anything other than assume that the underlying grid is uniform, stretched out to fit extent.
So instead what you have to do is to define x and y components for each value in grid. You have to think a bit how to do this, because you have (n, n)-shaped data and (n+1,)-shaped edges to go with it. We should probably choose the center of each bin to associate a data point with. So we need to find the midpoint of each bin, and pass those arrays to contour[f].
Something like this:
import numpy as np
import matplotlib.pyplot as plt
rng = np.random.default_rng()
size = 10000
x = rng.normal(loc=500, scale=100, size=size)
y = rng.normal(loc=600, scale=60, size=size)
nbins = 50
bins = (np.geomspace(10, 1000, nbins),) * 2
HH, xe, ye = np.histogram2d(x, y, bins=bins)
fig, ax = plt.subplots()
ax.hist2d(x, y, bins=bins, cmin=1)
grid = HH.transpose()
# compute bin midpoints
midpoints = (xe[1:] + xe[:-1])/2, (ye[1:] + ye[:-1])/2
cs = ax.contourf(*midpoints, grid, levels=2, extend='max', cmap='plasma', alpha=0.5, zorder=100)
ax.contour(*midpoints, grid, levels=2, colors='k', zorder=100)
# these are a red herring during debugging:
#ax.set_yscale('log')
#ax.set_xscale('log')
(I've cleaned up your code a bit.)
Alternatively, if you want to avoid having those white strips at the top and edge, you can keep your bin edges, and pad your grid with zeros:
grid_padded = np.pad(grid, [(0, 1)])
cs = ax.contourf(xe, ye, grid_padded, levels=2, extend='max', cmap='plasma', alpha=0.5, zorder=100)
ax.contour(xe, ye, grid_padded, levels=2, colors='k', zorder=100)
This gives us something like
This seems prettier, but if you think about your data this is less exact, because your data points are shifted with respect to the bin coordinates they correspond to. If you look closely you can see the contours being shifted with respect to the output of hist2d. You could fix this by generating geomspaces with one more final value which you only use for this final plotting step, and again use the midpoints of these edges (complete with a last auxiliary one).
I want to plot a vector field with vectors representing a displacement between one point to another on the map with cartopy.
My code works as expected when using the PlateCarree() transformation, but arrow length is several orders of magnitude off for all the other projections I tested.
Here is a MWE that should illustrate quite clearly the issue:
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import numpy
# Want to test for several different projections
projections = [
ccrs.PlateCarree(),
ccrs.EqualEarth(),
ccrs.Mollweide(),
ccrs.AzimuthalEquidistant(),
]
# ALl the coordinates will be given in the PlateCarree coordinate system.
coordinate_ccrs = ccrs.PlateCarree()
# We want N**2 points over the latitude/longitude values.
N = 5
lat, lon = numpy.meshgrid(numpy.linspace(-80, 80, N), numpy.linspace(-170, 170, N))
lat, lon = lat.flatten(), lon.flatten()
# We want arrows to appear, let make a small perturbation and try
# to do an arrow from (lon, lat) to (lon + perturbation, lat + perturbation).
rng = numpy.random.default_rng()
perturbation_amplitude = 10
lat_perturbation = perturbation_amplitude * rng.random(N * N)
lon_perturbation = perturbation_amplitude * rng.random(N * N)
# Create the matplotlib figure and axes, no projection for the moment as this
# will be changed later.
fig, axes = plt.subplots(2, 2)
axes = axes.flatten()
for i, projection in enumerate(projections):
# Replace the existing ax with an ax with the desired projection.
ax = axes[i]
fig.delaxes(ax)
ax = axes[i] = fig.add_subplot(2, 2, i + 1, projection=projection)
# Make the plot readable.
ax.set_global()
ax.gridlines(draw_labels="x")
# Non pertubed points are plotted in black.
ax.plot(lon, lat, "k.", ms=5, transform=coordinate_ccrs)
# Perturbed points are plotted in red.
ax.plot(
lon + lon_perturbation,
lat + lat_perturbation,
"r.",
ms=5,
transform=coordinate_ccrs,
)
# We try to draw arrows from a given black dot to its corresponding
# red dot.
ax.quiver(
lon,
lat,
lon_perturbation,
lat_perturbation,
transform=coordinate_ccrs,
# From https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.quiver.html?highlight=quiver#matplotlib.axes.Axes.quiver
# look at the documentation of the "scale_unit" parameter.
# The next 3 parameters are what matplotlib tell us to do. From
# matplotlib documentation:
# To plot vectors in the x-y plane, with u and v having the same units
# as x and y, use angles='xy', scale_units='xy', scale=1.
angles="xy",
scale_units="xy",
scale=1,
# Simply make the arrows nicer, removing these last 3 parameters do not
# solve the issue.
minshaft=2,
minlength=0.5,
width=0.002,
)
# Show everything
plt.show()
which display on the screen the following image:
The PlateCarree transformation is the only one displaying arrows. In fact, there are arrows in the other 3 projections, but I order to see them I need to scale them by 10000 with scale=0.00001 in the call to quiver, which gives:
Did I make a mistake when using cartopy API, is this expected behaviour and I missed something in the documentation, or is this a bug?
while there's quite some debate on github about cartopy's implementation of quiver-plot transformations GitHub-issues there is in fact a way on how to get your plot look as you want it to look...
However, while thinking about this... I noticed that there's a thing that you might want to consider when using projected quiver-plots...
As I see it, the re-projected arrows would would most probably need to be curved to really visualize the same direction as provided in the original data!
(in the input-crs the arrow points as a straight line from point A to B, but if you re-project the points, the "straight line" that connected A and B is now in general a curved line, and so if the original direction was correct, I think the new direction should be indicated as a curved arrow...)
That being said, you could achieve what you want by transforming the points manually instead of letting cartopy do the job:
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import numpy
# Want to test for several different projections
projections = [
ccrs.PlateCarree(),
ccrs.EqualEarth(),
ccrs.Mollweide(),
ccrs.AzimuthalEquidistant(),
]
# ALl the coordinates will be given in the PlateCarree coordinate system.
coordinate_ccrs = ccrs.PlateCarree()
# We want N**2 points over the latitude/longitude values.
N = 5
lat, lon = numpy.meshgrid(numpy.linspace(-80, 80, N), numpy.linspace(-170, 170, N))
lat, lon = lat.flatten(), lon.flatten()
# We want arrows to appear, let make a small perturbation and try
# to do an arrow from (lon, lat) to (lon + perturbation, lat + perturbation).
rng = numpy.random.default_rng()
perturbation_amplitude = 10
lat_perturbation = perturbation_amplitude * rng.random(N * N)
lon_perturbation = perturbation_amplitude * rng.random(N * N)
# Create the matplotlib figure and axes, no projection for the moment as this
# will be changed later.
fig, axes = plt.subplots(2, 2)
axes = axes.flatten()
for i, projection in enumerate(projections):
# Replace the existing ax with an ax with the desired projection.
ax = axes[i]
fig.delaxes(ax)
ax = axes[i] = fig.add_subplot(2, 2, i + 1, projection=projection)
# Make the plot readable.
ax.set_global()
ax.gridlines(draw_labels="x")
# Non pertubed points are plotted in black.
ax.plot(lon, lat, "k.", ms=5, transform=coordinate_ccrs)
# Perturbed points are plotted in red.
ax.plot(
lon + lon_perturbation,
lat + lat_perturbation,
"r.",
ms=5,
transform=coordinate_ccrs,
)
xy_start = projection.transform_points(coordinate_ccrs, lon, lat)[:,:-1].T
xy_end = projection.transform_points(coordinate_ccrs, lon + lon_perturbation,
lat + lat_perturbation)[:,:-1].T
# We try to draw arrows from a given black dot to its corresponding
# red dot.
ax.quiver(
*xy_start,
*(xy_end - xy_start),
# From https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.quiver.html?highlight=quiver#matplotlib.axes.Axes.quiver
# look at the documentation of the "scale_unit" parameter.
# The next 3 parameters are what matplotlib tell us to do. From
# matplotlib documentation:
# To plot vectors in the x-y plane, with u and v having the same units
# as x and y, use angles='xy', scale_units='xy', scale=1.
angles="xy",
scale_units="xy",
scale=1,
# Simply make the arrows nicer, removing these last 3 parameters do not
# solve the issue.
minshaft=2,
minlength=0.5,
width=0.002,
)
# Show everything
plt.show()
I'm attempting to create a divisionary curve on a scatter plot in matplotlib that would divide my scatterplot according to marker size.
The (x,y) are phi0 and phi0dot and I'm coloring/sizing according a to third variable 'e-folds'. I'd like to draw an 'S' shaped curve that divides the plot into small, black markers and large, cyan markers.
Here is a sample scatterplot run with a very few number of points for an example. Ultimately I will run with tens of thousands of points of data such that the divisionary would be much finer and more obviously 'S' shaped. This is roughly what I have in mind.
My code thus far looks like this:
# Set up the PDF
pdf_pages = PdfPages(outfile)
plt.rcParams["font.family"] = "serif"
# Create the canvas
canvas = plt.figure(figsize=(14.0, 14.0), dpi=100)
plt.subplot(1, 1, 1)
for a, phi0, phi0dot, efolds in datastore:
if efolds[-1] > 65:
plt.scatter(phi0[0], phi0dot[0], s=200, color='aqua')
else:
plt.scatter(phi0[0], phi0dot[0], s=30, color='black')
# Apply labels
plt.xlabel(r"$\phi_0$")
plt.ylabel(r"$\dot{\phi}_0$")
# Finish the file
pdf_pages.savefig(canvas)
pdf_pages.close()
print("Finished!")
This type of separation is very akin to what I'd like to do, but don't see immediately how I would extend this to my problem. Any advice would be much appreciated.
I would assume that the separation line between the differently classified points is a simple contour line along the threshold value.
Here I'm assuming classification takes values of 0 or 1, hence one can draw a contour along 0.5,
ax.contour(x,y,clas, [0.5])
Example:
import numpy as np
import matplotlib.pyplot as plt
# Some data on a grid
x,y = np.meshgrid(np.arange(20), np.arange(10))
z = np.sin(y+1) + 2*np.cos(x/5) + 2
fig, ax = plt.subplots()
# Threshold; values above the threshold belong to another class as those below.
thresh = 2.5
clas = z > thresh
size = 100*clas + 30*~clas
# scatter plot
ax.scatter(x.flatten(), y.flatten(), s = size.flatten(), c=clas.flatten(), cmap="bwr")
# threshold line
ax.contour(x,y,clas, [.5], colors="k", linewidths=2)
plt.show()
I am trying to create a 3-D plot and a 2-D plot side-by-side in python. I need equal aspect ratios for both plots, which I managed using code provided by this answer: https://stackoverflow.com/a/31364297/125507. The problem I'm having now is how to effectively "crop" the 3-D plot so it doesn't take up so much white space. That is to say, I want to reduce the length of the X and Y axes while maintaining equal scale to the (longer) Z-axis. Here is a sample code and plot:
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
def set_axes_equal(ax):
'''Make axes of 3D plot have equal scale so that spheres appear as spheres,
cubes as cubes, etc.. This is one possible solution to Matplotlib's
ax.set_aspect('equal') and ax.axis('equal') not working for 3D.
Input
ax: a matplotlib axis, e.g., as output from plt.gca().
'''
x_limits = ax.get_xlim3d()
y_limits = ax.get_ylim3d()
z_limits = ax.get_zlim3d()
x_range = abs(x_limits[1] - x_limits[0])
x_middle = np.mean(x_limits)
y_range = abs(y_limits[1] - y_limits[0])
y_middle = np.mean(y_limits)
z_range = abs(z_limits[1] - z_limits[0])
z_middle = np.mean(z_limits)
# The plot bounding box is a sphere in the sense of the infinity
# norm, hence I call half the max range the plot radius.
plot_radius = 0.5*max([x_range, y_range, z_range])
ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius])
ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius])
ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius])
ax = [None]*2
fig = plt.figure()
ax[0] = fig.add_subplot(121, projection='3d', aspect='equal')
ax[1] = fig.add_subplot(122, aspect='equal')
nn = 30
phis = np.linspace(0,np.pi, nn).reshape(1,nn)
psis = np.linspace(0,np.pi*2,nn).reshape(nn,1)
ones = np.ones((nn,1))
el_h = np.linspace(-5, 5, nn).reshape(1,nn)
x_sph = np.sin(phis)*np.cos(psis)
y_sph = np.sin(phis)*np.sin(psis)
z_sph = np.cos(phis)*ones
x_elp = np.sin(phis)*np.cos(psis)*.25
y_elp = np.sin(phis)*np.sin(psis)*.25
z_elp = el_h*ones
ax[0].scatter(x_sph, y_sph, z_sph)
ax[0].scatter(x_elp, y_elp, z_elp)
ax[1].scatter(y_sph, z_sph)
ax[1].scatter(y_elp, z_elp)
for ii in range(2):
ax[ii].set_xlabel('X')
ax[ii].set_ylabel('Y')
ax[0].set_zlabel('Z')
set_axes_equal(ax[0])
plt.savefig('SphereElipse.png', dpi=300)
And here is its image output:
3-D and 2-D sphere and ellipse side-by-side
Clearly the 2D plot automatically modifies the length of the axes while maintaining the scale, but the 3D plot doesn't, leading to a tiny representation which does not well use the space allotted to its subplot. Is there any way to do this? This question is similar to an earlier unanswered question How do I crop an Axes3D plot with square aspect ratio?, except it adds the stipulation of multiple subplots, which means the answers provided there do not work.