Unexpected behavior for contourplot in polar coordinates - jagged contours - python

I wish to plot a bunch of points onto a polar plot. When I apply it with simulated data, it works. When I try the same with my real data it fails and I'm not sure why.
# First with simulated data
# The angles for each point
phi = np.linspace(0, math.pi*2, 40) # full circle
phi = np.concatenate([phi, phi, phi, phi]) # 4 full circles
# The radii
rho = np.array([0,1,2,3]) # the radii for each circle
rho = np.repeat(rho, 40)
assert phi.shape == rho.shape
# First just plot the points
plt.figure()
ax = plt.subplot(111, projection='polar')
ax.scatter(phi, rho, c=rho)
ax.set_ylim(0,4)
This appears to work. Next, I use the following to draw concentric contours around the origin. The Z property is set to the radii, i.e., points that equally far away from the origin should be grouped within a contour.
# Create a meshgrid from the points
X, Y = np.meshgrid(phi, rho)
plt.figure()
ax = plt.subplot(111, projection='polar')
ax.scatter(X, Y, c=Y)
CS = ax.contourf(X,Y, Y, 2, alpha=0.4)
ax.set_ylim(0,4)
This works exactly as I want it:
Next, I want to apply this to my real data. My actual data has millions of data points, but here's just a small sample to reproduce.
rho = np.array([0.38818333, 0.73367091, 0.42336148, 1.39013061, 0.31064486,0.34546275, 0.05445943, 0.85551576, 0.55174167, 1.42371249,0.17644804, 1.76221456, 0.64519126, 0.02408941, 1.43986863,0.72718428, 0.4262945 , 0.1355583 , 0.86319986, 0.71212376,0.14891707, 1.01624534, 1.26915981, 1.39384488, 0.09623481,0.92635469, 1.74757901, 0.15811954, 0.22052651, 0.30784166,0.92740352, 1.29621377, 0.29832842, 1.04442307, 1.36185399,0.42979785, 0.94402815, 0.3786981 , 0.75865969, 1.97273479,0.61140136, 0.71452862, 0.25793468, 1.1751275 , 1.53945948,0.64150917, 0.09274101, 0.52548715, 0.7932458 , 0.90292444])
phi = np.array([1.04208195, 4.67055389, 3.32909655, 1.18709268, 0.86036178,5.820191 , 4.30457004, 1.81242968, 0.64295926, 4.85684143,2.73937709, 3.22891963, 0.25822595, 0.69526782, 0.70709764,1.92901075, 3.44538869, 5.38541473, 0.95255568, 4.01519928,0.8503274 , 5.26774545, 4.07787945, 4.51718652, 0.3170884 ,2.1946835 , 3.12550771, 5.67275731, 1.0000195 , 1.82570239,5.62578391, 0.81923255, 2.00131474, 0.48190872, 4.78875363,5.60395833, 2.01674743, 2.13494958, 5.10829845, 0.95324309,1.59531506, 4.99145225, 6.19873491, 3.32802456, 1.15590926,0.52989939, 6.02205398, 3.66013508, 4.16276819, 2.60498467])
assert rho.shape == phi.shape # both are (50,)
plt.figure()
ax = plt.subplot(111, projection='polar')
ax.scatter(phi, rho, c=rho)
ax.set_ylim(0,3)
Of course, my real data has much more variation that the simulated circles above. Next, I try to draw the contours in the same method. However, this now fails:
# Create a meshgrid from the points
X, Y = np.meshgrid(phi, rho)
plt.figure(figsize=(10,10)) # a little bigger to see better
ax = plt.subplot(111, projection='polar')
ax.scatter(X, Y, c=Y)
CS = ax.contourf(X,Y, Y, 2, alpha=0.4)
ax.set_ylim(0,4)

This behaviour is due to the fact that rho and phi are not sorted. Let's see:
import matplotlib.pyplot as plt
import numpy as np
rho = np.array([0.38818333, 0.73367091, 0.42336148, 1.39013061, 0.31064486,0.34546275, 0.05445943, 0.85551576, 0.55174167, 1.42371249,0.17644804, 1.76221456, 0.64519126, 0.02408941, 1.43986863,0.72718428, 0.4262945 , 0.1355583 , 0.86319986, 0.71212376,0.14891707, 1.01624534, 1.26915981, 1.39384488, 0.09623481,0.92635469, 1.74757901, 0.15811954, 0.22052651, 0.30784166,0.92740352, 1.29621377, 0.29832842, 1.04442307, 1.36185399,0.42979785, 0.94402815, 0.3786981 , 0.75865969, 1.97273479,0.61140136, 0.71452862, 0.25793468, 1.1751275 , 1.53945948,0.64150917, 0.09274101, 0.52548715, 0.7932458 , 0.90292444])
phi = np.array([1.04208195, 4.67055389, 3.32909655, 1.18709268, 0.86036178,5.820191 , 4.30457004, 1.81242968, 0.64295926, 4.85684143,2.73937709, 3.22891963, 0.25822595, 0.69526782, 0.70709764,1.92901075, 3.44538869, 5.38541473, 0.95255568, 4.01519928,0.8503274 , 5.26774545, 4.07787945, 4.51718652, 0.3170884 ,2.1946835 , 3.12550771, 5.67275731, 1.0000195 , 1.82570239,5.62578391, 0.81923255, 2.00131474, 0.48190872, 4.78875363,5.60395833, 2.01674743, 2.13494958, 5.10829845, 0.95324309,1.59531506, 4.99145225, 6.19873491, 3.32802456, 1.15590926,0.52989939, 6.02205398, 3.66013508, 4.16276819, 2.60498467])
plt.figure(figsize=(10,10))
ax = plt.subplot(111, projection='polar')
ax.set_ylim(0,4)
ax.scatter(phi, rho, c=rho)
#phi.sort()
#rho.sort()
X, Y = np.meshgrid(phi, rho)
CS = ax.contourf(X, Y, Y, 2, alpha=0.4)
plt.show()
gives:
If you uncomment the sorting lines:
But now the contours look weird, because the base data does not the whole geometry of interest. So we can create same data just for the purpose of the filled contours, instead:
alfa = np.radians(np.linspace(0, 360, 60))
r = np.arange(0, np.max(rho)+np.max(rho)/60, np.max(rho)/60)
r, alfa = np.meshgrid(r, alfa)
ax.contourf(alfa, r, r, 2, alpha=0.4)
Giving:

Related

polar pcolormesh plot projected onto cartopy map

To simplify, as much as possible, a question I already asked, how would you OVERLAY or PROJECT a polar plot onto a cartopy map.
phis = np.linspace(1e-5,10,10) # SV half cone ang, measured up from nadir
thetas = np.linspace(0,2*np.pi,361)# SV azimuth, 0 coincides with the vel vector
X,Y = np.meshgrid(thetas,phis)
Z = np.sin(X)**10 + np.cos(10 + Y*X) * np.cos(X)
fig, ax = plt.subplots(figsize=(4,4),subplot_kw=dict(projection='polar'))
im = ax.pcolormesh(X,Y,Z, cmap=mpl.cm.jet_r,shading='auto')
ax.set_theta_direction(-1)
ax.set_theta_offset(np.pi / 2.0)
ax.grid(True)
that results in
Over a cartopy map like this...
flatMap = ccrs.PlateCarree()
resolution = '110m'
fig = plt.figure(figsize=(12,6), dpi=96)
ax = fig.add_subplot(111, projection=flatMap)
ax.imshow(np.tile(np.array([[cfeature.COLORS['water'] * 255]], dtype=np.uint8), [2, 2, 1]), origin='upper', transform=ccrs.PlateCarree(), extent=[-180, 180, -180, 180])
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', resolution, edgecolor='black', facecolor=cfeature.COLORS['land']))
ax.pcolormesh(X,Y,Z, cmap=mpl.cm.jet_r,shading='auto')
gc.collect()
I'd like to project this polar plot over an arbitrary lon/lat... I can convert the polar theta/phi into lon/lat, but lon/lat coords (used on the map) are more 'cartesian like' than polar, hence you cannot just substitute lon/lat for theta/phi ... This is a conceptual problem. How would you tackle it?
Firstly, the data must be prepared/transformed into certain projection coordinates for use as input. And the instruction/option of the data's CRS must be specified correctly when used in the plot statement.
In your specific case, you need to transform your data into (long,lat) values.
XX = X/np.pi*180 # wrap around data in EW direction
YY = Y*9 # spread across N hemisphere
And plot it with an instruction transform=ccrs.PlateCarree().
ax.pcolormesh(XX,YY,Z, cmap=mpl.cm.jet_r,shading='auto',
transform=ccrs.PlateCarree())
The same (XX,YY,Z) data set can be plotted on orthographic projection.
Edit1
Update of the code and plots.
Part 1 (Data)
import matplotlib.colors
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import numpy as np
import matplotlib.pyplot as mpl
import cartopy.feature as cfeature
#
# Part 1
#
phis = np.linspace(1e-5,10,10) # SV half cone ang, measured up from nadir
thetas = np.linspace(0,2*np.pi,361)# SV azimuth, 0 coincides with the vel vector
X,Y = np.meshgrid(thetas,phis)
Z = np.sin(X)**10 + np.cos(10 + Y*X) * np.cos(X)
fig, ax = plt.subplots(figsize=(4,4),subplot_kw=dict(projection='polar'))
im = ax.pcolormesh(X,Y,Z, cmap=mpl.cm.jet_r,shading='auto')
ax.set_theta_direction(-1)
ax.set_theta_offset(np.pi / 2.0)
ax.grid(True)
Part 2 The required code and output.
#
# Part 2
#
flatMap = ccrs.PlateCarree()
resolution = '110m'
fig = plt.figure(figsize=(12,6), dpi=96)
ax = fig.add_subplot(111, projection=flatMap)
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land',
resolution, edgecolor='black', alpha=0.7,
facecolor=cfeature.COLORS['land']))
ax.set_extent([-180, 180, -90, 90], crs=ccrs.PlateCarree())
def scale_position(lat_deg, lon_deg, rad_deg):
# Two operations:
# 1. manipulates X,Y data and get (XX,YY)
# 2. create proper projection of (XX,YY), `rotpole_proj`
# Returns: XX,YY,rotpole_proj
# For X data
XX = X/np.pi*180 #always wrap around EW direction
# For Y data
# The cone data: min=0, max=10 --> (90-rad),90
# rad_deg = radius of the display area
top = 90
btm = top-rad_deg
YY = btm + (Y/Y.max())*rad_deg
# The proper coordinate system
rotpole_proj = ccrs.RotatedPole(pole_latitude=lat_deg, pole_longitude=lon_deg)
# Finally,
return XX,YY,rotpole_proj
# Location 1 (Asia)
XX1, YY1, rotpole_proj1 = scale_position(20, 100, 20)
ax.pcolormesh(XX1, YY1, Z, cmap=mpl.cm.jet_r,
transform=rotpole_proj1)
# Location 2 (Europe)
XX2, YY2, rotpole_proj2 = scale_position(62, -6, 8)
ax.pcolormesh(XX2, YY2, Z, cmap=mpl.cm.jet_r,
transform=rotpole_proj2)
# Location 3 (N America)
XX3, YY3, rotpole_proj3 = scale_position(29, -75, 30)
ax.pcolormesh(XX3, YY3, Z, cmap=mpl.cm.jet_r,shading='auto',
transform=rotpole_proj3)
#gc.collect()
plt.show()
This solution does NOT account for the projection point being at some altitude above the globe... I can do that part, so I really have trouble mapping the meshgrid to lon/lat so the work with the PREVIOUSLY GENERATES values of Z.
Here's a simple mapping directly from polar to cart:
X_cart = np.array([[p*np.sin(t) for p in phis] for t in thetas]).T
Y_cart = np.array([[p*np.cos(t) for p in phis] for t in thetas]).T
# Need to map cartesian XY to Z that is compatbile with above...
Z_cart = np.sin(X)**10 + np.cos(10 + Y*X) * np.cos(X) # This Z does NOT map to cartesian X,Y
print(X_cart.shape,Y_cart.shape,Z_cart.shape)
flatMap = ccrs.PlateCarree()
resolution = '110m'
fig = plt.figure(figsize=(12,6), dpi=96)
ax = fig.add_subplot(111, projection=flatMap)
ax.imshow(np.tile(np.array([[cfeature.COLORS['water'] * 255]], dtype=np.uint8), [2, 2, 1]), origin='upper', transform=ccrs.PlateCarree(), extent=[-180, 180, -180, 180])
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', resolution, edgecolor='black', facecolor=cfeature.COLORS['land']))
im = ax.pcolormesh(X_cart*2,Y_cart*2, Z_cart, cmap=mpl.cm.jet_r, shading='auto') # c=mapper.to_rgba(Z_cart), cmap=mpl.cm.jet_r)
gc.collect()
Which maps the polar plot center to lon/lat (0,0):
I'm close... I somehow need to move my cartesian coords to the proper lon/lat (the satellite sub-point) and then scale it appropriately. Have the set of lon/lat but I'm screwing up the meshgrid somehow... ???
The sphere_intersect() routine returns lon/lat for projection of theta/phi on the globe (that works)... The bit that doesn't work is building the meshgrid that replaces X,Y:
lons = np.array([orbits.sphere_intersect(SV_pos_vec, SV_vel_vec, az << u.deg, el << u.deg,
lonlat=True)[0] for az in thetas for el in phis], dtype='object')
lats = np.array([orbits.sphere_intersect(SV_pos_vec, SV_vel_vec, az << u.deg, el << u.deg,
lonlat=True)[1] for az in thetas for el in phis], dtype='object')
long, latg = np.meshgrid(lons,lats) # THIS IS A PROBLEM I BELIEVE...
and the pcolormesh makes a mess...

Changing the origin of a 3d plot

I am trying to graph a 3d plot of the function f(x,y,z) = cos(x) + cos(y) + cos(z). I found a code off here that I copied and pasted and it looked like this.
def fun(x, y, z):
return cos(x) + cos(y) + cos(z)
x, y, z = pi*np.mgrid[-1:1:31j, -1:1:31j, -1:1:31j]
vol = fun(x, y, z)
iso_val=0.0
verts, faces, _, _ = measure.marching_cubes(vol, iso_val, spacing=(0.1, 0.1, 0.1))
fig = plt.figure(figsize = (10,6))
ax = fig.add_subplot(111, projection='3d')
p = ax.plot_trisurf(verts[:, 0], verts[:,1], faces, verts[:, 2],
cmap='Spectral', lw=1)
fig.colorbar(p, ax=ax, pad = 0.1)
plt.show()
Then, I noticed that it the labels in xyz and I want the origin to be on the center of the plot and not on the corners (kind of like this)
I don't want to just mess with the labels of the graph, I want the array to reflect that the origin has been changed. I don't know how to do this, like, do I just subtract -1.5 on everything to center the whole plot?

Plot scaled and rotated bivariate distribution using matplotlib

I am trying to plot a bivariate gaussian distribution using matplotlib. I want to do this using the xy coordinates of two scatter points (Group A), (Group B).
I want to adjust the distribution by adjusting the COV matrix to account for each Groups velocity and their distance to an additional xy coordinate used as a reference point.
I've calculated the distance of each groups xy coordinate to that of the reference point. The distance is expressed as a radius, labelled [GrA_Rad],[GrB_Rad].
So the further they are away from the reference point the greater the radius. I've also calculated velocity labelled [GrA_Vel],[GrB_Vel]. The direction of each group is expressed as the orientation. This is labelled [GrA_Rotation],[GrB_Rotation]
Question on how I want the distribution to be adjusted for velocity and distance (radius):
I'm hoping to use SVD. Specifically, if I have the rotation angle of each scatter, this provides the direction. The velocity can be used to describe a scaling matrix [GrA_Scaling],[GrB_Scaling]. So this scaling matrix can be used to expand the radius in the x-direction and contract the radius in the y-direction. This expresses the COV matrix.
Finally, the distribution mean value is found by translating the groups location (x,y) by half the velocity.
Put simply: the radius is applied to each group's scatter point. The COV matrix is adjusted by the radius and velocity. So using the scaling matrix to expand the radius in x-direction and contract in y-direction. The direction is measured from the rotation angle. Then determine the distribution mean value by translating the groups location (x,y) by half the velocity.
Below is the df of these variables
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.animation as animation
d = ({
'Time' : [1,2,3,4,5,6,7,8],
'GrA_X' : [10,12,17,16,16,14,12,8],
'GrA_Y' : [10,12,13,7,6,7,8,8],
'GrB_X' : [5,8,13,16,19,15,13,5],
'GrB_Y' : [6,15,12,7,8,9,10,8],
'Reference_X' : [6,8,14,18,13,11,16,15],
'Reference_Y' : [10,12,8,12,15,12,10,8],
'GrA_Rad' : [8.3,8.25,8.2,8,8.15,8.15,8.2,8.3],
'GrB_Rad' : [8.3,8.25,8.3,8.4,8.6,8.4,8.3,8.65],
'GrA_Vel' : [0,2.8,5.1,6.1,1.0,2.2,2.2,4.0],
'GrB_Vel' : [0,9.5,5.8,5.8,3.16,4.12,2.2,8.2],
'GrA_Scaling' : [0,0.22,0.39,0.47,0.07,0.17,0.17,0.31],
'GrB_Scaling' : [0,0.53,0.2,0.2,0.06,0.1,0.03,0.4],
'GrA_Rotation' : [0,45,23.2,-26.56,-33.69,-36.86,-45,-135],
'GrB_Rotation' : [0,71.6,36.87,5.2,8.13,16.70,26.57,90],
})
df = pd.DataFrame(data = d)
I've made an animated plot of each xy coordinate.
GrA_X = [10,12,17,16,16,14,12,8]
GrA_Y = [10,12,13,7,6,7,8,8]
GrB_X = [5,8,13,16,19,15,13,5]
GrB_Y = [6,15,12,10,8,9,10,8]
Item_X = [6,8,14,18,13,11,16,15]
Item_Y = [10,12,8,12,15,12,10,8]
scatter_GrA = ax.scatter(GrA_X, GrA_Y)
scatter_GrB = ax.scatter(GrB_X, GrB_Y)
scatter_Item = ax.scatter(Item_X, Item_Y)
def animate(i) :
scatter_GrA.set_offsets([[GrA_X[0+i], GrA_Y[0+i]]])
scatter_GrB.set_offsets([[GrB_X[0+i], GrB_Y[0+i]]])
scatter_Item.set_offsets([[Item_X[0+i], Item_Y[0+i]]])
ani = animation.FuncAnimation(fig, animate, np.arange(0,9),
interval = 1000, blit = False)
Update
The question has been updated, and has gotten somewhat clearer. I've updated my code to match. Here's the latest output:
Aside from the styling, I think this matches what the OP described.
Here's the code that was used to produce the above plot:
dfake = ({
'GrA_X' : [15,15],
'GrA_Y' : [15,15],
'Reference_X' : [15,3],
'Reference_Y' : [15,15],
'GrA_Rad' : [15,25],
'GrA_Vel' : [0,10],
'GrA_Scaling' : [0,0.5],
'GrA_Rotation' : [0,45]
})
dffake = pd.DataFrame(dfake)
fig,axs = plt.subplots(1, 2, figsize=(16,8))
fig.subplots_adjust(0,0,1,1)
plotone(dffake, 'A', 0, xlim=(0,30), ylim=(0,30), fig=fig, ax=axs[0])
plotone(dffake, 'A', 1, xlim=(0,30), ylim=(0,30), fig=fig, ax=axs[1])
plt.show()
and the complete implementation of the plotone function that I used is in the code block below. If you just want to know about the math used to generate and transform the 2D gaussian PDF, check out the mvpdf function (and the rot and getcov functions it depends on):
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.stats as sts
def rot(theta):
theta = np.deg2rad(theta)
return np.array([
[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]
])
def getcov(radius=1, scale=1, theta=0):
cov = np.array([
[radius*(scale + 1), 0],
[0, radius/(scale + 1)]
])
r = rot(theta)
return r # cov # r.T
def mvpdf(x, y, xlim, ylim, radius=1, velocity=0, scale=0, theta=0):
"""Creates a grid of data that represents the PDF of a multivariate gaussian.
x, y: The center of the returned PDF
(xy)lim: The extent of the returned PDF
radius: The PDF will be dilated by this factor
scale: The PDF be stretched by a factor of (scale + 1) in the x direction, and squashed by a factor of 1/(scale + 1) in the y direction
theta: The PDF will be rotated by this many degrees
returns: X, Y, PDF. X and Y hold the coordinates of the PDF.
"""
# create the coordinate grids
X,Y = np.meshgrid(np.linspace(*xlim), np.linspace(*ylim))
# stack them into the format expected by the multivariate pdf
XY = np.stack([X, Y], 2)
# displace xy by half the velocity
x,y = rot(theta) # (velocity/2, 0) + (x, y)
# get the covariance matrix with the appropriate transforms
cov = getcov(radius=radius, scale=scale, theta=theta)
# generate the data grid that represents the PDF
PDF = sts.multivariate_normal([x, y], cov).pdf(XY)
return X, Y, PDF
def plotmv(x, y, xlim=None, ylim=None, radius=1, velocity=0, scale=0, theta=0, xref=None, yref=None, fig=None, ax=None):
"""Plot an xy point with an appropriately tranformed 2D gaussian around it.
Also plots other related data like the reference point.
"""
if xlim is None: xlim = (x - 5, x + 5)
if ylim is None: ylim = (y - 5, y + 5)
if fig is None:
fig = plt.figure(figsize=(8,8))
ax = fig.gca()
elif ax is None:
ax = fig.gca()
# plot the xy point
ax.plot(x, y, '.', c='C0', ms=20)
if not (xref is None or yref is None):
# plot the reference point, if supplied
ax.plot(xref, yref, '.', c='w', ms=12)
# plot the arrow leading from the xy point
if velocity > 0:
ax.arrow(x, y, *rot(theta) # (velocity, 0),
width=.4, length_includes_head=True, ec='C0', fc='C0')
# fetch the PDF of the 2D gaussian
X, Y, PDF = mvpdf(x, y, xlim=xlim, ylim=ylim, radius=radius, velocity=velocity, scale=scale, theta=theta)
# normalize PDF by shifting and scaling, so that the smallest value is 0 and the largest is 1
normPDF = PDF - PDF.min()
normPDF = normPDF/normPDF.max()
# plot and label the contour lines of the 2D gaussian
cs = ax.contour(X, Y, normPDF, levels=6, colors='w', alpha=.5)
ax.clabel(cs, fmt='%.3f', fontsize=12)
# plot the filled contours of the 2D gaussian. Set levels high for smooth contours
cfs = ax.contourf(X, Y, normPDF, levels=50, cmap='viridis', vmin=-.9, vmax=1)
# create the colorbar and ensure that it goes from 0 -> 1
cbar = fig.colorbar(cfs, ax=ax)
cbar.set_ticks([0, .2, .4, .6, .8, 1])
# add some labels
ax.grid()
ax.set_xlabel('X distance (M)')
ax.set_ylabel('Y distance (M)')
# ensure that x vs y scaling doesn't disrupt the transforms applied to the 2D gaussian
ax.set_aspect('equal', 'box')
return fig, ax
def fetchone(df, l, i, **kwargs):
"""Fetch all the needed data for one xy point
"""
keytups = (
('x', 'Gr%s_X'%l),
('y', 'Gr%s_Y'%l),
('radius', 'Gr%s_Rad'%l),
('velocity', 'Gr%s_Vel'%l),
('scale', 'Gr%s_Scaling'%l),
('theta', 'Gr%s_Rotation'%l),
('xref', 'Reference_X'),
('yref', 'Reference_Y')
)
ret = {k:df.loc[i, l] for k,l in keytups}
# add in any overrides
ret.update(kwargs)
return ret
def plotone(df, l, i, xlim=None, ylim=None, fig=None, ax=None, **kwargs):
"""Plot exactly one point from the dataset
"""
# look up all the data to plot one datapoint
xydata = fetchone(df, l, i, **kwargs)
# do the plot
return plotmv(xlim=xlim, ylim=ylim, fig=fig, ax=ax, **xydata)
Old answer -2
I've adjusted my answer to match the example the OP posted:
Here's the code that produced the above image:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.stats as sts
def rot(theta):
theta = np.deg2rad(theta)
return np.array([
[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]
])
def getcov(radius=1, scale=1, theta=0):
cov = np.array([
[radius*(scale + 1), 0],
[0, radius/(scale + 1)]
])
r = rot(theta)
return r # cov # r.T
def datalimits(*data, pad=.15):
dmin,dmax = min(d.min() for d in data), max(d.max() for d in data)
spad = pad*(dmax - dmin)
return dmin - spad, dmax + spad
d = ({
'Time' : [1,2,3,4,5,6,7,8],
'GrA_X' : [10,12,17,16,16,14,12,8],
'GrA_Y' : [10,12,13,7,6,7,8,8],
'GrB_X' : [5,8,13,16,19,15,13,5],
'GrB_Y' : [6,15,12,7,8,9,10,8],
'Reference_X' : [6,8,14,18,13,11,16,15],
'Reference_Y' : [10,12,8,12,15,12,10,8],
'GrA_Rad' : [8.3,8.25,8.2,8,8.15,8.15,8.2,8.3],
'GrB_Rad' : [8.3,8.25,8.3,8.4,8.6,8.4,8.3,8.65],
'GrA_Vel' : [0,2.8,5.1,6.1,1.0,2.2,2.2,4.0],
'GrB_Vel' : [0,9.5,5.8,5.8,3.16,4.12,2.2,8.2],
'GrA_Scaling' : [0,0.22,0.39,0.47,0.07,0.17,0.17,0.31],
'GrB_Scaling' : [0,0.53,0.2,0.2,0.06,0.1,0.03,0.4],
'GrA_Rotation' : [0,45,23.2,-26.56,-33.69,-36.86,-45,-135],
'GrB_Rotation' : [0,71.6,36.87,5.2,8.13,16.70,26.57,90],
})
df = pd.DataFrame(data=d)
limitpad = .5
clevels = 5
cflevels = 50
xmin,xmax = datalimits(df['GrA_X'], df['GrB_X'], pad=limitpad)
ymin,ymax = datalimits(df['GrA_Y'], df['GrB_Y'], pad=limitpad)
X,Y = np.meshgrid(np.linspace(xmin, xmax), np.linspace(ymin, ymax))
fig = plt.figure(figsize=(10,6))
ax = plt.gca()
Zs = []
for l,color in zip('AB', ('red', 'yellow')):
# plot all of the points from a single group
ax.plot(df['Gr%s_X'%l], df['Gr%s_Y'%l], '.', c=color, ms=15, label=l)
Zrows = []
for _,row in df.iterrows():
x,y = row['Gr%s_X'%l], row['Gr%s_Y'%l]
cov = getcov(radius=row['Gr%s_Rad'%l], scale=row['Gr%s_Scaling'%l], theta=row['Gr%s_Rotation'%l])
mnorm = sts.multivariate_normal([x, y], cov)
Z = mnorm.pdf(np.stack([X, Y], 2))
Zrows.append(Z)
Zs.append(np.sum(Zrows, axis=0))
# plot the reference points
# create Z from the difference of the sums of the 2D Gaussians from group A and group B
Z = Zs[0] - Zs[1]
# normalize Z by shifting and scaling, so that the smallest value is 0 and the largest is 1
normZ = Z - Z.min()
normZ = normZ/normZ.max()
# plot and label the contour lines
cs = ax.contour(X, Y, normZ, levels=clevels, colors='w', alpha=.5)
ax.clabel(cs, fmt='%2.1f', colors='w')#, fontsize=14)
# plot the filled contours. Set levels high for smooth contours
cfs = ax.contourf(X, Y, normZ, levels=cflevels, cmap='viridis', vmin=0, vmax=1)
# create the colorbar and ensure that it goes from 0 -> 1
cbar = fig.colorbar(cfs, ax=ax)
cbar.set_ticks([0, .2, .4, .6, .8, 1])
ax.set_aspect('equal', 'box')
Old answer -1
It's a little hard to tell exactly what you're after. It is possible to scale and rotate a multivariate gaussian distribution via its covariance matrix. Here's an example of how to do so based on your data:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.stats as sts
def rot(theta):
theta = np.deg2rad(theta)
return np.array([
[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]
])
def getcov(scale, theta):
cov = np.array([
[1*(scale + 1), 0],
[0, 1/(scale + 1)]
])
r = rot(theta)
return r # cov # r.T
d = ({
'Time' : [1,2,3,4,5,6,7,8],
'GrA_X' : [10,12,17,16,16,14,12,8],
'GrA_Y' : [10,12,13,7,6,7,8,8],
'GrB_X' : [5,8,13,16,19,15,13,5],
'GrB_Y' : [6,15,12,7,8,9,10,8],
'Reference_X' : [6,8,14,18,13,11,16,15],
'Reference_Y' : [10,12,8,12,15,12,10,8],
'GrA_Rad' : [8.3,8.25,8.2,8,8.15,8.15,8.2,8.3],
'GrB_Rad' : [8.3,8.25,8.3,8.4,8.6,8.4,8.3,8.65],
'GrA_Vel' : [0,2.8,5.1,6.1,1.0,2.2,2.2,4.0],
'GrB_Vel' : [0,9.5,5.8,5.8,3.16,4.12,2.2,8.2],
'GrA_Scaling' : [0,0.22,0.39,0.47,0.07,0.17,0.17,0.31],
'GrB_Scaling' : [0,0.53,0.2,0.2,0.06,0.1,0.03,0.4],
'GrA_Rotation' : [0,45,23.2,-26.56,-33.69,-36.86,-45,-135],
'GrB_Rotation' : [0,71.6,36.87,5.2,8.13,16.70,26.57,90],
})
df = pd.DataFrame(data=d)
xmin,xmax = min(df['GrA_X'].min(), df['GrB_X'].min()), max(df['GrA_X'].max(), df['GrB_X'].max())
ymin,ymax = min(df['GrA_Y'].min(), df['GrB_Y'].min()), max(df['GrA_Y'].max(), df['GrB_Y'].max())
X,Y = np.meshgrid(
np.linspace(xmin - (xmax - xmin)*.1, xmax + (xmax - xmin)*.1),
np.linspace(ymin - (ymax - ymin)*.1, ymax + (ymax - ymin)*.1)
)
fig,axs = plt.subplots(df.shape[0], sharex=True, figsize=(4, 4*df.shape[0]))
fig.subplots_adjust(0,0,1,1,0,-.82)
for (_,row),ax in zip(df.iterrows(), axs):
for c in 'AB':
x,y = row['Gr%s_X'%c], row['Gr%s_Y'%c]
cov = getcov(scale=row['Gr%s_Scaling'%c], theta=row['Gr%s_Rotation'%c])
mnorm = sts.multivariate_normal([x, y], cov)
Z = mnorm.pdf(np.stack([X, Y], 2))
ax.contour(X, Y, Z)
ax.plot(row['Gr%s_X'%c], row['Gr%s_Y'%c], 'x')
ax.set_aspect('equal', 'box')
This outputs:

Visualizing spherical harmonics in Python

I am trying to draw a spherical harmonics for my college project. The following formula I want to depict,
Y = cos(theta)
for that, I wrote this code
import numpy as np
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
def sph2cart(r, phi, tta):
''' r is from 0 to infinity '''
''' phi is from 0 to 2*pi '''
''' tta is from 0 to pi '''
x = r* np.sin(tta)* np.cos(phi)
y = r* np.sin(tta)* np.sin(phi)
z = r* np.cos(tta)
return x, y, z
# phi running from 0 to pi and tta from 0 to pi
phi = np.linspace(0, 2* np.pi, 25)
tta = np.linspace(0, np.pi, 25)
# meshgrid to generate points
phi, tta = np.meshgrid(phi, tta)
# THIS IS THE FUNCTION
Y = np.cos(tta)
# finally all things in cartesian co-ordinate system
# Note that "Y" is acting as "r"
x, y, z = sph2cart( Y, phi, tta)
# plotting :-
fig = plt.figure()
ax = fig.add_subplot( 111 , projection='3d')
ax.plot_surface(x, y, z, linewidth = 0.5, edgecolors = 'k')
And, get the sphere as a result. Which is not correct, because actual result is dumbbell like shape. See the second row of this image,
https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Spherical_Harmonics.png/1024px-Spherical_Harmonics.png
The picture in the Wikipedia article Spherical harmonics is obtained by using the absolute value of a spherical harmonic as the r coordinate, and then coloring the surface according to the sign of the harmonic. Here is an approximation.
x, y, z = sph2cart(np.abs(Y), phi, tta)
fig = plt.figure()
ax = fig.add_subplot( 111 , projection='3d')
from matplotlib import cm
ax.set_aspect('equal')
ax.plot_surface(x, y, z, linewidth = 0.5, facecolors = cm.jet(Y), edgecolors = 'k')
When you use Y itself as r, the two hemispheres (positive Y and negative Y) end up mapped onto the same half of the above surface.
The Y you are passing to the function needs to be an absolute value to make it r, else z = cos(theta)^2 is always positive. If r is to be the radius then this what you should be doing.
x, y, z = sph2cart(np.abs(Y), phi, tta)

How to plot a point inside a surface in 3d in python (matplotlib)?

I'm able to plot a surface in 3d in matplotlib, but I also need to plot a line, and a point on the surface. The surface that the line are fine, but the point does not show up on the surface for some reason, though. Here is the code:
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(-2.0, 2.0, 0.05)
Y = np.arange(-1.0, 3.0, 0.05)
X, Y = np.meshgrid(X, Y)
Z = (np.ones([np.shape(X)[0],np.shape(X)[1]])-X)**2+100*(Y-(X)**2)**2
Gx, Gy = np.gradient(Z) # gradients with respect to x and y
G = (Gx**2+Gy**2)**.5 # gradient magnitude
N = G/G.max() # normalize 0..1
surf = ax.plot_surface(
X, Y, Z, rstride=1, cstride=1,
facecolors=cm.jet(N),
linewidth=0,
antialiased=False,
shade=False)
plt.hold(True)
ax.hold(True)
# add the unit circle
x_1 = np.arange(-1.0, 1.0, 0.005)
x_2 = np.arange(-1.0, 1.0, 0.005)
y_1 = np.sqrt(np.ones(len(x_1)) - x_1**2)
y_2 = -np.sqrt(np.ones(len(x_2)) - x_2**2)
x = np.array(x_1.tolist() + x_2.tolist())
y = np.array(y_1.tolist() + y_2.tolist())
z = (np.ones(len(x))-x)**2+100*(y-(x)**2)**2
ax.plot(x, y, z, '-k')
plt.hold(True)
ax.hold(True)
ax.scatter(np.array([0.8]),
np.array([0.6]),
np.array([0.045]),
color='red',
s=40
)
# Get current rotation angle
print 'rotation angle is ', ax.azim
# Set rotation angle to 60 degrees
ax.view_init(azim=60)
plt.xlabel('x')
plt.ylabel('y')
plt.show()
The issue is that the point does not show up on the surface. Now, when I replace this code:
ax.scatter(np.array([0.8]),
np.array([0.6]),
np.array([0.045]),
color='red',
s=40
)
...with this code (i.e. just adding to the last value)...
ax.scatter(np.array([0.8]),
np.array([0.6]),
np.array([0.045+800]),
color='red',
s=40
)
...then it shows up. But I can't think of a reason why it is not showing up when I want to plot the actual value in the surface. Does someone know how to fix this?
(As an aside, I'd love to get rid of the weird line in the middle of the unit circle that I plot on the surface. I can't seem to get rid of it.)
Much obliged!

Categories