How to plot a donut chart around a point on a scatterplot? - python

I have a scatterplot with a few points which I can plot easily enough. I want to add a donut chart around each of the points to indicate which classes make up the point. I saw the example of nested donut charts but I want to make a scatter/donut plot for multiple points.
This is the code I have so far for making the scatterplot and the donut chart. It will plot all 3 data points and one donut chart for the first point.
import numpy as np
import matplotlib.pyplot as plt
# Fixing random state for reproducibility
np.random.seed(19680801)
## Scatter
# create three data points with three random class makeups
N = 3
N_class = 5
x = np.random.rand(N)
y = np.random.rand(N)
vals = [np.random.randint(2, size=N_class) for _ in range(N)]
plt.scatter(x, y, s=500)
plt.show()
## Donut plot
# Create 5 equal sized wedges
size_of_groups = np.ones(5)
# Create a pieplot
plt.pie(size_of_groups, colors=["grey" if val == 0 else "red" for val in vals[0]])
#plt.show()
# add a circle at the center
my_circle=plt.Circle( (0,0), 0.7, color='white')
p = plt.gcf()
p.gca().add_artist(my_circle)
plt.show()
Something similar to this for each point (disregarding the pie chart center, just a scatter point)

Adapting the Scatter plot with pie chart markers example, one can just add a white marker in the middle to make the pies become donuts.
import numpy as np
import matplotlib.pyplot as plt
# first define the ratios
r1 = 0.2 # 20%
r2 = r1 + 0.4 # 40%
# define some sizes of the scatter marker
sizes = np.array([60, 80, 120])*4
center_sizes = sizes/3.
# calculate the points of the first pie marker
#
# these are just the origin (0,0) +
# some points on a circle cos,sin
x = [0] + np.cos(np.linspace(0, 2 * np.pi * r1, 10)).tolist()
y = [0] + np.sin(np.linspace(0, 2 * np.pi * r1, 10)).tolist()
xy1 = np.column_stack([x, y])
s1 = np.abs(xy1).max()
x = [0] + np.cos(np.linspace(2 * np.pi * r1, 2 * np.pi * r2, 10)).tolist()
y = [0] + np.sin(np.linspace(2 * np.pi * r1, 2 * np.pi * r2, 10)).tolist()
xy2 = np.column_stack([x, y])
s2 = np.abs(xy2).max()
x = [0] + np.cos(np.linspace(2 * np.pi * r2, 2 * np.pi, 10)).tolist()
y = [0] + np.sin(np.linspace(2 * np.pi * r2, 2 * np.pi, 10)).tolist()
xy3 = np.column_stack([x, y])
s3 = np.abs(xy3).max()
fig, ax = plt.subplots()
ax.scatter(range(3), range(3), marker=xy1,
s=s1 ** 2 * sizes, facecolor='indigo')
ax.scatter(range(3), range(3), marker=xy2,
s=s2 ** 2 * sizes, facecolor='gold')
ax.scatter(range(3), range(3), marker=xy3,
s=s3 ** 2 * sizes, facecolor='crimson')
# centers
ax.scatter(range(3), range(3), s=center_sizes, marker="o", color="w")
plt.show()
If instead a real pie chart is desired, you may use the arguments center and radius to position several pies on the axes.
import matplotlib.pyplot as plt
# first define the ratios
r1 = 0.2 # 20%
r2 = r1 + 0.4 # 40%
x = list(range(3))
y = list(range(3))
fig, ax = plt.subplots()
for xi,yi in zip(x,y):
ax.pie([r1,r2,r2], colors=['indigo', "gold", 'crimson'],
center=(xi, yi), radius=0.2+xi/4,
wedgeprops=dict(width=(0.2+xi/4)/2), frame=True)
ax.autoscale()
plt.show()

Related

How to speed up matplotlib scatterpie chart?

Is there any way to speed up matplotlib scatter pie chart? I am currently using these draw_pie function and plotting around 3k points. It takes more than 30 minutes to plot all these points. Thats way slower than R's function which takes rougly 5min.
def draw_pie(dist,
xpos,
ypos,
size,
ax=None):
if ax is None:
fig, ax = plt.subplots(figsize=(10,8))
# for incremental pie slices
cumsum = np.cumsum(dist)
cumsum = cumsum/ cumsum[-1]
pie = [0] + cumsum.tolist()
for r1, r2 in zip(pie[:-1], pie[1:]):
angles = np.linspace(2 * np.pi * r1, 2 * np.pi * r2)
x = [0] + np.cos(angles).tolist()
y = [0] + np.sin(angles).tolist()
xy = np.column_stack([x, y])
ax.scatter([xpos], [ypos], marker=xy, s=size)
return ax

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()

How to draw circles on the perimeter of a circle?

I'm trying to plot something like this:
I don't know how to find the center of smaller circles in for loops. First, I've tried to plot it with smaller number of circles(for example 2) but I don't know why the smaller circles are semi-circles??
My try:
import numpy as np
import matplotlib.pyplot as plt
r = 2, h = 1, k = 1
axlim = r + np.max((abs(h),np.max(abs(k))))
x = np.linspace(-axlim, axlim, 100)
X,Y = np.meshgrid(x,x)
F = (X-h)**2 + (Y-k)**2 - r**2
plt.contour(X,Y,F,0)
F1 = (X-(h+r))**2 + (Y-k)**2 - (r/3)**2
plt.contour(X,Y,F1,0)
F2 = (X-h)**2 + (Y-(k+r))**2 - (r/3)**2
plt.contour(X,Y,F2,0)
plt.gca().set_aspect('equal')
plt.axis([-4*r, 4*r, -4*r,4*r])
# plt.axis('off')
plt.show()
The output:
Sine, cosine and an angle evenly divided over the range 0, 2picould be used:
import numpy as np
import matplotlib.pyplot as plt
num_circ = 7
rad_large = 7
rad_small = 6
thetas = np.linspace(0, 2 * np.pi, num_circ, endpoint=False)
fig, ax = plt.subplots()
ax.add_patch(plt.Circle((0, 0), rad_large, fc='none', ec='navy'))
for theta in thetas:
ax.add_patch(plt.Circle((rad_large * np.cos(theta), rad_large * np.sin(theta),), rad_small, fc='none', ec='crimson'))
ax.autoscale_view() # calculate the limits for the x and y axis
ax.set_aspect('equal') # show circles as circles
plt.show()

Contour density plot in matplotlib using polar coordinates

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

Python:Curved surface plot with density colors

I have irregularly spaced mesh points data in the form [[xi1,yi1,zi1], [xi2,yi2,zi2],....]. They form a part of a sphere
I also have data [[x1,y1,z1,n1],[x2,y2,z2,n2]....] where (x1,y1,z1) etc tells the coordinate of the midpoint of each mesh bin and ni are the densities at the corresponding locations. The 3d scatter plot with square markers of the data look like this (where the colors show the value of n)
its side view showing the curvature
I am trying to make this into a smooth surface plot. I have looked into this example matplotlib color but here the gridpoints are equally spaced while in my case they are not, also how would one represent the densities using color in such an irregular grid. I am open to trying other packages other than matplotlib.
Thanks
One method is to manually create and plot a collection of triangles:
(Edit: manually creating and coloring triangles around bin midpoints)
import numpy
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.tri import Triangulation
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import matplotlib.cm as cm
# Generate a dataset
R = 1
# bin midpoints
theta = numpy.linspace(numpy.pi/6, numpy.pi/3, 20) + numpy.pi / 2
phi = numpy.linspace(numpy.pi/6, numpy.pi/3, 20)
ttheta, pphi = numpy.meshgrid(theta, phi)
x = R * numpy.sin(ttheta) * numpy.cos(pphi)
y = R * numpy.sin(ttheta) * numpy.sin(pphi)
z = R * numpy.cos(ttheta)
n = numpy.exp(-(ttheta - numpy.pi/4 - numpy.pi/2)**2 * 20 - (pphi - numpy.pi/4)**2 * 20)
mappable = cm.ScalarMappable(cmap=cm.coolwarm, norm=matplotlib.colors.Normalize(vmin=0, vmax=1))
colors = mappable.to_rgba(n)
# Scatter plot
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
ax.scatter(x.flatten(), y.flatten(), z.flatten(), c=colors.reshape(x.size, 4))
ax.set_xlim(0.2, 0.8)
ax.set_ylim(0.2, 0.8)
ax.set_zlim(-0.9, -0.45)
ax.elev = 50
fig.savefig('t.png')
# Surface plot
# bin vertex spherical coordinates
dtheta = theta[1] - theta[0]
dphi = phi[1] - phi[0]
v_theta = numpy.concatenate([theta - dtheta/2, numpy.array([theta[-1] + dtheta/2])])
v_phi = numpy.concatenate([phi - dphi/2, numpy.array([phi[-1] + dphi/2])])
# bin vertex Cartesian coordinates
v_ttheta, v_pphi = numpy.meshgrid(v_theta, v_phi)
vx = R * numpy.sin(v_ttheta) * numpy.cos(v_pphi)
vy = R * numpy.sin(v_ttheta) * numpy.sin(v_pphi)
vz = R * numpy.cos(v_ttheta)
# Creating triangles and corresponding face colors
triangles = []
facecolors = []
for i in range(v_theta.size - 1):
for j in range(v_phi.size - 1):
triangles.extend([
[(i, j), (i + 1, j), (i, j + 1)],
[(i + 1, j), (i + 1, j + 1), (i, j + 1)]])
facecolors.extend([
colors[i, j],
colors[i, j]
])
triangle_vertices = numpy.array(
[[[vx[i,j], vy[i,j], vz[i,j]] for i, j in t] for t in triangles])
coll = Poly3DCollection(triangle_vertices, facecolors=facecolors, edgecolors=(0,0,0,0))
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
ax.add_collection(coll)
ax.set_xlim(0.2, 0.8)
ax.set_ylim(0.2, 0.8)
ax.set_zlim(-0.9, -0.45)
ax.elev = 50
fig.savefig('t2.png')
The scatter plot:
The surface plot:

Categories

Resources