Can matplotlib.pyplot.plot color code a curve pointwise - matplotlib

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

Related

How can Matplotlib axes be scaled hyperbolically?

I have a plot a bit like this:
The differences between the two lines (red and blue) are most important in my actual data (a ROC curve) at say the grid cell 0.2<x<0.4, 0.8<y<1. Now, I could crop for that grid cell, but let's say I'd rather scale both the x and y axes hyperbolically -- where the y-axis hyperbolic curve has its peak at about 0.9 and the x-axis has its peak at about 0.3 -- such that the 2D space gets stretched out for the grid cell of interest and gets compacted elsewhere (and preserving the meaning of the axes tick numbers). How would one accomplish this? The beginnings of my attempt are below. How would my code be modified to implement the axis scaling I described?
from matplotlib import gridspec
from matplotlib import scale as mscale
from matplotlib import transforms as mtransforms
from matplotlib.ticker import FormatStrFormatter
from matplotlib.ticker import NullFormatter, NullLocator, MultipleLocator
import math
import matplotlib
import matplotlib.patches as mpatches
import matplotlib.pylab as plt
import matplotlib.pyplot as plt
import matplotlib.ticker
import numpy as np
import seaborn as sns
sns.set_palette('husl')
sns.set()
plt.rcParams["figure.figsize"] = [5, 5]
x = np.arange(0, 1, step=0.01)
y1 = 1-1/np.exp(10*x)
y2 = 1-1.1/np.exp(10*x)
plt.scatter(x, y1, s=1, facecolor='red')
plt.scatter(x, y2, s=1, facecolor='blue')
plt.show();
class CustomScale(mscale.ScaleBase):
name = 'custom'
def __init__(self, axis, **kwargs):
mscale.ScaleBase.__init__(self)
self.thresh = None #thresh
self.name = 'custom'
def get_transform(self):
return self.CustomTransform(self.thresh)
def set_default_locators_and_formatters(self, axis):
pass
class CustomTransform(mtransforms.Transform):
input_dims = 1
output_dims = 1
is_separable = True
def __init__(self, thresh):
mtransforms.Transform.__init__(self)
self.thresh = thresh
def transform_non_affine(self, a):
#return np.log(1+a)
return np.exp(a)-1
#return 1+(1/2)*a
mscale.register_scale(CustomScale)
plt.scatter(x, y1, s=1, facecolor='red')
plt.scatter(x, y2, s=1, facecolor='blue')
plt.xscale('custom')
plt.show();
You may be able to achieve this using FuncScale (registered as 'function').
f = lambda a: np.exp(a) - 1
g = lambda b: np.log(b + 1)
plt.xscale('function', functions=(f, g))
For hyperbolic scaling, you could use lambda x: 1 / x for both functions.
See the example in the scales documentation: https://matplotlib.org/3.3.4/gallery/scales/scales.html

How to fill histogram with color gradient where a fixed point represents the middle of of colormap

This code
import numpy as np
import matplotlib.pyplot as plt
def randn(n, sigma, mu):
return sigma * np.random.randn(n) + mu
x = randn(1000, 40., -100.)
cm = plt.cm.get_cmap("seismic")
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
_, bins, patches = ax.hist(x,color="r",bins=30)
bin_centers = 0.5*(bins[:-1]+bins[1:])
col = bin_centers - min(bin_centers)
col /= max(col)
for c, p in zip(col, patches):
plt.setp(p, "facecolor", cm(c))
plt.savefig("b.png", dpi=300, bbox_inches="tight")
produces the following histograms
I want to use the diverging colormap seismic and would like to have all bars representing the occurrence of negative numbers to be bluish and all bars representing positive numbers reddish. Around zero the bars should always be white. Therefore the first graph should be mostly reddish and the last one should be mostly bluish. How can I achieve that?
If this is about visual appearance only, you can normalize your colors to the range between the maximum absolute value and its negative counterpart, such that zero is always in the middle (max |bins|).
import numpy as np; np.random.seed(42)
import matplotlib.pyplot as plt
plt.rcParams["figure.figsize"] = 6.4,4
def randn(n, sigma, mu):
return sigma * np.random.randn(n) + mu
x1 = randn(999, 40., -80)
x2 = randn(750, 40., 80)
x3 = randn(888, 16., -30)
def hist(x, ax=None):
cm = plt.cm.get_cmap("seismic")
ax = ax or plt.gca()
_, bins, patches = ax.hist(x,color="r",bins=30)
bin_centers = 0.5*(bins[:-1]+bins[1:])
maxi = np.abs(bin_centers).max()
norm = plt.Normalize(-maxi,maxi)
for c, p in zip(bin_centers, patches):
plt.setp(p, "facecolor", cm(norm(c)))
fig, axes = plt.subplots(nrows=3, sharex=True)
for x, ax in zip([x1,x2,x3], axes):
hist(x,ax=ax)
plt.show()
I have an alternative answer for a different use case. I wanted to have the different colours from the divergent colormap be dynamically mapped to their respective "width" on either side of the divergence point. Additionally, I wanted to explicitly set the divergence point (in my case, 1).
I achieved this by modifying the answer from #ImportanceofBeingErnest, although in the end I didn't need to do any normalization, I just used two plots on the same figure, and chose the sequential colormaps which, when put end-to-end, re-formed the target divergent colormap.
def hist2(x, vmin, vmax, cmmap_name, ax=None,):
cm = plt.cm.get_cmap(cmmap_name)
ax = ax or plt.gca()
_, bins, patches = ax.hist(x,color="r",bins=50)
bin_centers = 0.5*(bins[:-1]+bins[1:])
norm = plt.Normalize(vmin, vmax)
for c, p in zip(bin_centers, patches):
plt.setp(p, "facecolor", cm(norm(c)))
data = <YOUR DATA>
left_data = [i for i in data if i < <YOUR DIVERGENCE POINT>]
right_data = [i for i in data if i >= <YOUR DIVERGENCE POINT>]
fig, ax = plt.subplots(nrows=1)
hist2(left_data, min(left_data), max(left_data), "YlOrRd_r", ax=ax)
hist2(right_data, min(right_data), max(right_data), "YlGn", ax=ax)
plt.show()
Some of my results:

Python Subplot 3d Surface and Heat Map

I plan to create a figure in matplotlib, with a 3D surface on the left and its corresponding contour map on the right.
I used subplots but it only show the contour map (with blank space for the surface), and a separate figure for the surface.
Is it possible to create these plots in one figure side-by side?
EDIT: The code is as follows:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
import numpy as np
x = np.arange(-5, 5, 0.25)
y = np.arange(-5, 5, 0.25)
x, y = np.meshgrid(x, y)
r = np.sqrt(x**2 + y**2)
z = np.sin(r)
fig, (surf, cmap) = plt.subplots(1, 2)
fig = plt.figure()
surf = fig.gca(projection='3d')
surf.plot_surface(x,y,z)
cmap.contourf(x,y,z,25)
plt.show()
I guess it's hard to use plt.subplots() in order to create a grid of plots with different projections.
So the most straight forward solution is to create each subplot individually with plt.subplot.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
import numpy as np
x = np.arange(-5, 5, 0.25)
y = np.arange(-5, 5, 0.25)
x, y = np.meshgrid(x, y)
r = np.sqrt(x**2 + y**2)
z = np.sin(r)
ax = plt.subplot(121, projection='3d')
ax.plot_surface(x,y,z)
ax2 = plt.subplot(122)
ax2.contourf(x,y,z,25)
plt.show()
Of course one may also use the gridspec capabilities for more sophisticated grid structures.

Matplotlib: scatter plot with colormaps for edgecolor but no facecolor

I want to have a scatter plot with colormap for edgecolors but no facecolors.
When I use facecolor='None', it does not work.
import numpy as np
import matplotlib.pyplot as plt
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
colors = np.random.rand(N)
area = np.pi * (15 * np.random.rand(N))**2 # 0 to 15 point radii
plt.scatter(x, y, s=area,c=colors,facecolors='None',cmap="gist_rainbow", alpha=0.5)
plt.show()
Any solution?
The c argument will affect facecolor and edgecolor simultaneouly, the arguments facecolor and edgecolor are hence ignored.
A solution would be not to use the c argument together with a colormap, but instead use facecolors and edgecolors alone. In this case facecolors can be set to "None" and edgecolors can be given a list of colors to use.
To create this list, the same colormap can be applied.
c = plt.cm.gist_rainbow(colors)
plt.scatter(x, y, s=area,facecolors="None", edgecolors=c, lw=1,alpha=0.5)
A complete example:
import numpy as np
import matplotlib.pyplot as plt
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
colors = np.random.rand(N)
area = np.pi * (15 * np.random.rand(N))**2 # 0 to 15 point radii
c = plt.cm.gist_rainbow(colors)
plt.scatter(x, y, s=area,facecolors="None", edgecolors=c, lw=2,alpha=0.5)
plt.show()
The problem is that color= overrides the facecolors= argument.
The solution I came up with is to get the PathCollection returned by pyplot.scatter() and then change the facecolor directly. Note that you probably need to increase the line width to see the edges better.
import numpy as np
import matplotlib.pyplot as plt
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
colors = np.random.rand(N)
area = np.pi * (15 * np.random.rand(N))**2 # 0 to 15 point radii
a = plt.scatter(x, y, s=area,c=colors,facecolor='none',lw=2,cmap="gist_rainbow", alpha=0.5)
a.set_facecolor('none')
plt.show()
I know this has been dead for a while, but I wanted to add my experience as I just encountered this same problem.
I prefer Diziet's method as passing the PathCollection object to a colorbar and having it match the cmap used in the scatter plot works exactly as it would if you didn't alter the facecolors.
With the accepted solution, however, I encountered some odd behavior where even after removing the cmap argument from the ax.scatter call the scatter plot edge colormap and the colorbar colormap didn't match.

How to draw polar hist2d/hexbin in 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.