My script for plotting creates two legends for each label. I do not know how to make legend() not duplicate. I checked on stackoverflow and found two methods. But I could not implement them here. Any ideas?
Matplotlib: Don't show errorbars in legend
Stop matplotlib repeating labels in legend
symbols = [u'\u2193']
#Plotting our vsini values
for i, symbol in enumerate(symbols):
for x0,y0 in zip(vsini_slit_cl, vsini_slit):
plt.text(x0,y0, symbol, fontname='STIXGeneral', size = 10, va='center', ha='center', clip_on=True,color = '#737373')
for i, symbol in enumerate(symbols):
for x0, y0 in zip(vsini_cl_sl, vsini_sl):
plt.text(x0, y0, symbol, fontname='STIXGeneral', size = 10, va='center', ha='center', clip_on=True)
# PLOTTING VSINI FROM LITERATURE
plt.plot((vmag_lit-jmag_lit), vsini_lit, 'o', color = '#a6a6a6', label='Literature')
# PLOTTING SLOW VSINI FROM LITERATURE
plt.plot(vsini_slit_cl, vsini_slit, 'o', color = '#a6a6a6')
# PLOTTING VSINI FROM OUR WORK
plt.plot(vsini_cl_sl, vsini_sl, 'o', label='This Work' )
plt.errorbar(vsini_color, vsini_chad, yerr=vsini_chad_sig, fmt='bo', capsize=3)
plt.legend()
plt.savefig('vsini_colors.jpg', dpi=200)
Just use
plt.legend(numpoints=1)
The default behavior is to use 2 points, which is what you need to make a legend entry for lines.
See: legend user guide and plt.legend doc and legend doc
Related
I am using the following code to generate this heatmap:
h= np.vstack((aug2014, sep2014,oct2014, nov2014, dec2014, jan2015, feb2015, mar2015, apr2015, may2015, jun2015, jul2015, aug2015))
dim = np.arange(1, 32, 1)
fig, ax = plt.subplots(figsize=(9,3))
heatmap = ax.imshow(h, cmap=plt.cm.get_cmap('Blues', 4), aspect=0.5, clim=[1,144])
cbar = fig.colorbar(heatmap, ticks = [1, 36, 72, 108, 144], label = 'Number of valid records per day')
ax.set_xlabel("Days", fontsize=15)
ax.set_ylabel("Months", fontsize=15)
ax.set_title("Number of valid records per day", fontsize=20)
ax.set_xticks(range(0,31))
ax.set_xticklabels(dim, rotation=45, ha='center', minor=False)
ax.set_yticks(range(0,13,1))
ax.set_yticklabels(ylabel[7:20])
ax.grid(which = 'minor', color = 'w')
ax.set_facecolor('gray')
fig.show()
As you can see, the labels on the y-axis are not very readable. I was wondering whether there would be a way for me to either increase the dimension of the grid cell or change the scale on the axis to increase the space between the labels. I have tried changing the figsize but all it did was to make the colorbar much bigger than the heatmap. I also have have two subsidiary questions:
Can someone explain to me why the grid lines do not show on the figure although I have defined them?
How can I increase the font of the colorbar title?
Any help would be welcomed!
What is the best way to specify my colorbar legend location while ensuring the legend title is within the figure? Sometimes the location will be upper right, as shown here; but in other plots it will be variable, upper/lower left/right.
It is okay if the solution doesn't use inset_axes().
Alternative Solution:
It would also be okay if the colorbar legend is to the right of the subplot, if the "My Legend" title is vertical and on the left, and the tick labels are on the right and horizontal (I don't know how to do that).
Using Python 3.8.
## Second Plot
vals2 = ax2.scatter(df.x, df.y, edgecolors = 'none', c = df.z,
norm = mcolors.LogNorm(), cmap=rainbow)
ax2.set_aspect('equal')
ax2.set_title('Subplot Title', style='italic');
ax2.set_xlabel('x');
ax2.set_ylabel('y');
cbaxes = inset_axes(ax2, width="30%", height="10%", location = 'upper right')
clb = plt.colorbar(vals2, cax=cbaxes, format = '%1.2f', orientation='horizontal');
clb.ax.set_title('My Legend')
I would still prefer to have the colorbar (with tick labels and title) inside the subplot; but I did find a way to do the Alternative Solution:
vals2 = ax2.scatter(df.x, df.y, edgecolors = 'none', c = df.z,
norm = mcolors.LogNorm(), cmap=rainbow)
ax2.set_aspect('equal')
ax2.set_title('Subplot Title', style='italic');
ax2.set_xlabel('x');
ax2.set_ylabel('y');
clb = fig.colorbar(slips2, ax=ax2, format = '%1.2g', location='right', aspect=25)
clb.ax.set_ylabel('My Legend')
clb.ax.yaxis.set_label_position('left')
The color bar is taller than the subplot because ax2 is constrained to be equal xy aspect ratio based on the limits in another subplot (ax1, not shown).
I was making a plot of f(x,y,z) and wanted this to be displayed in a 2D-plane. To avoid cluttering my legend i decided to have different linestyles for y, different colors for z and place the two in two separate legends. I couldn't find out how to do this even after a lot of digging, so I'm posting the solution i came up with here :) If anyone has more elegant solutions I'm all ears :)
Basically the solution was to make three plots, set two of them to have size (0,0) and place those two where i wanted the legends. It feels like an ugly way to do it, but it gave a nice plot and i didn't find any other way :) The resulting plot looks like this:
def plot_alt(style = 'log'):
cmap = cm.get_cmap('inferno')
color_scale = 1.2 #Variable to get colors from a certain part of the colormap
#Making grids for delta T and average concentration
D_T_axis = -np.logspace(np.log10(400), np.log10(1), 7)
C_bar_list = np.linspace(5,10,4)
ST_list = np.logspace(-3,-1,100)
# f(x,y,z)
DC_func = lambda C_bar, ST, DT: 2*C_bar * (1 - np.exp(ST*DT))/(1 + np.exp(ST*DT))
#Some different linestyles
styles = ['-', '--', '-.', ':']
fig, ax = plt.subplots(1,3, figsize = (10,5))
plt.sca(ax[0])
for i, C_bar in enumerate(C_bar_list): #See plot_c_rel_av_DT() for 'enumerate'
for j, DT in enumerate(D_T_axis):
plt.plot(ST_list, DC_func(C_bar, ST_list, DT), color = cmap(np.log10(-DT)/(color_scale*np.log10(-D_T_axis[0]))),
linestyle = styles[i])
# Generating separate legends by plotting lines in the two other subplots
# Basically: to get two separate legends i make two plots, place them where i want the legends
# and set their size to zero, then display their legends.
plt.sca(ax[1]) #Set current axes to ax[1]
for i, C_bar in enumerate(C_bar_list):
# Plotting the different linestyles
plt.plot(C_bar_list, linestyle = styles[i], color = 'black', label = str(round(C_bar, 2)))
plt.sca(ax[2])
for DT in D_T_axis:
#plotting the different colors
plt.plot(D_T_axis, color = cmap(np.log10(-DT)/(color_scale*np.log10(-D_T_axis[0]))), label = str(int(-DT)))
#Placing legend
#This is where i move and scale the three plots to make one plot and two legends
box0 = ax[0].get_position() #box0 is an object that contains the position and dimentions of the ax[0] subplot
box2 = ax[2].get_position()
ax[0].set_position([box0.x0, box0.y0, box2.x0 + 0.4*box2.width, box0.height])
box0 = ax[0].get_position()
ax[1].set_position([box0.x0 + box0.width, box0.y0 + box0.height + 0.015, 0,0])
ax[1].set_axis_off()
ax[2].set_position([box0.x0 + box0.width ,box0.y0 + box0.height - 0.25, 0,0])
ax[2].set_axis_off()
#Displaying plot
plt.sca(ax[0])
plt.xscale('log')
plt.xlim(0.001, 0.1)
plt.ylim(0, 5)
plt.xlabel(r'$S_T$')
plt.ylabel(r'$\Delta C$')
ax[1].legend(title = r'$\langle c \rangle$ [mol/L]',
bbox_to_anchor = (1,1), loc = 'upper left')
ax[2].legend(title = r'$-\Delta T$ [K]', bbox_to_anchor = (1,1), loc = 'upper left')
#Suptitle is the title of the figure. You can also have titles for the individual subplots
plt.suptitle('Steady state concentration gradient as a function of Soret-coefficient\n'
'for different temperature gradients and total concentrations')
I'd like a function in Matplotlib similar to the Matlab 'scatterhist' function which takes continuous values for 'x' and 'y' axes, plus a categorical variable as input; and produces a scatter plot with marginal KDE plots and two or more categorical variables in different colours as output:
I've found examples of scatter plots with marginal histograms in Matplotlib, marginal histograms in Seaborn jointplot, overlapping histograms in Matplotlib and marginal KDE plots in Matplotib ; but I haven't found any examples which combine scatter plots with marginal KDE plots and are colour coded to indicate different categories.
If possible, I'd like a solution which uses 'vanilla' Matplotlib without Seaborn, as this will avoid dependencies and allow complete control and customisation of the plot appearance using standard Matplotlib commands.
I was going to try to write something based on the above examples; but before doing so wanted to check whether a similar function was already available, and if not then would be grateful for any guidance on the best approach to use.
#ImportanceOfBeingEarnest: Many thanks for your help.
Here's my first attempt at a solution.
It's a bit hacky but achieves my objectives, and is fully customisable using standard matplotlib commands. I'm posting the code here with annotations in case anyone else wishes to use it or develop it further. If there are any improvements or neater ways of writing the code I'm always keen to learn and would be grateful for guidance.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
from scipy import stats
label = ['Setosa','Versicolor','Virginica'] # List of labels for categories
cl = ['b','r','y'] # List of colours for categories
categories = len(label)
sample_size = 20 # Number of samples in each category
# Create numpy arrays for dummy x and y data:
x = np.zeros(shape=(categories, sample_size))
y = np.zeros(shape=(categories, sample_size))
# Generate random data for each categorical variable:
for n in range (0, categories):
x[n,:] = np.array(np.random.randn(sample_size)) + 4 + n
y[n,:] = np.array(np.random.randn(sample_size)) + 6 - n
# Set up 4 subplots as axis objects using GridSpec:
gs = gridspec.GridSpec(2, 2, width_ratios=[1,3], height_ratios=[3,1])
# Add space between scatter plot and KDE plots to accommodate axis labels:
gs.update(hspace=0.3, wspace=0.3)
# Set background canvas colour to White instead of grey default
fig = plt.figure()
fig.patch.set_facecolor('white')
ax = plt.subplot(gs[0,1]) # Instantiate scatter plot area and axis range
ax.set_xlim(x.min(), x.max())
ax.set_ylim(y.min(), y.max())
ax.set_xlabel('x')
ax.set_ylabel('y')
axl = plt.subplot(gs[0,0], sharey=ax) # Instantiate left KDE plot area
axl.get_xaxis().set_visible(False) # Hide tick marks and spines
axl.get_yaxis().set_visible(False)
axl.spines["right"].set_visible(False)
axl.spines["top"].set_visible(False)
axl.spines["bottom"].set_visible(False)
axb = plt.subplot(gs[1,1], sharex=ax) # Instantiate bottom KDE plot area
axb.get_xaxis().set_visible(False) # Hide tick marks and spines
axb.get_yaxis().set_visible(False)
axb.spines["right"].set_visible(False)
axb.spines["top"].set_visible(False)
axb.spines["left"].set_visible(False)
axc = plt.subplot(gs[1,0]) # Instantiate legend plot area
axc.axis('off') # Hide tick marks and spines
# Plot data for each categorical variable as scatter and marginal KDE plots:
for n in range (0, categories):
ax.scatter(x[n],y[n], color='none', label=label[n], s=100, edgecolor= cl[n])
kde = stats.gaussian_kde(x[n,:])
xx = np.linspace(x.min(), x.max(), 1000)
axb.plot(xx, kde(xx), color=cl[n])
kde = stats.gaussian_kde(y[n,:])
yy = np.linspace(y.min(), y.max(), 1000)
axl.plot(kde(yy), yy, color=cl[n])
# Copy legend object from scatter plot to lower left subplot and display:
# NB 'scatterpoints = 1' customises legend box to show only 1 handle (icon) per label
handles, labels = ax.get_legend_handles_labels()
axc.legend(handles, labels, scatterpoints = 1, loc = 'center', fontsize = 12)
plt.show()`
`
Version 2, using Pandas to import 'real' data from a csv file, with a different number of entries in each category. (csv file format: row 0 = headers; col 0 = x values, col 1 = y values, col 2 = category labels). Scatterplot axis and legend labels are generated from column headers.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
from scipy import stats
import pandas as pd
"""
Create scatter plot with marginal KDE plots
from csv file with 3 cols of data
formatted as following example (first row of
data are headers):
'x_label', 'y_label', 'category_label'
4,5,'virginica'
3,6,'sentosa'
4,6, 'virginica' etc...
"""
df = pd.read_csv('iris_2.csv') # enter filename for csv file to be imported (within current working directory)
cl = ['b','r','y', 'g', 'm', 'k'] # Custom list of colours for each categories - increase as needed...
headers = list(df.columns) # Extract list of column headers
# Find min and max values for all x (= col [0]) and y (= col [1]) in dataframe:
xmin, xmax = df.min(axis=0)[0], df.max(axis=0)[0]
ymin, ymax = df.min(axis=0)[1], df.max(axis=0)[1]
# Create a list of all unique categories which occur in the right hand column (ie index '2'):
category_list = df.ix[:,2].unique()
# Set up 4 subplots and aspect ratios as axis objects using GridSpec:
gs = gridspec.GridSpec(2, 2, width_ratios=[1,3], height_ratios=[3,1])
# Add space between scatter plot and KDE plots to accommodate axis labels:
gs.update(hspace=0.3, wspace=0.3)
fig = plt.figure() # Set background canvas colour to White instead of grey default
fig.patch.set_facecolor('white')
ax = plt.subplot(gs[0,1]) # Instantiate scatter plot area and axis range
ax.set_xlim(xmin, xmax)
ax.set_ylim(ymin, ymax)
ax.set_xlabel(headers[0], fontsize = 14)
ax.set_ylabel(headers[1], fontsize = 14)
ax.yaxis.labelpad = 10 # adjust space between x and y axes and their labels if needed
axl = plt.subplot(gs[0,0], sharey=ax) # Instantiate left KDE plot area
axl.get_xaxis().set_visible(False) # Hide tick marks and spines
axl.get_yaxis().set_visible(False)
axl.spines["right"].set_visible(False)
axl.spines["top"].set_visible(False)
axl.spines["bottom"].set_visible(False)
axb = plt.subplot(gs[1,1], sharex=ax) # Instantiate bottom KDE plot area
axb.get_xaxis().set_visible(False) # Hide tick marks and spines
axb.get_yaxis().set_visible(False)
axb.spines["right"].set_visible(False)
axb.spines["top"].set_visible(False)
axb.spines["left"].set_visible(False)
axc = plt.subplot(gs[1,0]) # Instantiate legend plot area
axc.axis('off') # Hide tick marks and spines
# For each category in the list...
for n in range(0, len(category_list)):
# Create a sub-table containing only entries matching current category:
st = df.loc[df[headers[2]] == category_list[n]]
# Select first two columns of sub-table as x and y values to be plotted:
x = st[headers[0]]
y = st[headers[1]]
# Plot data for each categorical variable as scatter and marginal KDE plots:
ax.scatter(x,y, color='none', s=100, edgecolor= cl[n], label = category_list[n])
kde = stats.gaussian_kde(x)
xx = np.linspace(xmin, xmax, 1000)
axb.plot(xx, kde(xx), color=cl[n])
kde = stats.gaussian_kde(y)
yy = np.linspace(ymin, ymax, 1000)
axl.plot(kde(yy), yy, color=cl[n])
# Copy legend object from scatter plot to lower left subplot and display:
# NB 'scatterpoints = 1' customises legend box to show only 1 handle (icon) per label
handles, labels = ax.get_legend_handles_labels()
axc.legend(handles, labels, title = headers[2], scatterpoints = 1, loc = 'center', fontsize = 12)
plt.show()
I'm trying to add a legend to a matplotlib radar/polar graph. I am very new to matplotlib so please excuse the code. I also expect this is very simple but I've been at it an hour and got nowhere.
I have the following which produces a list of labels in the bottom left corner but whenever I try to add handles to give the color representing the label I lose the legend.
# Set color of axes
plt.rc('axes', linewidth=0.5, edgecolor="#888888")
# Create polar plot
ax = plt.subplot(111, polar=True)
# Set clockwise rotation. That is:
ax.set_theta_offset(pi / 2)
ax.set_theta_direction(-1)
# Set position of y-labels
ax.set_rlabel_position(0)
# Set color and linestyle of grid
ax.xaxis.grid(True, color="#888888", linestyle='solid', linewidth=0.5)
ax.yaxis.grid(True, color="#888888", linestyle='solid', linewidth=0.5)
# Plot data
ax.plot(x_as, values, linewidth=0, linestyle='solid', zorder=3)
# Fill area
ax.fill(x_as, values, 'r', alpha=0.3)
plt.legend(labels=[self.get_object().name], loc=(-.42,-.13))
if not self.get_object().subscription is None:
if self.get_object().subscription.benchmark:
bx = plt.subplot(111, polar=True)
bx.plot(x_as, baseline, linewidth=0, linestyle='solid', zorder=3)
bx.fill(x_as, baseline, 'b', alpha=0.3)
plt.legend(labels=[self.get_object().name, 'Benchmark'], loc=(-.42,-.13))
I believe I need
plt.lengend(handles=[some list], labels=[self.get_object().name, 'Benchmark'], loc=(-.42,-.13))
I do not understand what the list of handles should be and I've tried a number of things, including [ax, bx], [ax.plt(), bx.plt()], ['r', 'b']
From the documentation:
handles : sequence of Artist, optional
A list of Artists (lines, patches) to be added to the legend. Use this together with labels, if you need full control on what is shown
in the legend and the automatic mechanism described above is not
sufficient.
The length of handles and labels should be the same in this case. If they are not, they are truncated to the smaller length.
plt.plot returns a list a line2D objects which is what you need to pass to plt.legend(). Therefore a simplified example is as follows:
labels = ["Line 1", "Line 2"]
lines1, = plt.plot([1,2,3])
lines2, = plt.plot([3,2,1])
handles = [lines1, lines2]
plt.legend(handles, labels)
plt.show()