How to draw polar hist2d/hexbin in matplotlib? - matplotlib

I have a random vector (random length and random angle) and would like to plot its approximate PDF (probability density function) via hist2d or hexbin. Unfortunately they seems not to work with polar plots, the following code yields nothing:
import numpy as np
import matplotlib.pyplot as plt
# Generate random data:
N = 1024
r = .5 + np.random.normal(size=N, scale=.1)
theta = np.pi / 2 + np.random.normal(size=N, scale=.1)
# Plot:
ax = plt.subplot(111, polar=True)
ax.hist2d(theta, r)
plt.savefig('foo.png')
plt.close()
I would like it to look like this: pylab_examples example code: hist2d_demo.py only in polar coordinates. The closest result so far is with colored scatter plot as adviced here:
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import gaussian_kde
# Generate random data:
N = 1024
r = .5 + np.random.normal(size=N, scale=.1)
theta = np.pi / 2 + np.random.normal(size=N, scale=.1)
# Plot:
ax = plt.subplot(111, polar=True)
# Using approach from:
# https://stackoverflow.com/questions/20105364/how-can-i-make-a-scatter-plot-colored-by-density-in-matplotlib
theta_r = np.vstack([theta,r])
z = gaussian_kde(theta_r)(theta_r)
ax.scatter(theta, r, c=z, s=10, edgecolor='')
plt.savefig('foo.png')
plt.close()
Image from the second version of the code
Is there a better way to make it more like real PDF generated with hist2d? This question seems to be relevant (the resulting image is as expected), but it looks messy.

One way to this using pcolormesh:
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import gaussian_kde
# Generate random data:
N = 10000
r = .5 + np.random.normal(size=N, scale=.1)
theta = np.pi / 2 + np.random.normal(size=N, scale=.1)
# Histogramming
nr = 50
ntheta = 200
r_edges = np.linspace(0, 1, nr + 1)
theta_edges = np.linspace(0, 2*np.pi, ntheta + 1)
H, _, _ = np.histogram2d(r, theta, [r_edges, theta_edges])
# Plot
ax = plt.subplot(111, polar=True)
Theta, R = np.meshgrid(theta_edges, r_edges)
ax.pcolormesh(Theta, R, H)
plt.show()
Result:
Note that the histogram is not yet normalized by the area of the bin, which is not constant in polar coordinates. Close to the origin, the bins are pretty small, so some other kind of meshing might be better.

Related

How to shared color palette between multiple subplots?

I have the following figure:
The figure is composed by the following code snippet:
fig = plt.figure(constrained_layout=True)
grid = fig.add_gridspec(2, 2)
ax_samples_losses = fig.add_subplot(grid[0, 0:])
ax_samples_losses.set_title('Avg. loss per train sample (epoch 0 excluded)')
for sample_idx, sample_avg_train_loss_history in enumerate(samples_avg_train_loss_history):
ax_samples_losses.plot(sample_avg_train_loss_history, label='Sample ' + str(sample_idx))
ax_samples_losses.set_title('Avg. loss per train sample (epoch 0 excluded)')
ax_samples_losses.set_xlabel('Epoch')
ax_samples_losses.set_ylabel('Sample avg. loss')
ax_samples_losses.set_xticks(range(1, epochs))
ax_samples_losses.tick_params(axis='x', rotation=90)
ax_samples_losses.yaxis.set_ticks(np.arange(0, np.max(samples_avg_train_loss_history), 0.25))
ax_samples_losses.tick_params(axis='both', which='major', labelsize=6)
plt.legend(bbox_to_anchor=(1, 1), prop={'size': 6}) #loc="upper left"
# fig.legend(...)
ax_patches_per_sample = fig.add_subplot(grid[1, 0])
#for sample_idx, sample_patches_count in enumerate(samples_train_patches_count):
# ax_patches_per_sample.bar(sample_patches_count, label='Sample ' + str(sample_idx))
ax_patches_per_sample.bar(range(0, len(samples_train_patches_count)), samples_train_patches_count, align='center')
ax_patches_per_sample.set_title('Patches per sample')
ax_patches_per_sample.set_xlabel('Sample')
ax_patches_per_sample.set_ylabel('Patch count')
ax_patches_per_sample.set_xticks(range(0, len(samples_train_patches_count)))
ax_patches_per_sample.yaxis.set_ticks(np.arange(0, np.max(samples_train_patches_count), 20))
ax_patches_per_sample.tick_params(axis='both', which='major', labelsize=6)
where
samples_train_patches_count is a simple list with the number of patches per sampled image
samples_avg_train_loss_history is a list of lists in the shape samples, epochs (so if viewed as a matrix every row will be a sample and every column will be the loss of that sample over time)
I do believe I need to do both
shared legend
shared color palette
The shared legend can be done by using get_legend_handles_labels(). However I do not know how to share colors. Both subplots describe different properties of the same thing - the samples. In short I would like to have Patches per sample subplot have all the colors Avg. loss per train sample (epoch 0 excluded) uses.
The first plot is using standard matplotlib Tab10 discrete color map. We can create a cycler over this colormap, and set one by one the color of each bar:
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib.gridspec import GridSpec
import numpy as np
from itertools import cycle
# create a cycler to continously loop over a discrete colormap
cycler = cycle(cm.tab10.colors)
N = 10
x = np.arange(N).astype(int)
y = np.random.uniform(5, 15, N)
f = plt.figure()
gs = GridSpec(2, 4)
ax0 = f.add_subplot(gs[0, :-1])
ax1 = f.add_subplot(gs[1, :-1])
ax2 = f.add_subplot(gs[:, -1])
for i in x:
ax0.plot(x, np.exp(-x / (i + 1)), label="Sample %s" % (i + 1))
h, l = ax0.get_legend_handles_labels()
ax1.bar(x, y)
for p in ax1.patches:
p.set_facecolor(next(cycler))
ax2.axis(False)
ax2.legend(h, l)
plt.tight_layout()
EDIT to accommodate comment. To avoid repetitions you should use a colormap. Matplotlib offers many colormaps. Alternatively, you can also create your own.
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib.gridspec import GridSpec
import numpy as np
from itertools import cycle
N = 50
# create a cycler to continously loop over a discrete colormap
colors = cm.viridis(np.linspace(0, 1, N))
x = np.arange(N).astype(int)
y = np.random.uniform(5, 15, N)
f = plt.figure()
gs = GridSpec(2, 4)
ax0 = f.add_subplot(gs[0, :-1])
ax1 = f.add_subplot(gs[1, :-1])
ax2 = f.add_subplot(gs[:, -1])
ax1.bar(x, y)
for i in x:
c = next(cycler)
ax0.plot(x, np.exp(-x / (i + 1)), color=c, label="Sample %s" % (i + 1))
ax1.patches[i].set_facecolor(c)
h, l = ax0.get_legend_handles_labels()
ax2.axis(False)
ax2.legend(h, l)
plt.tight_layout()

Can matplotlib.pyplot.plot color code a curve pointwise

Here is an example from matplotlib, where pyplot.plot is used and a curve is piecewise color coded.
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(0.0, 2.0, 0.01)
s = np.sin(2 * np.pi * t)
upper = 0.77
lower = -0.77
supper = np.ma.masked_where(s < upper, s)
slower = np.ma.masked_where(s > lower, s)
smiddle = np.ma.masked_where((s < lower) | (s > upper), s)
fig, ax = plt.subplots()
ax.plot(t, smiddle, t, slower, t, supper)
plt.show()
My question is: Can matplotlib.pyplot.plot color code a curve also pointwise (using any color map). I know that I could use matplotlib.pyplot.scatter instead to do that.
No, it can't. See the documentation. As you say, use plt.scatter() for this.
You could call it for every point in your dataset using a different marker format for each, but that would be insanity, because it would effectively call .plot() for every point it plots, which is very wasteful when .scatter() exists.
If you insist though:
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
n = 1000
x = np.linspace(0, 2*np.pi, n)
y = np.sin(x)
cmap = plt.get_cmap('hsv')
norm = mpl.colors.Normalize(vmin=y.min(), vmax=y.max())
for i in range(n):
plt.plot(x[i], y[i], marker='.', markersize=25, c=cmap(norm(y[i])))
plt.show()

Refreshing plot in matplotlib

As part of displaying the progression of a linear regression model fit, I need to be able to update/refresh an xy plot. Below is a simple script for 3 sets of y data, which need to be shown sequentially. However, they are piled up on top of each other. When fig.canvas.flush_events() is substituted with fig.clear() or fig.clf() the result is a blank plot. What am I - as a newbie -missing?
import torch as tc
import matplotlib.pyplot as plt
tc.manual_seed(1)
X=tc.linspace(-3,3,30)
y0=X.pow(2)+0.5*tc.randn(X.shape[0])
y1=y0/1.3
y2=y0/1.6
y=[y0,y1,y2]
fig=plt.figure()
ax=fig.add_subplot()
ax.set_xlim(-3.3,3.3)
ax.set_ylim(-0.5,9.5)
for i in range(3):
y_new=y[i]
ax.plot(X,y_new,'db')
fig.canvas.draw()
fig.canvas.flush_events()
plt.pause(1)
fig.show()
In your loop, you are creating a new line every time you call ax.plot. The better way is to create a Line2D artist and to update the coordinates of the point in the loop:
(NB i've converted your example to using numpy instead of torch)
import matplotlib.pyplot as plt
import numpy as np
X = np.linspace(-3, 3, 30)
y0 = np.power(X, 2) + 0.5 * np.random.randn(X.shape[0])
y1 = y0 / 1.3
y2 = y0 / 1.6
y = [y0, y1, y2]
fig = plt.figure()
ax = fig.add_subplot()
l, = ax.plot(X, y0, 'db')
ax.set_xlim(-3.3, 3.3)
ax.set_ylim(-0.5, 9.5)
for i in range(3):
y_new = y[i]
l.set_ydata(y_new)
fig.canvas.draw()
plt.pause(1)
plt.show()
For this kind of things, you'd be better off using the FuncAnimation module provided by maptlotlib though:
import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np
X = np.linspace(-3, 3, 30)
y0 = np.power(X, 2) + 0.5 * np.random.randn(X.shape[0])
y1 = y0 / 1.3
y2 = y0 / 1.6
y = [y0, y1, y2]
fig = plt.figure()
ax = fig.add_subplot()
l, = ax.plot(X, y0, 'db')
ax.set_xlim(-3.3, 3.3)
ax.set_ylim(-0.5, 9.5)
def animate(y_new):
l.set_ydata(y_new)
return l,
ani = animation.FuncAnimation(fig, func=animate, frames=y, interval=1000)
fig.show()

How to set set the marker size of a 3D scatter plot fixed to the axis?

I've asked a similar question before (How to set a fixed/static size of circle marker on a scatter plot?), but now I wanna do it in 3D. How can I do that?
thanks
As in the 2D case, you need to draw the spheres yourself. If you want nicely shaped spheres this means to draw many patches and thus gets slow quite quickly.
Here's a basic way of doing it:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
def plot_shere(ax, x, y, z, r, resolution=100, **kwargs):
""" simple function to plot a sphere to a 3d axes """
u = np.linspace(0, 2 * np.pi, resolution)
v = np.linspace(0, np.pi, resolution)
xx = r * np.outer(np.cos(u), np.sin(v)) + x
yy = r * np.outer(np.sin(u), np.sin(v)) + y
zz = r * np.outer(np.ones(np.size(u)), np.cos(v)) + z
ax.plot_surface(xx, yy, zz, rstride=4, cstride=4, **kwargs)
# create some random data (set seed to make it reproducable)
np.random.seed(0)
(x,y,z) = np.random.randint(0,10,(3,5))
r = np.random.randint(2,4,(5,))
# set up the figure
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# loop through the data and plot the spheres
for p in zip(x,y,z,r):
plot_shere(ax, *p, edgecolor='none', color=np.random.rand(3))
# set the axes limits and show the plot
ax.set_ylim([-4,14])
ax.set_xlim([-4,14])
ax.set_zlim([-4,14])
plt.show()
Result:

matplotlib: imshow a 2d array with plots of its marginal densities

How can one plot a 2d density with its marginal densities,
along the lines of
scatterplot-with-marginal-histograms-in-ggplot2
or
2D plot with histograms / marginals,
in matplotlib ?
In outline,
# I have --
A = a 2d numpy array >= 0
xdens ~ A.mean(axis=0)
ydens ~ A.mean(axis=1)
# I want --
pl.imshow( A )
pl.plot( xdens ) narrow, below A
pl.plot( ydens ) narrow, left of A, with the x y axes flipped
Added in 2017: see the lovely example of seaborn.jointplot,
also this on SO. (The question was in 2013, before seaborn.)
You can use sharex and sharey with subplots:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
t = np.linspace(0, 31.3, 100)
f = np.linspace(0, 1000, 1000)
a = np.exp(-np.abs(f-200)/200)[:, None] * np.random.rand(t.size)
flim = (f.min(), f.max())
tlim = (t.min(), t.max())
gs = gridspec.GridSpec(2, 2, width_ratios=[1,3], height_ratios=[3,1])
ax = plt.subplot(gs[0,1])
axl = plt.subplot(gs[0,0], sharey=ax)
axb = plt.subplot(gs[1,1], sharex=ax)
ax.imshow(a, origin='lower', extent=tlim+flim, aspect='auto')
plt.xlim(tlim)
axl.plot(a.mean(1), f)
axb.plot(t, a.mean(0))
Which gives you: