How to do a scatter plot with empty circles in Python? - python

In Python, with Matplotlib, how can a scatter plot with empty circles be plotted? The goal is to draw empty circles around some of the colored disks already plotted by scatter(), so as to highlight them, ideally without having to redraw the colored circles.
I tried facecolors=None, to no avail.

From the documentation for scatter:
Optional kwargs control the Collection properties; in particular:
edgecolors:
The string ‘none’ to plot faces with no outlines
facecolors:
The string ‘none’ to plot unfilled outlines
Try the following:
import matplotlib.pyplot as plt
import numpy as np
x = np.random.randn(60)
y = np.random.randn(60)
plt.scatter(x, y, s=80, facecolors='none', edgecolors='r')
plt.show()
Note: For other types of plots see this post on the use of markeredgecolor and markerfacecolor.

Would these work?
plt.scatter(np.random.randn(100), np.random.randn(100), facecolors='none')
or using plot()
plt.plot(np.random.randn(100), np.random.randn(100), 'o', mfc='none')

Here's another way: this adds a circle to the current axes, plot or image or whatever :
from matplotlib.patches import Circle # $matplotlib/patches.py
def circle( xy, radius, color="lightsteelblue", facecolor="none", alpha=1, ax=None ):
""" add a circle to ax= or current axes
"""
# from .../pylab_examples/ellipse_demo.py
e = Circle( xy=xy, radius=radius )
if ax is None:
ax = pl.gca() # ax = subplot( 1,1,1 )
ax.add_artist(e)
e.set_clip_box(ax.bbox)
e.set_edgecolor( color )
e.set_facecolor( facecolor ) # "none" not None
e.set_alpha( alpha )
(The circles in the picture get squashed to ellipses because imshow aspect="auto" ).

In matplotlib 2.0 there is a parameter called fillstyle
which allows better control on the way markers are filled.
In my case I have used it with errorbars but it works for markers in general
http://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.errorbar.html
fillstyle accepts the following values: [‘full’ | ‘left’ | ‘right’ | ‘bottom’ | ‘top’ | ‘none’]
There are two important things to keep in mind when using fillstyle,
1) If mfc is set to any kind of value it will take priority, hence, if you did set fillstyle to 'none' it would not take effect.
So avoid using mfc in conjuntion with fillstyle
2) You might want to control the marker edge width (using markeredgewidth or mew) because if the marker is relatively small and the edge width is thick, the markers will look like filled even though they are not.
Following is an example using errorbars:
myplot.errorbar(x=myXval, y=myYval, yerr=myYerrVal, fmt='o', fillstyle='none', ecolor='blue', mec='blue')

Basend on the example of Gary Kerr and as proposed here one may create empty circles related to specified values with following code:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.markers import MarkerStyle
x = np.random.randn(60)
y = np.random.randn(60)
z = np.random.randn(60)
g=plt.scatter(x, y, s=80, c=z)
g.set_facecolor('none')
plt.colorbar()
plt.show()

So I assume you want to highlight some points that fit a certain criteria. You can use Prelude's command to do a second scatter plot of the hightlighted points with an empty circle and a first call to plot all the points. Make sure the s paramter is sufficiently small for the larger empty circles to enclose the smaller filled ones.
The other option is to not use scatter and draw the patches individually using the circle/ellipse command. These are in matplotlib.patches, here is some sample code on how to draw circles rectangles etc.

Related

how to get different line colors depending on one variable for different plots in one single figure in python? [duplicate]

This question already has an answer here:
Drawing a colorbar aside a line plot, using Matplotlib
(1 answer)
Closed 1 year ago.
Let's say I have one figure with a certain number of plots, which resembles like this one:
where the colors of the single plots are decided automatically by matplotlib. The code to obtain this is very simple:
for i in range(len(some_list)):
x, y = some_function(dataset, some_list[i])
plt.plot(x, y)
Now suppose that all these lines depend on a third variable z. I would like to include this information plotting the given lines with a color that gives information about the magnitude of z, possibly using a colormap and a colorbar on the right side of the figure. What would you suggest me to do? I exclude to use a legend since in my figures I have many more lines that the ones I am showing. All information I can find is about how to draw one single line with different colors, but this is not what I am looking for. I thank you in advance!
Here it is some code that, in my opinion, you can easily adapt to your problem
import numpy as np
import matplotlib.pyplot as plt
from random import randint
# generate some data
N, vmin, vmax = 12, 0, 20
rd = lambda: randint(vmin, vmax)
segments_z = [((rd(),rd()),(rd(),rd()),rd()) for _ in range(N)]
# prepare for the colorization of the lines,
# first the normalization function and the colomap we want to use
norm = plt.Normalize(vmin, vmax)
cm = plt.cm.rainbow
# most important, plt.plot doesn't prepare the ScalarMappable
# that's required to draw the colorbar, so we'll do it instead
sm = plt.cm.ScalarMappable(cmap=cm, norm=norm)
# plot the segments, the segment color depends on z
for p1, p2, z in segments_z:
x, y = zip(p1,p2)
plt.plot(x, y, color=cm(norm(z)))
# draw the colorbar, note that we pass explicitly the ScalarMappable
plt.colorbar(sm)
# I'm done, I'll show the results,
# you probably want to add labels to the axes and the colorbar.
plt.show()

Matplotlib plot has slanted lines

I'm trying to plot projections of coordinates onto a line, but for some reason, Matplotlib is plotting the projections in a slightly slanted manner. Ideally, I would like the (blue) projections to be perpendicular to the (green) line. Here's an image of how it looks with sample data:
As you can see, the angles between the blue lines and the green line are slightly obtuse instead of right. I tried playing around with the rotation parameter to the annotate function, but this did not help. The code for this plot is below, although the data might look a bit different since the random generator is not seeded:
import numpy as np
import matplotlib.pyplot as plt
prefs = {'color':'purple','edgecolors':'black'}
X = np.dot(np.random.rand(2,2), np.random.rand(2,50)).T
pts = np.linspace(-1,1)
v1_m = 0.8076549717643662
plt.scatter(X[:,0],X[:,1],**prefs)
plt.plot(pts, [v1_m*x for x in pts], color='lightgreen')
for x,y in X:
# slope of connecting line
# y = mx+b
m = -np.reciprocal(v1_m)
b = y-m*x
# find intersecting point
zx = b/(v1_m-m)
zy = v1_m*zx
# draw line
plt.annotate('',(zx,zy),(x,y),arrowprops=dict(linewidth=2,arrowstyle='-',color='lightblue'))
plt.show()
The problem lies in the unequal axes which makes it look like they are not at a right angle. Use plt.axis('equal') to have equal axis spans on x- and y-axis and a square figure with equal height and width. plt.axis('scaled') works the same way. As pointed out by #CedricZoppolo, you should set the equal aspect ratios before plt.show(). As per docs, setting the aspect ratio to "equal" means
same scaling from data to plot units for x and y
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8,8))
# Your code here
plt.axis('equal')
plt.show()
Choosing a square figure is not necessary as it works also with rectangular figures as
fig = plt.figure(figsize=(8,6))
# Your code here
plt.axis('equal')
plt.show()
The blue lines not being perpendicular is due to axis not being equal.
You just need to add below line before plt.show()
plt.gca().set_aspect('equal')
Below you can see the resulted graph:

Why doesn't the color of the points in a scatter plot match the color of the points in the corresponding legend?

I have a sample scatterplot via matplotlib via the code below.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 100, 501)
y = np.sin(x)
label = 'xy data sample'
plt.scatter(x, y, cmap='plasma', c=x, label=label)
legend_dict = dict(ncol=1, loc='best', scatterpoints=4, fancybox=True, shadow=True)
plt.legend(**legend_dict)
plt.show()
Running the code above produces the plot below.
The colormap was successfully plotted, but the legend shows points that are all blue rather than points in a color that correspond to the chosen colormap. Why does this happen?
I tried putting cmap='plasma' in legend_dict, but it results in the error below.
File "/Users/.../
site-packages/matplotlib/axes/_axes.py", line 550, in legend
self.legend_ = mlegend.Legend(self, handles, labels, **kwargs)
TypeError: __init__() got an unexpected keyword argument 'cmap'
EDIT:
My desired output is to have the four dots represented in the legend to be a different color via the chosen colormap. Ideally, cmap='plasma' in this example could produce a legend using something similar to a blue dot, then a purple dot, then an orange-red dot, then a yellow dot. Although a colorbar could make for a possible alternative, I have yet to look through any documentation about colorbars.
A colorbar can be achieved via plt.colorbar(). This would allow to directly see the values corresponding to the colors.
Having the points in the legend show different colors is of course also nice, although it would not allow to give any quantitative information.
Unfortunately matplotlib does not provide any inbuilt way to achieve this. So one way would be to subclass the legend handler used to create the legend handle and implement this feature.
Here we create a ScatterHandler with a custom create_collection method, in which we create the desired PathCollection and use this by specifying it in the legend_map dictionary of the legend.
handler_map={ type(sc) : ScatterHandler()}
The following code seems a bit complicated at first sight, however you may simply copy the class without understanding it completely and use it in your code.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerRegularPolyCollection
class ScatterHandler(HandlerRegularPolyCollection):
def update_prop(self, legend_handle, orig_handle, legend):
legend._set_artist_props(legend_handle)
legend_handle.set_clip_box(None)
legend_handle.set_clip_path(None)
def create_collection(self, orig_handle, sizes, offsets, transOffset):
p = type(orig_handle)([orig_handle.get_paths()[0]],
sizes=sizes, offsets=offsets,
transOffset=transOffset,
cmap=orig_handle.get_cmap(),
norm=orig_handle.norm )
a = orig_handle.get_array()
if type(a) != type(None):
p.set_array(np.linspace(a.min(),a.max(),len(offsets)))
else:
self._update_prop(p, orig_handle)
return p
x = np.linspace(0, 100, 501)
y = np.sin(x)*np.cos(x/50.)
sc = plt.scatter(x, y, cmap='plasma', c=x, label='xy data sample')
legend_dict = dict(ncol=1, loc='best', scatterpoints=4, fancybox=True, shadow=True)
plt.legend(handler_map={type(sc) : ScatterHandler()}, **legend_dict)
plt.show()

How to set marker type for a specific point in a matplotlib scatter plot with colormap

I have a user case that, let's say I have three series data: x,y,z.
I would like to make a scatter plot using (x,y) as coordinates and z as the color of scatter points, using cmap keyword of plt.scatter. However, I would like to highlight some specific point by using a different marker type and size than other points.
​A minimum example is like below:
x,y,z = np.random.randn(3,10)
plt.scatter(x,y,c=z,cmap=matplotlib.cm.jet)
plt.colorbar()​
​If I want to use a different marker type for (x[5],y[5],z[5]), how could I do that?
The only way I can think of is to plot again for this point using plt.scatter([x[5],y[5]) but define the color by manually finding the colormap ​color corresponding to z[5]. However this is quite tedious. Is there a better way?
Each scatterplot has one single marker, you cannot by default use different markers in a single scatterplot. Hence, if you are happy to only change the markersize and leave the marker the same, you can supply an array of different sizes to the scatter's s argument.
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(10)
x,y,z = np.random.randn(3,10)
sizes = [36]*len(x)
sizes[5] = 121
plt.scatter(x,y,c=z,s=sizes, cmap=plt.cm.jet)
plt.colorbar()
plt.show()
If you really need a different marker style, you can to plot a new scatter plot. You can then set the colorlimits of the second scatter to the ones from the first.
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(10)
x,y,z = np.random.randn(3,10)
xs, ys, zs = [x[5]], [y[5]], [z[5]]
print xs, ys, zs
y[5] = np.nan
sc = plt.scatter(x,y,c=z,s=36, cmap=plt.cm.jet)
climx, climy = sc.get_clim()
plt.scatter(xs,ys,c=zs,s=121, marker="s", cmap=plt.cm.jet, vmin=climx, vmax=climy )
plt.colorbar()
plt.show()
Finally, a bit of a complicated solution to have several different markers in the same scatter plot would be given in this answer.

Instead of grid lines on a plot, can matplotlib print grid crosses?

I want to have some grid lines on a plot, but actually full-length lines are too much/distracting, even dashed light grey lines. I went and manually did some editing of the SVG output to get the effect I was looking for. Can this be done with matplotlib? I had a look at the pyplot api for grid, and the only thing I can see that might be able to get near it are the xdata and ydata Line2D kwargs.
This cannot be done through the basic API, because the grid lines are created using only two points. The grid lines would need a 'data' point at every tick mark for there to be a marker drawn. This is shown in the following example:
import matplotlib.pyplot as plt
ax = plt.subplot(111)
ax.grid(clip_on=False, marker='o', markersize=10)
plt.savefig('crosses.png')
plt.show()
This results in:
Notice how the 'o' markers are only at the beginning and the end of the Axes edges, because the grid lines only involve two points.
You could write a method to emulate what you want, creating the cross marks using a series of Artists, but it's quicker to just leverage the basic plotting capabilities to draw the cross pattern.
This is what I do in the following example:
import matplotlib.pyplot as plt
import numpy as np
NPOINTS=100
def set_grid_cross(ax, in_back=True):
xticks = ax.get_xticks()
yticks = ax.get_yticks()
xgrid, ygrid = np.meshgrid(xticks, yticks)
kywds = dict()
if in_back:
kywds['zorder'] = 0
grid_lines = ax.plot(xgrid, ygrid, 'k+', **kywds)
xvals = np.arange(NPOINTS)
yvals = np.random.random(NPOINTS) * NPOINTS
ax1 = plt.subplot(121)
ax2 = plt.subplot(122)
ax1.plot(xvals, yvals, linewidth=4)
ax1.plot(xvals, xvals, linewidth=7)
set_grid_cross(ax1)
ax2.plot(xvals, yvals, linewidth=4)
ax2.plot(xvals, xvals, linewidth=7)
set_grid_cross(ax2, in_back=False)
plt.savefig('gridpoints.png')
plt.show()
This results in the following figure:
As you can see, I take the tick marks in x and y to define a series of points where I want grid marks ('+'). I use meshgrid to take two 1D arrays and make 2 2D arrays corresponding to the double loop over each grid point. I plot this with the mark style as '+', and I'm done... almost. This plots the crosses on top, and I added an extra keyword to reorder the list of lines associated with the plot. I adjust the zorder of the grid marks if they are to be drawn behind everything.*****
The example shows the left subplot where by default the grid is placed in back, and the right subplot disables this option. You can notice the difference if you follow the green line in each plot.
If you are bothered by having grid crosses on the boarder, you can remove the first and last tick marks for both x and y before you define the grid in set_grid_cross, like so:
xticks = ax.get_xticks()[1:-1] #< notice the slicing
yticks = ax.get_yticks()[1:-1] #< notice the slicing
xgrid, ygrid = np.meshgrid(xticks, yticks)
I do this in the following example, using a larger, different marker to make my point:
***** Thanks to the answer by #fraxel for pointing this out.
You can draw on line segments at every intersection of the tickpoints. Its pretty easy to do, just grab the tick locations get_ticklocs() for both axis, then loop through all combinations, drawing short line segments using axhline and axvline, thus creating a cross hair at every intersection. I've set zorder=0 so the cross-hairs are drawn first, so that they are behind the plot data. Its easy to control the color/alpha and cross-hair size. Couple of slight 'gotchas'... do the plot before you get the tick locations.. and also the xmin and xmax parameters seem to require normalisation.
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.plot((0,2,3,5,5,5,6,7,8,6,6,4,3,32,7,99), 'r-',linewidth=4)
x_ticks = ax.xaxis.get_ticklocs()
y_ticks = ax.yaxis.get_ticklocs()
for yy in y_ticks[1:-1]:
for xx in x_ticks[1:-1]:
plt.axhline(y=yy, xmin=xx / max(x_ticks) - 0.02,
xmax=xx / max(x_ticks) + 0.02, color='gray', alpha=0.5, zorder=0)
plt.axvline(x=xx, ymin=yy / max(y_ticks) - 0.02,
ymax=yy / max(y_ticks) + 0.02, color='gray', alpha=0.5, zorder=0)
plt.show()

Categories