I have a data named prices, and I use a prices.tail(1) to build a histogram.
Also I have some variables: left_border = 341.086, right_border = 437.177, line_length = 1099.
And the next code:
plt.figure(figsize=(9,6))
plt.hist(prices.tail(1), bins = 400)
x2 = [left_border,left_border]
y2 = [0, line_length]
plt.plot(x2, y2, color = 'green')
x3 = [right_border, right_border]
y3 = [0, line_length]
plt.plot(x3, y3, color = 'green')
plt.show()
Produce an output:
How I can colour part of histogram which is between the green borders differently from part outside the green borders, gradientally? Also to pick bins which are nearly by green borders and turn them into another colour?
Thanks.
The exact meaning of 'gradiently' here is uncertain to me. Here are some ideas that can serve as a base to create the desired solution.
hist returns the values of each bin, the limits of the bins and the patches that were drawn; you can color the patches depending on their mean x-position
to create a gradient like effect, the simplest is interpolating linearly between two colors; a function such as sqrt can be used to make the effect start quicker
axvspan can draw a vertical span between two given x coordinates; set zorder=0 to make sure the span stays behind the histogram bars; or set an alpha=0.3 to draw it as a transparent layer over the bars
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np
prices_np = 14*np.random.normal(5, 1, 10000)**2
left_border = 341.086
right_border = 437.177
# line_length = 1099
divisor_tickness = 10
main_color = mcolors.to_rgb('dodgerblue')
highlight_color = mcolors.to_rgb('limegreen')
divisor_color = mcolors.to_rgb('crimson')
binvals, bins, patches = plt.hist(prices_np, bins = 400, color=main_color)
bin_centers = 0.5 * (bins[:-1] + bins[1:])
for p, x in zip(patches, bin_centers):
#x, _ = p.get_xy()
#w = p.get_width()
if left_border < x < right_border:
f = 2*min(x-left_border, right_border-x) / (right_border - left_border)
f = f ** 0.5
p.set_facecolor([ (h_rgb*f + m_rgb * (1-f)) for m_rgb, h_rgb in zip(main_color, highlight_color)] )
elif left_border-divisor_tickness < x <= left_border or right_border <= x < right_border + divisor_tickness:
p.set_facecolor(divisor_color)
plt.axvspan(left_border, right_border, color='lightgoldenrodyellow', zorder=0)
plt.show()
To get a smooth gradient depending on the bar heights, a gaussian kde could be useful:
kde = gaussian_kde(prices_np)
max_kde = max([kde(x)[0] for x in bin_centers])
for x, p in zip(bin_centers, patches):
p.set_facecolor(plt.cm.viridis((kde(x)[0] / max_kde) ))
Related
I have a pretty rough data set I am using to draw a phase diagram with matplotlib's pcolormesh.
x,y are 2D numpy arrays and represent a uniform grid at the moment. z contains integers ranging from 1-9, each number matching a phase. The option shading='nearest' centers the color chosen according to z at (x,y). My colormap is segmented matching the possible z values.
vmin, vmax = 1, 9
colors = ['blue', 'orange', 'black', 'gray', 'cyan', 'lime', 'yellow', 'green', 'red']
cmap = ListedColormap(colors)
axes[0].pcolormesh(x, y, z, shading = 'nearest', vmin = vmin, vmax = vmax, cmap = cmap)
With this I get the upper subplot which is acceptable for me.
However, some of the phases have common properties which is why I would like to add contours. For example, I would like to draw a contour that separates the colored and the black/gray parts. I have two problems here:
It would be great if I could use contour but I cannot manage to do so, see the second subplot drawn with contourf, without it triangulating(?) my data. This would not be a problem if I had a lot more data points but it is unlikely that I will increase the resolution by much. Even if I could live with triangulation: No yellow area should be drawn. But since z jumps from z=8(green) to z = 6(lime) contour inserts an intermediate yellow area.
Depending on how we solve this problem: I would really like to be able to draw contours both for connected and disconnected areas.
An idea I have is defining a new phase that covers lime, green and cyan and then outline that area. The data manipulation for this is simple, however, I do not know how to proceed with matplotlib after that. Besides, I do not know how one would identify connected and disconnected cells.
I managed to come of with the following setup that I am almost satisfied with:
The key is the so-called alpha shape. It is in essence a triangulation, roughly speaking for determining the bounding polygon of a set of points. Here is the corresponding python module. It was very simple to implement. I had no prior experience with shapely. In addition, I had to dig a bit into matplotlib's pcolor source code. In the end I came up with the following script, main code is at the bottom.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
import numpy.ma as ma
from descartes import PolygonPatch
import alphashape
#various parameters
nrows, ncols = 1, 1
# create segmented colormap with 9 colors (I will need 9 colors in my actual application)
colors = ['blue', 'orange', 'black', 'gray', 'cyan', 'lime', 'yellow', 'green', 'red']
cmap = mpl.colors.ListedColormap(colors)
# set vmin and vmax from 1 to 9
vmin = 1
vmax = 9
# name of the saved imagefile
savename = 'plot_alpha_shape'
# alpha value for the alpha shape, dont confuse it with the opacity from the
# standard mpl kwargs
alpha = 0.5
# grid discretization
dx = 2.0
dy = 1
# create 2d rectangular mesh
x, y = np.meshgrid(dx*np.arange(3), dy*np.arange(4))
# create homogeneous demo data
z = np.ones_like(x)*2
# change some values to make z heterogeneous
z[0] = np.ones(3)
z[1,-1] = 1
# define mask for the contour
mask = z>1
z_masked = ma.masked_array(z, mask = mask)
pcolor_kwargs = dict(shading = 'nearest', vmin = vmin, vmax = vmax, cmap = cmap)
contour_kwargs = dict(fc = 'none', ec = 'k', linewidth = 3)
def get_quadrilateral_vertices(x,y):
X = interp_grid(x)
Y = interp_grid(y)
X = interp_grid(X.T).T
Y = interp_grid(Y.T).T
return X, Y
def interp_grid(X):
dX = np.diff(X, axis=1)/2.
X = np.hstack((X[:, [0]] - dX[:, [0]],
X[:, :-1] + dX,
X[:, [-1]] + dX[:, [-1]]))
return X
def get_xymask(x,y):
# merge x and y masks in case they are different
mask = ma.getmaskarray(x) + ma.getmaskarray(y)
# map mask to the cells in order to merge it with z mask
xymask = (mask[0:-1, 0:-1] + mask[1:, 1:] +
mask[0:-1, 1:] + mask[1:, 0:-1])
return xymask
def execute_masking(x,y,z):
# get dimensions
Ny, Nx = x.shape
xymask = get_xymask(x,y)
# merge all masks
# don't plot if C or any of the surrounding vertices are masked.
mask = ma.getmaskarray(z) + xymask
unmask = ~mask
X1 = ma.filled(x[:-1, :-1])[unmask]
Y1 = ma.filled(y[:-1, :-1])[unmask]
X2 = ma.filled(x[1:, :-1])[unmask]
Y2 = ma.filled(y[1:, :-1])[unmask]
X3 = ma.filled(x[1:, 1:])[unmask]
Y3 = ma.filled(y[1:, 1:])[unmask]
X4 = ma.filled(x[:-1, 1:])[unmask]
Y4 = ma.filled(y[:-1, 1:])[unmask]
# npoly = len(X1)
xy = np.stack([X1, Y1, X2, Y2, X3, Y3, X4, Y4], axis=-1)
# one vertex is duplicate in the original code
# xy = np.stack([X1, Y1, X2, Y2, X3, Y3, X4, Y4, X1, Y1], axis=-1)
# transform to array of xy pairs
verts = xy.reshape((-1, 2))
z = ma.filled(z[:Ny - 1, :Nx - 1])[unmask]
return verts, z
def get_masked_data(x,y,z):
X, Y = get_quadrilateral_vertices(x,y)
# convert to MA, if necessary.
z = ma.asarray(z)
X = ma.asarray(X)
Y = ma.asarray(Y)
return execute_masking(X,Y,z)
def plot_vertices(ax, verts):
verts = verts.T
ax.plot(*verts, linestyle = '', marker = 'x', color = 'r', ms = 10)
# main code
# x,y,z are cellcentered data
# use get_masked_data and its inner functions to get the vertices of the
# cells used in pcolor
# we are not using zdummy here
verts, zdummy = get_masked_data(x,y,z_masked)
# map vertices to a list of (x,y) tuples, each representing one vertex
contour_data = list(zip(verts[:,0], verts[:,1]))
# create an alpha shape from the vertices
contour_alphashape = alphashape.alphashape(verts, alpha)
# create figure with one subplot
fig = plt.figure(figsize=(15/2.54,nrows*4/2.54), constrained_layout=True)
gs = GridSpec(nrows, ncols, figure=fig)
axes = [fig.add_subplot(gs[j,i]) for j in range(nrows) for i in range(ncols)]
# plot vertices
plot_vertices(axes[0], verts)
# plot pcolor
pmesh1 = axes[0].pcolor(x,y,z, **pcolor_kwargs)
# plot the contour using alphashape
contour = PolygonPatch(contour_alphashape,**contour_kwargs)
axes[0].add_patch(contour)
# save the plot
plt.savefig(savename + '.png')
Almost all of the defined functions are taken from matplotlib's pcolor and _pcolorargs.
The alpha shape will become more detailed the larger you choose alpha. For very small alpha you will get a convex hull. I am attaching the result of the script.
As you can see, the contour does not exactly match the blue area. If alpha becomes too large, alpha shape will not return a proper polygon if I understood it correctly which is why I cannot make the contour align even tighter. I think it has something to do with the regular spacing of my data, too.
I have this histogram computed from a pandas dataframe.
I want to change the colors based on the x-axis values.
For example:
If the value is = 0 the color should be green
If the value is > 0 the color should be red
If the value is < 0 the color should be yellow
I'm only concerned with the x-axis. The height of the bar doesn't matter much to me. All other solutions are for the y-axis.
For each bar patch in ax.containers[0], use set_color based on the x position:
get_x returns the left edge, so get the midpoint by adding half of get_width
x probably won't be exactly 0, so test with some buffer (0.2 in this example)
Since you asked for pandas in the comments, this example uses DataFrame.plot.hist, but you can do this with any matplotlib-based histogram/bar plot:
df = pd.DataFrame({'A': np.random.default_rng(222).uniform(-1, 1, 40)})
ax = df.plot.hist()
for bar in ax.containers[0]:
# get x midpoint of bar
x = bar.get_x() + 0.5 * bar.get_width()
# set bar color based on x
if x < -0.2:
bar.set_color('orange')
elif x > 0.2:
bar.set_color('red')
else:
bar.set_color('green')
Just plot them one by one:
import matplotlib as mpl
import matplotlib.pyplot as plt
x = np.linspace(-1,1,10)
y = np.random.uniform(0,1,10)
width = 0.2
plt.figure(figsize = (12, 6))
cmap = mpl.cm.RdYlGn.reversed()
norm = mpl.colors.Normalize(vmin=0, vmax=10)
for x0, y0 in zip(x,y):
plt.bar(x0, y0, width = width, color = cmap(norm(np.abs(x0*10))))
I have a function that I'd like to plot in python and shade the region of interest. I've tried using pyplot.fill_between() but can not quite get what I want. I've attached an image and shaded in orange the region I want to be filled:
I plot the function (in blue) and then the graph is bounded by y=0, y ≈ 0.05 and x = 0.And I wish to shade the relevant region (in orange).
Any tips as to how to go about this?
Thanks in advance.
import numpy as np
import matplotlib.pyplot as plt
def fn (M, r_min):
d = (1- 2*M/ r_min)
x = M/(r_min)**2
A_0 = d**-0.5
A_dot = np.arange(-0.6,0.5,0.0001) #X axis
a = np.zeros(len(A_dot))
for i in range(1,len(A_dot)):
a[i] = -3*d*A_dot[i]**2 -2*x*A_dot[i] + A_0**2*x**2 #Y axis
plt.plot(A_dot, a)
plt.xlim(-0.55,0.55)
plt.axhline(y = 0, color='black', linestyle='--')
plt.axhline(y = 0.049382716, color = 'black', linestyle = '--')
plt.axvline(x = 0,color ='black', linestyle = '--')
idx = np.argwhere(np.diff(np.sign(a))).flatten() #Finding intersection on x+axis
plt.plot(A_dot[idx], a[idx], 'ro')
plt.xlabel('$\\frac{dA_0}{d\tau}$')
plt.ylabel('$|a|^2$')
plt.show()
return(A_dot,a)
fn(1,3)
You need to give the x and y vectors as inputs to fill_between. To do that, you can define a mask selecting between the interception point and 0 (add to your fn function):
x_min = A_dot[idx[1]]
x_max = 0.0
mask_x = np.logical_and(A_dot >= x_min, A_dot <= x_max)
plt.fill_between(x=A_dot[mask_x], y1=a[mask_x], y2=0, color='orange')
Result:
The alpha levels are increased where the points overlap, so the darkest areas are where points are overlapping.
I would instead like the alpha levels to subtract from each other somehow - so that if there was an overlapping section it would be lighter than a section with no overlap.
Here is an example of what I mean - from left to right the points become darker as there are more overlapped:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(20, 3))
# X and Y coordinates for red circles
red_xs = [1]
red_ys = [1]
# Plot with a large markersize
markersize = 35
alpha = 0.1
for i in range(20):
red_xs[0] += 1
for add in range(i):
ax.plot(
red_xs,
red_ys,
marker="o",
color="r",
linestyle="",
markersize=markersize,
alpha=alpha,
)
which looks as:
I would like the inverse of this - to be able to start with an alpha level of the point on the far right and for the point to be come fainter in the areas that overlap, which would have the result of rendering as the point in the far left as many are overlayed.
To give a more concrete example where points are no perfectly overlayed:
import matplotlib.pyplot as plt
import random
fig, ax = plt.subplots(figsize=(20, 3))
# X and Y coordinates for red circles
red_xs = [1]
red_ys = [1]
# Plot with a large markersize
markersize = 35
alpha = 0.01
random.seed(1)
for j in range(5):
red_xs = [1]
red_ys = [1]
for i in range(20):
u = 0.1
v = 0.00000001
dx = random.uniform(-u, u)
dy = random.uniform(-u, u)
red_xs[0] += 2 + dx
red_ys[0] += dy
for add in range(i):
ax.plot(
red_xs,
red_ys,
marker="o",
color="r",
linestyle="",
markersize=markersize,
alpha=alpha,
)
looks as:
For parts where there are overlapping points such as these:
The solution should render y (where they intersect) the alpha of x, and x the alpha of y. And this should work for any number of layers.
It's possible to fill between lines with a color:
http://matplotlib.sourceforge.net/examples/pylab_examples/fill_between_demo.html
It's also possible to use a continuous colormap for a line:
http://matplotlib.sourceforge.net/examples/pylab_examples/multicolored_line.html
Is it possible (and reasonably easy) to use a continuous colormap for the colored fill between two lines? For example, the color fill may change along x based on the difference between the two lines at x (or based on another set of data).
I found a solution to this problem. It builds on the brilliant but hacky solution of #Hooked. You create a 2D grid filed from lots of small boxes. It's not the fastest solution but it should be pretty flexible (more so than solutions which apply imshow to the patches).
import numpy as np
import pylab as plt
#Plot a rectangle
def rect(ax, x, y, w, h, c,**kwargs):
#Varying only in x
if len(c.shape) is 1:
rect = plt.Rectangle((x, y), w, h, color=c, ec=c,**kwargs)
ax.add_patch(rect)
#Varying in x and y
else:
#Split into a number of bins
N = c.shape[0]
hb = h/float(N); yl = y
for i in range(N):
yl += hb
rect = plt.Rectangle((x, yl), w, hb,
color=c[i,:], ec=c[i,:],**kwargs)
ax.add_patch(rect)
#Fill a contour between two lines
def rainbow_fill_between(ax, X, Y1, Y2, colors=None,
cmap=plt.get_cmap("Reds"),**kwargs):
plt.plot(X,Y1,lw=0) # Plot so the axes scale correctly
dx = X[1]-X[0]
N = X.size
#Pad a float or int to same size as x
if (type(Y2) is float or type(Y2) is int):
Y2 = np.array([Y2]*N)
#No colors -- specify linear
if colors is None:
colors = []
for n in range(N):
colors.append(cmap(n/float(N)))
#Varying only in x
elif len(colors.shape) is 1:
colors = cmap((colors-colors.min())
/(colors.max()-colors.min()))
#Varying only in x and y
else:
cnp = np.array(colors)
colors = np.empty([colors.shape[0],colors.shape[1],4])
for i in range(colors.shape[0]):
for j in range(colors.shape[1]):
colors[i,j,:] = cmap((cnp[i,j]-cnp[:,:].min())
/(cnp[:,:].max()-cnp[:,:].min()))
colors = np.array(colors)
#Create the patch objects
for (color,x,y1,y2) in zip(colors,X,Y1,Y2):
rect(ax,x,y2,dx,y1-y2,color,**kwargs)
# Some Test data
X = np.linspace(0,10,100)
Y1 = .25*X**2 - X
Y2 = X
g = np.exp(-.3*(X-5)**2)
#Plot fill and curves changing in x only
fig, axs =plt.subplots(1,2)
colors = g
rainbow_fill_between(axs[0],X,Y1,Y2,colors=colors)
axs[0].plot(X,Y1,'k-',lw=4)
axs[0].plot(X,Y2,'k-',lw=4)
#Plot fill and curves changing in x and y
colors = np.outer(g,g)
rainbow_fill_between(axs[1],X,Y1,Y2,colors=colors)
axs[1].plot(X,Y1,'k-',lw=4)
axs[1].plot(X,Y2,'k-',lw=4)
plt.show()
The result is,
Your solution is great and flexible ! In particular the 2D case is really nice. Such a feature could be added to fill_between maybe if the colors kwargs of the function would accept an array of the same length of x and y ?
Here is a simpler case for the 1D case using the fill_between function. It does the same but as it use trapezes instead of rectangle the result is smoother.
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import norm
# Select a color map
cmap = mpl.cm.bwr
# Some Test data
npts = 100
x = np.linspace(-4, 4, npts)
y = norm.pdf(x)
z = np.sin(2 * x)
normalize = mpl.colors.Normalize(vmin=z.min(), vmax=z.max())
# The plot
fig = plt.figure()
ax = fig.add_axes([0.12, 0.12, 0.68, 0.78])
plt.plot(x, y, color="gray")
for i in range(npts - 1):
plt.fill_between([x[i], x[i+1]], [y[i], y[i+1]], color=cmap(normalize(z[i])))
cbax = fig.add_axes([0.85, 0.12, 0.05, 0.78])
cb = mpl.colorbar.ColorbarBase(cbax, cmap=cmap, norm=normalize, orientation='vertical')
cb.set_label("Sin function", rotation=270, labelpad=15)
plt.show()