Reputation: 8077
I want to make a plot consisting of coloured rectangles, and have been using this example https://matplotlib.org/gallery/misc/histogram_path.html#sphx-glr-gallery-misc-histogram-path-py as a guide:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.path as path
fig, ax = plt.subplots()
# Fixing random state for reproducibility
np.random.seed(19680801)
data = np.random.randn(1000)
n, bins = np.histogram(data, 50)
# get the corners of the rectangles for the histogram
left = np.array(bins[:-1])
right = np.array(bins[1:])
bottom = np.zeros(len(left))
top = bottom + n
XY = np.array([[left, left, right, right], [bottom, top, top, bottom]]).T
# get the Path object
barpath = path.Path.make_compound_path_from_polys(XY)
# make a patch out of it
patch = patches.PathPatch(barpath)
ax.add_patch(patch)
# update the view limits
ax.set_xlim(left[0], right[-1])
ax.set_ylim(bottom.min(), top.max())
plt.show()
I would like a different colour for each rectangle and thought I could do this using a list of floats in cols and setting
patch=patches.PathPatch(barpath,fill=True,color=cols
,cmap='CMRmap')
But I get "Invalid rgba arg", does this routine only recognize RGB lists?
Upvotes: 4
Views: 1681
Reputation: 339170
A matplotlib.patches.PathPatch
has a single facecolor. It cannot be used to colorize parts of it differently. This is mentioned in the example
can draw collections of regularly shaped objects with homogeneous properties more efficiently with a PathCollection.
So the motivation to use this strategy instead of creating a usual histogram and colorize its bars is efficiency. A slightly less efficient way of creating a bar plot, but still faster than the usual bars
is to use a PolyCollection
.
So let's look at three different solutions below.
import numpy as np; np.random.seed(19680801)
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.path as path
import matplotlib.collections
import matplotlib.cm
data = np.random.randn(1000)
def compoundpathhist(data, nbins=50):
n, bins = np.histogram(data, nbins)
# get the corners of the rectangles for the histogram
left = np.array(bins[:-1])
right = np.array(bins[1:])
bottom = np.zeros(len(left))
top = bottom + n
# we need a (numrects x numsides x 2) numpy array for the path helper
# function to build a compound path
XY = np.array([[left, left, right, right], [bottom, top, top, bottom]]).T
# get the Path object
barpath = path.Path.make_compound_path_from_polys(XY)
# make a patch out of it
patch = patches.PathPatch(barpath)
fig, ax = plt.subplots()
ax.add_patch(patch)
# update the view limits
ax.set_xlim(left[0], right[-1])
ax.set_ylim(bottom.min(), top.max())
fig.savefig("bartest.png")
def polyhist(data, nbins=50, colors=True):
n, bins = np.histogram(data, nbins)
# get the corners of the rectangles for the histogram
left = np.array(bins[:-1])
right = np.array(bins[1:])
bottom = np.zeros(len(left))
top = bottom + n
# we need a (numrects x numsides x 2) numpy array to be used as
# vertices for the PolyCollection
XY = np.array([[left, left, right, right], [bottom, top, top, bottom]]).T
c=None
if colors:
c = matplotlib.cm.RdYlBu(n/n.max())
pc = matplotlib.collections.PolyCollection(XY, facecolors=c)
fig, ax = plt.subplots()
ax.add_collection(pc)
# update the view limits
ax.set_xlim(left[0], right[-1])
ax.set_ylim(bottom.min(), top.max())
fig.savefig("bartest.png")
def hist_c(data, nbins=50, colors=True):
fig, ax = plt.subplots()
n, bins, patches = ax.hist(data, nbins)
if colors:
cols = matplotlib.cm.RdYlBu(n/n.max())
for p,c in zip(patches, cols):
p.set_facecolor(c)
# update the view limits
ax.set_xlim(bins.min(), bins.max())
ax.set_ylim(n.min(), n.max())
fig.savefig("bartest.png")
compoundpathhist(data, nbins=50)
polyhist(data, nbins=50)
hist_c(data, nbins=50, colors=True)
plt.show()
compoundpathhist
) is the one from the linked example. It is fast, but cannot show color.polyhist
) doesn't use a single patch, but instead a PolyCollection
. The PolyCollection
's facecolors can be set via a colormap.hist_c
) is the adapted usual solution to colorize individual bars.Now we can time the three functions. I'm using 90000 data points and 50, 500 and 5000 bins.
We observe that for a usual number of ~50 bins, there is essentially no difference between those methods. However for larger number of bins, the hist
method takes significantly longer. Even for 5000 bins, there is almost no difference between the compoundpathhist
method (which cannot use color) and the polyhist
, which can use color. This is hence a useful alternative without sacrificing efficiency.
There is by the way almost no difference between using color or not using color in the respective functions.
Upvotes: 3
Reputation: 39042
I think you are following a longer unnecessary route to color each bar differently. Simply use the following (shorter) approach. Using PathPatch
and Path
is not at all required. You can remove edgecolor='black'
if you don't want the black edges.
Explanation: Get the patches
object directly from the plt.hist
. Then, create a list of colors based on the colormap CMRmap
. Finally loop over the patches
and set the color of each patch.
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
# Fixing random state for reproducibility
np.random.seed(19680801)
data = np.random.randn(1000)
N, bins, patches = plt.hist(data, bins=50, edgecolor='black')
cm = plt.get_cmap('CMRmap')
for i in range(len(patches)):
color = cm(1.*i/len(patches))
patches[i].set_facecolor(color)
Upvotes: 3