简体   繁体   中英

Matplotlib: Fill from binary data

I have a binary numpy array that contains filling data, and two grids that define the bounding box for the data:

data = np.random.choice([0, 1], size=12).reshape((3, 4))
xGrid = np.linspace(1, 4, 4)
yGrid = np.linspace(0.1, 0.3, 3)

I would like to plot a particular color, with a particular alpha, on any grid point where data is 1 , and nothing when data is 0 . The two closest matplotlib functions are

  • fill , which requires (x, y) coordinates and can't work with this data
  • imshow or matshow , which unfortunately will plot some color everywhere . That is, it will also plot some color drawn from the color map wherever data == 0 . Now, I could fiddle around to make that color be the background color of the ax, but that's rather nasty.

The bounding boxes are expected to behave as follows: xGrid contains three values, and there are three data points on the x-dimension. Each value in xGrid denotes the location of the center point for each of the data points, and similar for yGrid . "Filling a data point" then corresponds to filling the rectangle defined by the center coordinates ( x, y ).

What's the best way of achieving this?

With the understanding that the filled areas are to be drawn using the grid intersections as central points, we have

In [27]: import numpy as np 
    ...: import matplotlib.pyplot as plt 
    ...: np.random.seed(2018) 
    ...: data = np.random.choice([0, 1], size=12).reshape((3, 4)) 
    ...: xGrid = np.linspace(1, 4, 4) 
    ...: yGrid = np.linspace(0.1, 0.3, 3)                                                 

In [28]: print(data)                                                                      
[[0 0 0 1]
 [1 0 0 0]
 [1 1 1 1]]

In [29]: dx, dy = (xGrid[1]-xGrid[0])/2, (yGrid[1]-yGrid[0])/2                                    

In [30]: xli, yli = [], [] 
    ...: for y in yGrid: 
    ...:     for x in xGrid: # the x's in data are changing faster, so inner loop
    ...:         xli.append([x-dx, x+dx, x+dx, x-dx, x-dx]) 
    ...:         yli.append([y-dy, y-dy, y+dy, y+dy, y-dy]) 

In [31]: for xs, ys, Bool in zip(xli, yli, data.flatten()): 
    ...:     if Bool : plt.fill(xs, ys, color='red')
    ...: plt.gca().set_facecolor('yellow')                                      

Executing the code above gives me 在此处输入图片说明

It's worth mentioning that only the filled rectangles are drawn, as shown by filling the background of the plotting area with a different color.

plt.fill is documented here and the lists created in the first for loop are simply the x , y coordinates of the corners of a rectangle that could possibly be drawn by plt.fill .


A Note on Efficiency

If one has to draw a few hundreds of rectangles the simple approach above is OK, if we go into the tens of thousands maybe we want to loop over the data points with enumerate , if it's needed construct the x , y lists and draw the rectangle on the fly or, even better for performance, create a Rectangle patch, put it in a PatchCollection and use the ax.add_collection method when we have finished the loop on dataan example is available in the Matplotlib docs that can be easily adapted to the scope and another example is this new answer of mine.

Using imshow() based on this example for using the alpha .

I am using set_ticks code given by @BM

def make_rgb_transparent(rgb, bg_rgb, alpha):
    return [alpha * c1 + (1 - alpha) * c2
            for (c1, c2) in zip(rgb, bg_rgb)]

import matplotlib
from matplotlib import colors

alpha =1.0
white = np.ones((1,3))
rgb = colors.colorConverter.to_rgb('red')
rgb_new = make_rgb_transparent(rgb, (1, 1, 1), alpha)
red_white_map = colors.LinearSegmentedColormap.from_list('map_white', np.vstack((white, rgb_new)),2)

ax=plt.imshow(data,cmap=red_white_map)
ax.axes.set_xticks(np.arange(len(xGrid)))
ax.axes.set_xticklabels([str(a) for a in xGrid])
ax.axes.set_yticks(np.arange(len(yGrid)))
ax.axes.set_yticklabels([str(a) for a in yGrid])

在此处输入图片说明

You can manage color with the colormap parameter. Here a fast solution using imshow, with total control on all parameters, in particular custom colors:

from pylab import imshow,show,cm
from matplotlib.colors import LinearSegmentedColormap
alpha=.7
cdict = {'blue': ((0.0, 0.0, 0.0), (1.0, 0.0, 0.0)),
 'green': ((0.0, 0.0, 0.0), (1.0, 0.0, 0.0)),
 'red': ((0.0, 0.0, 0.0), (1.0, alpha,alpha))} 
mycolors = LinearSegmentedColormap("my_colors",cdict,N=2)

ax=imshow(data,cmap=mycolors)
ax.axes.set_xticks(np.arange(len(xGrid)))
ax.axes.set_xticklabels([str(a) for a in xGrid])
ax.axes.set_yticks(np.arange(len(yGrid)))
ax.axes.set_yticklabels([str(a) for a in yGrid])
ax.axes.set_xbound(-.5,3.5)
ax.axes.set_ybound(-.5,2.5)
ax.axes.set_aspect(.2/3)

For : 在此处输入图片说明

In another answer I mentioned that possible efficiency issues can be solved using Rectangle patches and a PatchCollection — here it is an implementation of this approach. First the initialization, note the imports of Rectangle and PatchCollection

In [99]: import numpy as np 
    ...: import matplotlib.pyplot as plt 
    ...: from matplotlib.collections import PatchCollection 
    ...: from matplotlib.patches import Rectangle 
    ...:  
    ...: np.random.seed(2018) 
    ...: data = np.random.choice([0, 1], size=12).reshape((3, 4)) 
    ...: xGrid = np.linspace(1, 4, 4) 
    ...: yGrid = np.linspace(0.1, 0.3, 3) 
    ...: dx, dy = (xGrid[1]-xGrid[0])/2, (yGrid[1]-yGrid[0])/2 
    ...: print(data)                                                                             
[[0 0 0 1]
 [1 0 0 0]
 [1 1 1 1]]

Next we construct the PatchCollection : we need a provisional list of patches, we loop on the rows of data AND the y coordinates and on the columns in each row AND the x coordinates, if we have to we add a Rectangle to the list of patches and finally we instantiate it

In [100]: patches = [] 
     ...: for y, row in zip(yGrid, data): 
     ...:     for x, col in zip(xGrid, row): 
     ...:         if col: patches.append(Rectangle((x-dx, y-dy), 2*dx, 2*dy)) 
     ...: pc = PatchCollection(patches) 

And in the end the plotting, we need two axis' methods so plt.gca() is needed, we modify the rectangles using methods of the path collection, committ the collection to the ax and finally we call explicitly the autoscale_view method that is required to have the correct axes limits.

In [101]: ax = plt.gca() 
     ...: pc.set_facecolor('yellow') 
     ...: pc.set_edgecolor('black') 
     ...: ax.add_collection(pc) 
     ...: ax.autoscale_view()                                                                    

And this is the result

在此处输入图片说明

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM