Manually set color of points in legend - python

I'm making a scatter plot which looks like this:
(MWE at bottom of question)
As can be seen in the image above the colors of the points in the legend are set to blue automatically by matplotlib. I need to set this points to some other color not present in the colormap (ie: black) so they won't generate confusion with the colors associated with said colormap.
I looked around but the matplotlib.legend module does not seem to accept a color keyword. Is there any way to do this?
Here's the MWE:
import matplotlib.pyplot as plt
import numpy as np
def rand_data():
return np.random.uniform(low=0., high=1., size=(100,))
# Generate data.
x, y, x2, x3 = [rand_data() for i in range(4)]
# This data defines the markes and labels used.
x1 = np.random.random_integers(7, 9, size=(100,))
# Order all lists so smaller points are on top.
order = np.argsort(-np.array(x2))
# Order x and y.
x_o, y_o = np.take(x, order), np.take(y, order)
# Order list related to markers and labels.
z1 = np.take(x1, order)
# Order list related to sizes.
z2 = np.take(x2, order)
# Order list related to colors.
z3 = np.take(x3, order)
plt.figure()
cm = plt.cm.get_cmap('RdYlBu')
# Scatter plot where each value in z1 has a different marker and label
# assigned.
mrk = {7: ('o', '7'), 8: ('s', '8'), 9: ('D', '9')}
for key, value in mrk.items():
s1 = (z1 == key)
plt.scatter(x_o[s1], y_o[s1], marker=value[0], label=value[1],
s=z2[s1] * 100., c=z3[s1], cmap=cm, lw=0.2)
# Plot colorbar
plt.colorbar()
# Plot legend.
plt.legend(loc="lower left", markerscale=0.7, scatterpoints=1, fontsize=10)
plt.show()

You can obtain the legend handles and change their colors individually:
ax = plt.gca()
leg = ax.get_legend()
leg.legendHandles[0].set_color('red')
leg.legendHandles[1].set_color('yellow')

Adding to the other answers – I've had trouble in the past changing color of legend markers with set_color. An alternate approach is to build the legend yourself:
import matplotlib.lines as mlines
eight = mlines.Line2D([], [], color='blue', marker='s', ls='', label='8')
nine = mlines.Line2D([], [], color='blue', marker='D', ls='', label='9')
# etc etc
plt.legend(handles=[eight, nine])
Building legends from scratch can sometimes save the hassle of dealing with the obscure internals of an already built legend. More information in Matplotlib docs here.

You can retrieve the label of each legend handle with lh.get_label() if you want to map colors to specific labels.
For my purposes it worked best to create a dict from legendHandles and change the colors like so:
ax = plt.gca()
leg = ax.get_legend()
hl_dict = {handle.get_label(): handle for handle in leg.legendHandles}
hl_dict['9'].set_color('red')
hl_dict['8'].set_color('yellow')

While I found that the solution with legendHandles[i].set_color did not work for errorbar, I managed to do the following workaround:
ax_legend = fig.add_subplot(g[3, 0])
ax_legend.axis('off')
handles_markers = []
markers_labels = []
for marker_name, marker_style in markers_style.items():
pts = plt.scatter([0], [0], marker=marker_style, c='black', label=marker_name)
handles_markers.append(pts)
markers_labels.append(marker_name)
pts.remove()
ax_legend.legend(handles_markers, markers_labels, loc='center', ncol=len(markers_labels), handlelength=1.5, handletextpad=.1)
See this GitHub issue as well.

Related

Matplotlib Draw a Constant y Axis

I want to use matpoltlib to make a plot that with a constant y axis(always from 0 to 14 and the gap is 1), since I want to make labels for them and my dot values will be(x, y) where y is from 0 to 14 gap 1, and a changing x axis. I already tried to play with y ticks. And here is my code for that:
fig, ax = plt.subplots()
fig.canvas.draw()
plt.yticks(np.arange(0, 14, 1))
labels = [item.get_text() for item in ax.get_yticklabels()]
labels[1] = 'Not Detected'
labels[2] = 'A/G'
labels[3] = 'G/G'
labels[4] = 'C/T'
labels[5] = 'C/C'
labels[6] = 'A/A'
labels[7] = '-1'
labels[8] = 'ε3/ε3'
labels[9] = 'A/C'
labels[10] = 'T/T'
labels[11] = 'C/G'
labels[12] = 'ε2/ε3'
labels[13] = 'G/T'
ax.set_yticklabels(labels)
what I'm thinking about is to use some values or lines with white color so those y axis will appear. But I'm looking for a more efficient way of doing it. And here is the diagram I generated with the current code. It only shows C/C right now and I want all labels to appear in the diagram.
I tried draw white points with:
x1 = np.arange(n)
y1 = np.arange(1,15,1)
plt.scatter(x1,y1,color = 'white')
Which did give me what I want: But I was wondering whether there is a lib setting that can do this.
I would recommend just using a fixed locator and fixed formatter for your y axis. The function, ax.set_yticklabels() is simply a convenience wrapper for these tick methods.
I would also recommend having your y_labels in a list or using a loop structure as this is a more generalizable and modifiable implementation.
If I'm understanding the goals of your plot correctly, something like this may work well for you.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
#make some data
x = np.arange(25)
y = np.random.randint(1, 14, size=25)
#convert y labels to a list
y_labels = [
'Not Detected','A/G','G/G','C/T','C/C','A/A',
'-1','ε3/ε3', 'A/C','T/T','C/G','ε2/ε3','G/T'
]
#define figure/ax and set figsize
fig, ax = plt.subplots(figsize=(12,8))
#plot data, s is marker size, it's points squared
ax.scatter(x, y, marker='x', s=10**2, color='#5d2287', linewidth=2)
#set major locator and formatter to fixed, add grid, hide top/right spines
locator = ax.yaxis.set_major_locator(mpl.ticker.FixedLocator(np.arange(1, 14)))
formatter = ax.yaxis.set_major_formatter(mpl.ticker.FixedFormatter(y_labels))
grid = ax.grid(axis='y', dashes=(8,3), alpha=0.3, color='gray')
spines = [ax.spines[x].set_visible(False) for x in ['top','right']]
params = ax.tick_params(labelsize=12) #increase label font size

matplotlib basemap plotting legend corresponding to size of points on map

I'm using matplotlib's basemap functionality to plot data points on a map. Each point is weighed by how many co-occurring points exist within a 5-kM radius. I'd like to put a reference table that corresponds to different-sized outbreaks at the bottom, however I can't figure out how to do this. This is my code so far:
map = Basemap(llcrnrlon=-20.,llcrnrlat=-40,urcrnrlon=160.,urcrnrlat=40.,projection='cyl', lat_0=13.5317, lon_0=2.4604)
map.drawmapboundary(fill_color='paleturquoise')
map.fillcontinents(color='olivedrab',lake_color='paleturquoise')
map.drawcoastlines()
map.drawcountries()
map.drawstates()
used = set()
for i,j,k,n in DATA:
if map.is_land(i,j):
if k in used: continue
used.add(k)
alpha = 0.5
if n == 1:
alpha = 1
n *= 3
map.plot(i, j, marker='o',color='r',ms=n, alpha=alpha)
plt.show()
note, DATA is a list of 4-tuples. Each entry in the 4-tuple corresponds to the (latitude, longitude, unique ID corresponding to points co-occuring within a 5x5 km square, number of points with the same uniq ID)
result:
The most obvious option is to first create custom labels and handles in matplotlib and then to intitialize a custom legend with them. For instance, if we choose a "showcase" sample of five point sizes, ranging from 1 to 5, you might want to do something along the lines of:
def outbreak_artist(size):
""" Returns a single-point marker artist of a given size """
# note that x3 factor corresponds to your
# internal scaling within the for loop
return plt.Line2D((0,), (0,), color='r', marker='o',
ms=size*3, alpha=alpha, linestyle='')
sizes = [1, 2, 3, 4, 5]
# adapted from https://stackoverflow.com/a/4701285/4118756
# to place the legend beneath the figure neatly
ax = plt.gca()
box = ax.get_position()
ax.set_position([box.x0, box.y0 + box.height * 0.1,
box.width, box.height * 0.9])
red_dots = [outbreak_artist(size) for size in sizes]
labels = ["{} outbreaks".format(size) for size in sizes]
ax.legend(red_dots, labels, loc='upper center',
bbox_to_anchor=(0.5, -0.05), ncol=5)
plt.show()
I fiddled with the legend positioning a bit to bring it out of the plot following this post.
P.S.: I think I ran fig, ax = plt.subplots(figsize = (9.44, 4.76)) before making a Basemap to make the legend size align with the map size.

User defined legend in python

I have this plot in which some areas between curves are being filled by definition. Is there any way to include them in legend? Especially where those filled areas are overlapped and as well as that a new and different color is being appeared.
Or there is possibility to define an arbitrary legend regardless of the curves' data?
Using fill_bettween to plot your data will automatically include the filled area in the legend.
To include the areas where the two datasets overlap, you can combine the legend handles from both dataset into a single legend handle.
As pointed out in the comments, you can also define any arbitrary legend handle with a proxy.
Finally, you can define exactly what handles and labels you want to appear in the legend, regardless of the data plotted in your graph.
See the MWE below that illustrates the points stated above:
import matplotlib.pyplot as plt
import numpy as np
plt.close('all')
# Gererate some datas:
x = np.random.rand(50)
y = np.arange(len(x))
# Plot data:
fig, ax = plt.subplots(figsize=(11, 4))
fillA = ax.fill_between(y, x-0.25, 0.5, color='darkolivegreen', alpha=0.65, lw=0)
fillB = ax.fill_between(y, x, 0.5, color='indianred', alpha=0.75, lw=0)
linec, = ax.plot(y, np.zeros(len(y))+0.5, color='blue', lw=1.5)
linea, = ax.plot(y, x, color='orange', lw=1.5)
lineb, = ax.plot(y, x-0.25, color='black', lw=1.5)
# Define an arbitrary legend handle with a proxy:
rec1 = plt.Rectangle((0, 0), 1, 1, fc='blue', lw=0, alpha=0.25)
# Generate the legend:
handles = [linea, lineb, linec, fillA, fillB, (fillA, fillB),
rec1, (fillA, fillB, rec1)]
labels = ['a', 'b', 'c', 'A', 'B', 'A+B', 'C', 'A+B+C']
ax.legend(handles, labels, loc=2, ncol=4)
ax.axis(ymin=-1, ymax=2)
plt.show()
Yes, you are absolutely right ian_itor, tacaswell and Jean-Sébastien, user defined legend seems to be the unique solution, in addition I made different linewidth for those area to be distinguishable from the curves, and playing with alpha got the right color.
handles, labels = ax.get_legend_handles_labels()
display = (0,1,2,3,4)
overlap_1 = plt.Line2D((0,1),(0,0), color='firebrick', linestyle='-',linewidth=15, alpha = 0.85)
overlap_2= plt.Line2D((0,1),(0,0), color='darkolivegreen',linestyle='-',linewidth=15, alpha = 0.65)
over_lo_3= plt.Line2D((0,1),(0,0), color='indianred',linestyle='-',linewidth=15, alpha = 0.75)
ax.legend([handle for i,handle in enumerate(handles) if i in display]+[overlap_1 , overlap_2 , overlap_3 ],
[label for i,label in enumerate(labels) if i in display]+['D','F','G'])

How to make a scatter plot of different sizes, colour, and positions?

How to make a scatter plot with random values of delta_x and delta_y positions; where each point has certain frequency value(color intensity changes depending upon intensity), i.e., a certain symbols.
Example plot: (from Alberdi, et al, 2013)
If I'm understanding you correctly, you're asking how to have scatter share a color scale but have different symbols for different groups, correct?
There are a few different ways to handle this.
The key is to call scatter multiple times (one for each different group), but pass in the same vmin, vmax, and cmap arguments.
As an complete (and arguably over-complex) example of reproducing the plot above:
import numpy as np
import matplotlib.pyplot as plt
# Generate data
freq_groups = [1.7, 2.3, 5.0, 8.4]
num = 50
x = np.random.normal(0, 0.5, num)
y = np.random.normal(0.2, 0.5, num)
year = 9 * np.random.random(num) + 1993.5
frequencies = np.random.choice(freq_groups, num)
symbols = ['o', '^', 's', 'd']
# Plot data
fig, ax = plt.subplots(figsize=(8, 9))
for freq, marker in zip(freq_groups, symbols):
mask = np.isclose(freq, frequencies)
scat = ax.scatter(x[mask], y[mask], c=year[mask], s=100, marker=marker,
cmap='jet_r', vmin=year.min(), vmax=year.max(),
label='{:0.1f} GHz'.format(freq), color='black')
ax.legend(loc='upper left', scatterpoints=1)
ax.set(xlabel='Relative RA (mas)', ylabel='Relative Dec (mas)')
ax.invert_xaxis()
cbar = fig.colorbar(scat, orientation='horizontal')
cbar.set_label('Epoch (year)')
cbar.formatter.useOffset = False
cbar.update_ticks()
fig.tight_layout()
plt.show()

Plot representative errorbar size in legend?

I have some data (with errors) to be plotted in a rather dense display. I would like to plot these points without errorbars (because it makes it too busy), but to plot a representative error bar in a legend (which shows the errorbar with an accurate size).
Here is what I have so far (which is not successful).
import pylab as pl
p1, = pl.plot([1,2,3], label="test1")
p2, = pl.plot([3,2,1], label="test2")
errorbarsize = 1.65 # Need this to be properly scaled in the legend
# p3, = pl.plot([1], label='data', color="red") # works
# p3, = pl.scatter(1, 1, label='data', color="red")
# p3, = pl.errorbar(1, 1, yerr=errorbarsize, label='data', color="red")
l1 = pl.legend([p1], ["Label 1"], loc=1)
l2 = pl.legend([p2], ["Label 2"], loc=2) # this removes l1 from the axes.
l3 = pl.legend([p3], ["Label 3"], loc=3, numpoints=1)
gca().add_artist(l1) # add l1 as a separate artist to the axes
gca().add_artist(l2) # add l2 as a separate artist to the axes
Also, it would be best if I could plot this in a separate legend, but that might be asking too much.
Here is an example using Thorsten Kranz's suggestion, and another example of how to show representative error bars...
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(2,1)
# -- make some fake data
x = np.random.normal(loc=4, size=100)
x.sort()
y = np.random.normal(loc=5, size=100)
y.sort()
y_errorbarsize = y.std()
x_errorbarsize = y.std()
### Example 1
# From Thorsten Kranz comment...
axs[0].plot(x, y, label="Example #1")
axs[0].fill_between(x,y-y_errorbarsize/2, y+y_errorbarsize/2,alpha=0.3)
### Example 2
axs[1].scatter(x, y, label="Example #2", alpha=0.8)
# Place our representative error bar by hand.
axis_coordinates_of_representative_error_bar = (0.1, 0.8)
screen_coordinates_of_representative_error_bar = axs[1].transAxes.transform(axis_coordinates_of_representative_error_bar)
screen_to_data_transform = axs[1].transData.inverted().transform
data_coordinates_of_representative_error_bar = screen_to_data_transform(screen_coordinates_of_representative_error_bar)
foo = data_coordinates_of_representative_error_bar
axs[1].errorbar(foo[0], foo[1], xerr=x_errorbarsize/2, yerr=y_errorbarsize/2, capsize=3)
plt.show()
plt.close()

Categories

Resources