Contour density plot in matplotlib using polar coordinates - python

From a set of angle (theta) and radius (r) I drew a scatter plot using matplotlib:
fig = plt.figure()
ax = plt.subplot(111, polar=True)
ax.scatter(theta, r, color='None', edgecolor='red')
ax.set_rmax(1)
plt.savefig("polar.eps",bbox_inches='tight')
Which gave me this figure
I now want to draw the density contour map on top of that, so I tried:
fig = plt.figure()
ax = plt.subplot(111, polar=True)
H, theta_edges, r_edges = np.histogram2d(theta, r)
cax = ax.contourf(theta_edges[:-1], r_edges[:-1], H, 10, cmap=plt.cm.Spectral)
ax.set_rmax(1)
plt.savefig("polar.eps",bbox_inches='tight')
Which gave me the following results that is obviously not what I wanted to do.
What am I doing wrong ?

I think that the solution to your problem is to define the bins arrays for your histogram (for instance a linspaced array between 0 and 2pi for theta and between 0 and 1 for r). This can be done with the bins or range arguments of function numpy.histogram
I you do so, make sure that the theta values are all between 0 and 2pi by plotting theta % (2 * pi) instead of theta.
Finally, you may choose to plot the middle of the bin edges instead of the left side of the bins as done in your example (use 0.5 * (r_edges[1:] + r_edges[:-1]) instead of r_edges[:-1])
below is a suggestion of code
import matplotlib.pyplot as plt
import numpy as np
#create the data
r1 = .2 + .2 * np.random.randn(200)
theta1 = 0. + np.pi / 7. * np.random.randn(len(r1))
r2 = .8 + .2 * np.random.randn(300)
theta2 = .75 * np.pi + np.pi / 7. * np.random.randn(len(r2))
r = np.concatenate((r1, r2))
theta = np.concatenate((theta1, theta2))
fig = plt.figure()
ax = plt.subplot(111, polar=True)
#define the bin spaces
r_bins = np.linspace(0., 1., 12)
N_theta = 36
d_theta = 2. * np.pi / (N_theta + 1.)
theta_bins = np.linspace(-d_theta / 2., 2. * np.pi + d_theta / 2., N_theta)
H, theta_edges, r_edges = np.histogram2d(theta % (2. * np.pi), r, bins = (theta_bins, r_bins))
#plot data in the middle of the bins
r_mid = .5 * (r_edges[:-1] + r_edges[1:])
theta_mid = .5 * (theta_edges[:-1] + theta_edges[1:])
cax = ax.contourf(theta_mid, r_mid, H.T, 10, cmap=plt.cm.Spectral)
ax.scatter(theta, r, color='k', marker='+')
ax.set_rmax(1)
plt.show()
which should result as

Related

Conformal Color Mapping of Complex Functions with 3 or more variables

I used the following code to color map the complex plane onto a hypocycloid. No problems there.
import numpy as np
import matplotlib.pyplot as plt
#setting up the meshgrid for the input space
a = np.arange(-np.pi, np.pi, 0.1)
b = np.arange(-np.pi, np.pi, 0.1)
A, B = np.meshgrid(a, b)
#setting parameters for plotting a circle
angle = np.arange(0, 2*np.pi, 2*np.pi/100)
R = 1
p = R*np.cos(angle)
q = R*np.sin(angle)
#defining a function to color the plane
fx = np.sqrt(A**2 + B**2)
fig, (ax1, ax2) = plt.subplots(1, 2,figsize=(10,5))
ax1.pcolormesh(A,B, fx)
y = (1/3)*(np.exp(1j*A) + np.exp(1j*(B-A)) + np.exp(-1j*B))
# extract real and imaginary parts using numpy
real = y.real
imag = y.imag
# plot the complex numbers
ax2.scatter(real, imag, c=fx, s=7, edgecolor='k', linewidths=0.1)
ax2.plot(p, q, '--k')
#labeling plots
ax1.set_xlim(-np.pi, np.pi)
ax1.set_ylim(-np.pi, np.pi)
ax1.set_ylabel('Imaginary')
ax1.set_xlabel('Real')
ax1.set_title('Input Plane')
ax2.set_xlim(-1.05, 1.05)
ax2.set_ylim(-1.05, 1.05)
ax2.set_ylabel('Imaginary')
ax2.set_xlabel('Real')
ax2.set_title('Output Plane')
plt.grid('--k', axis='both')
plt.show()
Now I would like to extend this so that I can add more variables. For example, using the function y = np.exp(1jA) + np.exp(1j(B-A)) + np.exp(1j*(C-B)) + np.exp(1j*(D-C)) + np.exp(-1j*D)
which would require a 4D meshgrid?? Somehow, updating the meshgrid and using this function doesn't work. How can I fix this?

The scaling of my plots make the curves appear the same when they are not. How to shape them so that the difference can be seen?

I have placed my two plots side by side.
However, I have noticed that the plots have been shaped to be the same size, and this has caused the distribution curves to appear the same when I know they are not. The Cobalt curve should be shorter and fatter than the Rhodium curve.
fig, (ax1, ax2) = plt.subplots(1, 2)
mu = Mean_Sd(rhodium_data, "Mean all Angles")[2]
sigma = Mean_Sd(rhodium_data, "Mean all Angles")[3]
x = mu + sigma * np.random.randn(437)
num_bins = 50
n, bins, patches = ax.hist(x, num_bins, density=1) # creates histogram
# line of best fit
y = ((1 / (np.sqrt(2 * np.pi) * sigma)) *
np.exp(-0.5 * (1 / sigma * (bins - mu))**2))
#Creating the plot graphic
ax1.plot(bins, y, '-')
ax1.tick_params(top=True, right=True)
ax1.tick_params(direction='in', length=6, width=1, colors='0')
ax1.grid()
ax1.set_xlabel("Mean of the Four Angles")
ax1.set_ylabel("Probability density")
ax1.set_title(r"Rhodium Distribution")
#####-----------------------------------------------------------------------------------####
mu = Mean_Sd(cobalt_data, "Mean all Angles")[2]
sigma = Mean_Sd(cobalt_data, "Mean all Angles")[3]
x = mu + sigma * np.random.randn(437)
num_bins = 50
n, bins, patches = ax.hist(x, num_bins, density=1) # creates histogram
# line of best fit
y = ((1 / (np.sqrt(2 * np.pi) * sigma)) *
np.exp(-0.5 * (1 / sigma * (bins - mu))**2))
#Creating the plot graphic
ax2.plot(bins, y, '-')
ax2.tick_params(top=True, right=True)
ax2.tick_params(direction='in', length=6, width=1, colors='0')
ax2.grid()
ax2.set_xlabel("Mean of the Four Angles")
ax2.set_ylabel("Probability density")
ax2.set_title(r"Cobalt Distribution")
####----------------------------------------------------------------------------------####
fig.tight_layout()
plt.show()
Here is my code. I'm working with Python 3 on Jupyter Notebooks.
Edit
The mean of 'Mean all Angles' from 'Cobalt Data' is 105.1 Degrees. The standard deviation of 'Mean all Angles' from column 'Cobalt Data' is 7.866 Degrees.
The mean of 'Mean all Angles' from 'Rhodium Data' is 90.19 Degrees. The standard deviation of 'Mean all Angles' from column 'Rhodium Data' is 1.35 Degrees.
mu will be the mean, and sigma is the standard deviation.
Rhodium: mu = 90.19. sigma = 1.35
Cobalt: mu = 105.1. sigma = 7.866
As you have pointed out, the range difference between the two distributions is substantial. You could try to set ax1.set_xlim, ax1.set_ylim, ax2.set_xlim, ax2.set_ylim, but in my opinion at least one subplot would end up to be hardly legible.
What if you combine the two subplots into one?
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(1)
mu = 105.1
sigma = 7.866
x1 = mu + sigma * np.random.randn(437)
num_bins = 50
n, bins1, patches = ax.hist(x1, num_bins, density=1, color="tab:blue", alpha=0.4) # creates histogram
# line of best fit
y1 = ((1 / (np.sqrt(2 * np.pi) * sigma)) *
np.exp(-0.5 * (1 / sigma * (bins1 - mu))**2))
#####-----------------------------------------------------------------------------------####
mu = 90.19
sigma = 1.35
x2 = mu + sigma * np.random.randn(437)
num_bins = 50
n, bins2, patches = ax.hist(x2, num_bins, density=1, color="tab:orange", alpha=0.4) # creates histogram
# line of best fit
y2 = ((1 / (np.sqrt(2 * np.pi) * sigma)) *
np.exp(-0.5 * (1 / sigma * (bins2 - mu))**2))
#Creating the plot graphic
ax.plot(bins1, y1, '-', label="Rhodium Distribution", color="tab:blue")
ax.plot(bins2, y2, '-', label="Cobalt Distribution", color="tab:orange")
ax.set_xlabel("Mean of the Four Angles")
ax.grid()
ax.set_ylabel("Probability density")
ax.tick_params(top=True, right=True)
ax.tick_params(direction='in', length=6, width=1, colors='0')
ax.legend()
ax.grid(which='major', axis='x', linewidth=0.75, linestyle='-', color='0.85')
ax.grid(which='minor', axis='x', linewidth=0.25, linestyle='--', color='0.80')
ax.grid(which='major', axis='y', linewidth=0.75, linestyle='-', color='0.85')
ax.grid(which='minor', axis='y', linewidth=0.25, linestyle='--', color='0.80')
ax.minorticks_on()
####----------------------------------------------------------------------------------####
fig.tight_layout()
plt.show()

Filling area below function on 3d plot of 2d slices in Matplotlib

I would like to make a 3D plot with several 2D line plot "slices" and shade the area between the x-axis and the curve (i.e. under the curve). When trying to do this with polygons I am getting filling but the correct areas are not being filled. Any help would be most appreciated!
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(15,15))
ax = fig.add_subplot(111, projection='3d')
colors = ['r','b','g','m']
phi = [0,np.pi/4,np.pi/3, np.pi/2]
for c, k in zip(colors, phi):
eps2 = 0.001j
eps = np.linspace(-3,3,10000)
E = eps + eps2
gR = ((1-(((np.cos(k)+np.sin(k)*1j)**2)/((E+np.sqrt(1-E**2)*1j)**4)))/(1+(((np.cos(k)+np.sin(k)*1j)**2)/((E+np.sqrt(1-E**2)*1j)**4))))*1j
N = gR.imag
utol = 2
N[N>utol] = 2
ax.plot(eps, N, k,zdir='y', color=c)
verts = [list(zip(eps,N))]
poly = PolyCollection(verts, facecolors=c)
poly.set_alpha(1)
ax.add_collection3d(poly, zs=k,zdir='y')
ax.set_xlabel('Energy')
ax.set_ylabel('Phi')
ax.set_zlabel('DOS')
ax.set_yticks(phi)
ax.set_zlim(0,2)
ax.set_ylim(0,2)
plt.show()
Incorrect Plot for reference:
You created a polygon by connecting the first and last vertex of your curves. As these vertices have y = 2 everything gets connected with the horizontal line at that y-value.
To close the polygon at zero, repeat the first and the last x-value (np.pad(eps, 1, mode='edge')) and pad the y-values with a zero at both ends (np.pad(N, 1)).
If desired, ax.set_yticklabels(...) can show the y-ticks as a formula with pi.
Further, matplotlib seems to have a serious problem about deciding the relative depth of each polygon, showing them all mixed up. A workaround could be to rotate everything 180 degrees, e.g. by setting ax.view_init(elev=22, azim=130).
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(15, 15))
ax = fig.add_subplot(111, projection='3d')
colors = ['r', 'b', 'g', 'm']
phi = [0, np.pi / 4, np.pi / 3, np.pi / 2]
for c, k in zip(colors, phi):
eps2 = 0.001j
eps = np.linspace(-3, 3, 10000)
E = eps + eps2
gR = ((1 - (((np.cos(k) + np.sin(k) * 1j) ** 2) / ((E + np.sqrt(1 - E ** 2) * 1j) ** 4))) / (
1 + (((np.cos(k) + np.sin(k) * 1j) ** 2) / ((E + np.sqrt(1 - E ** 2) * 1j) ** 4)))) * 1j
N = gR.imag
utol = 2
N[N > utol] = 2
ax.plot(eps, N, k, zdir='y', color=c)
verts = [list(zip(np.pad(eps, 1, mode='edge'), np.pad(N, 1)))]
poly = PolyCollection(verts, facecolors=c)
poly.set_alpha(1)
ax.add_collection3d(poly, zs=k, zdir='y')
ax.set_xlabel('Energy')
ax.set_ylabel('Phi')
ax.set_zlabel('DOS')
ax.set_yticks(phi)
ax.set_yticklabels(['$0$' if k == 0 else f'$\pi / {np.pi / k:.0f}$' for k in phi])
ax.set_zlim(0, 2)
ax.set_ylim(0, 2)
ax.view_init(elev=22, azim=130)
plt.show()

Zoom in on polar plot

I'd like to create an inset within my fig which is a zoom in on part of my polar plot.
I've tried various different methods but can't seem to crack the correct way to do using matplotlib. My code to create the plot (from my pandas dataframe) is below. I've also included the plot that it produces.
def plot_polar_chart_new(n, start, df, sales, title):
HSV_tuples = [(x * 1.0 / n, 0.5, 0.5) for x in range(n)]
RGB_tuples = map(lambda x: colorsys.hsv_to_rgb(*x), HSV_tuples)
RGB_normalised = [tuple(n / max(t) for n in t) for t in RGB_tuples]
figsize=(15, 15)
fig = mpl.pyplot.figure(figsize=figsize)
ax = fig.add_subplot(1,1,1, polar=True)
start = 0
prev_count = 0
for i, salesperson in enumerate(sales):
count, division = (df[salesperson], df.index)
ax.bar((division - start) * 2 * np.pi / N, height=count, width=2 * np.pi / N, color=RGB_normalised[i], bottom=prev_count, label=salesperson)
prev_count += count
ax.set_xticks(np.linspace(0, 2 * np.pi, N, endpoint=False))
ax.set_xticklabels(range(start, N + start),fontsize=20)
ax.yaxis.set_tick_params(labelsize=20)
ax.set_theta_direction(-1)
ax.set_theta_offset(np.pi / 2.0)
ax.set_title(title, y=1.1, fontsize=20)
ax.legend(bbox_to_anchor=(0.9, 1.1), loc=2)
mpl.pyplot.show()
I'd like to create a plot inset which zooms in on part of the plot between 17 and 02.
Please help!
Thanks

Matplotlib ignore negative values in 3D plot

I have to plot a 3d function which has meaningless negative values (they should not appear in the plot). The function which has to be plot is like:
def constraint_function(x, y):
return min(
(1800 - 0.3 * x - 0.5 * y) / 0.4,
(500 - 0.1 * x - 0.08 * y) / 0.12,
(200 - 0.06 * x - 0.04 * y) / 0.05
)
I'm calculating the function the following way:
xs = np.linspace(0, 3600, 1000)
ys = np.linspace(0, 3600, 1000)
zs = np.empty(shape=(1000, 1000))
for ix, x in enumerate(xs):
for iy, y in enumerate(ys):
zs[ix][iy] = constraint_function(x, y)
xs, ys = np.meshgrid(xs, ys)
The function has valid values mostly in the square [0, 3600]x[0, 3600]. The first approach I had is setting the axis limits to fit my needs:
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.azim = 20
ax.set_xlim(0, 3500)
ax.set_ylim(0, 3500)
ax.set_zlim(0, 4500)
ax.plot_surface(xs, ys, zs)
plt.show()
Which results in the following plot:
It just ignored the limits and did plot it anyway. The second approach was defining the negative values as np.nan changing the function to be as:
def constraint_function(x, y):
temp = min(
(1800 - 0.3 * x - 0.5 * y) / 0.4,
(500 - 0.1 * x - 0.08 * y) / 0.12,
(200 - 0.06 * x - 0.04 * y) / 0.05
)
return temp if temp >= 0 else np.nan
and setting the alpha of invalid values to zero:
plt.cm.jet.set_bad(alpha=0.0)
ax.azim = 20
ax.set_xlim(0, 3500)
ax.set_ylim(0, 3500)
ax.set_zlim(0, 4500)
ax.plot_surface(xs, ys, zs)
plt.show()
It leaves me with saw-like borders which is also something I don't want to have. Is there a way to get rid of these edges and getting a smooth line when the plot is turning negative?
First, your z-value array axes are reversed; it should be zs[iy][ix] not zs[ix][iy]. Because of this your plot is flipped left-for-right.
Second, building your z array by iterating in Python is much slower; you should instead delegate to numpy, like so:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
# create axis sample
xs = np.linspace(0, 3600, 1000)
ys = np.linspace(0, 3600, 1000)
# create mesh samples
xxs, yys = np.meshgrid(xs, ys)
# create data
zzs = np.min([
((1800 - 0.30 * xxs - 0.50 * yys) / 0.40),
(( 500 - 0.10 * xxs - 0.08 * yys) / 0.12),
(( 200 - 0.06 * xxs - 0.04 * yys) / 0.05)
], axis=0)
# clip data which is below 0.0
zzs[zzs < 0.] = np.NaN
NumPy vectorized operations are many times faster.
Third, there is nothing particularly wrong with your code except the sampling resolution is too low; set it higher,
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.azim = 20
ax.set_xlim(0, 3500)
ax.set_ylim(0, 3500)
ax.set_zlim(0, 4500)
ax.plot_surface(xxs, yys, zzs, rcount=200, ccount=200)
plt.show()
produces
Technically, you can skew the grid, such that the points of the grid, which would cause a zick-zack pattern are shifted such, that they lie on a line.
This is shown below.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
x=np.linspace(-5,5,6)
X,Y = np.meshgrid(x,x)
Z = X+Y
X[Z==-2] = X[Z==-2]+1
Y[Z==-2] = Y[Z==-2]+1
Z[Z==-2] = 0
Z[Z<0] = np.nan
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_zlim(0, 12)
ax.plot_surface(X, Y, Z)
plt.show()
The problem would now be to generalize this approach for arbitrary surfaces. It's sure possible but needs a bit of work.

Categories

Resources