How to have two legends in a python scatter plot? - python

I have four sets of data with eight (ordered) data points each. I would like to do a scatter plot where you can see the data points distinguished by the different data sets (symbol) and data points (color) with two legends. I prepared a minimal example below that shows what my scatter plot should look like.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as clrs
# t contains the labels for the data sets
t = [0.0, -0.5, -1.0, -1.5, -2.0, -2.5, -3.0, -3.5]
# create the color map
magma = plt.cm.get_cmap('magma', 8)
newcolors = magma(np.linspace(0, 1, 8))
cm = clrs.ListedColormap(newcolors)
fig, ax = plt.subplots()
# create for scatter plots for the four different data sets. Each scatter plot has a different marker and label.
scatter1 = ax.scatter(np.random.rand(8), np.random.rand(8), c=t, marker='o', label="label1", cmap=cm)
scatter2 = ax.scatter(np.random.rand(8), np.random.rand(8), c=t, marker='^', label="label2", cmap=cm)
scatter3 = ax.scatter(np.random.rand(8), np.random.rand(8), c=t, marker='d', label="label3", cmap=cm)
scatter4 = ax.scatter(np.random.rand(8), np.random.rand(8), c=t, marker='X', label="label4", cmap=cm)
# produce a legend with the unique colors from the scatter
legend1 = ax.legend(*scatter1.legend_elements(), loc="upper left", title="Legend 1")
for i in range(8):
legend1.legendHandles[i].set_color(newcolors[i])
ax.add_artist(legend1)
# create a second legend that shows the markers and labels
legend2 = ax.legend(loc="lower right", title="Legend 2")
legend2.legendHandles[0].set_color('m')
legend2.legendHandles[1].set_color('m')
legend2.legendHandles[2].set_color('m')
legend2.legendHandles[3].set_color('m')
ax.add_artist(legend2)
plt.show()
The only thing I'm not happy about is the color in Legend 2. I don't know why but, the set_color option only changes the color of the edges. The fill color is the first color in my colormap...
Thanks for any help!
Follow up question: How can I change the symbols in legend 1, e.g., squares instead of circles?

Related

How to increase plottable space above a subplot in matplotlib?

I am currently making a plot on matplotlib, which looks like below.
The code for which is:
fig, ax1 = plt.subplots(figsize=(20,5))
ax2 = ax1.twinx()
# plt.subplots_adjust(top=1.4)
ax2.fill_between(dryhydro_df['Time'],dryhydro_df['Flow [m³/s]'],0,facecolor='lightgrey')
ax2.set_ylim([0,10])
AB = ax2.fill_between(dryhydro_df['Time'],[12]*len(dryhydro_df['Time']),9.25,facecolor=colors[0],alpha=0.5,clip_on=False)
ab = ax2.scatter(presence_df['Datetime'][presence_df['AB']==True],[9.5]*sum(presence_df['AB']==True),marker='X',color='black')
# tidal heights
ax1.plot(tide_df['Time'],tide_df['Tide'],color='dimgrey')
I want the blue shaded region and black scatter to be above the plot. I can move the elements above the plot by using clip_on=False but I think I need to extend the space above the plot to do visualise it. Is there a way to do this? Mock-up of what I need is below:
You can use clip_on=False to draw outside the main plot. To position the elements, an xaxis transform helps. That way, x-values can be used in the x direction, while the y-direction uses "axes coordinates". ax.transAxes() uses "axes coordinates" for both directions.
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
dates = pd.date_range('2018-07-01', '2018-07-31', freq='H')
xs = dates.to_numpy().astype(float)
ys = np.sin(xs * .091) * (np.sin(xs * .023) ** 2 + 1)
fig, ax1 = plt.subplots(figsize=(20, 5))
ax1.plot(dates, ys)
ax1.scatter(np.random.choice(dates, 10), np.repeat(1.05, 10), s=20, marker='*', transform=ax1.get_xaxis_transform(),
clip_on=False)
ax1.plot([0, 1], [1.05, 1.05], color='steelblue', lw=20, alpha=0.2, transform=ax1.transAxes, clip_on=False)
plt.tight_layout() # fit labels etc. nicely
plt.subplots_adjust(top=0.9) # make room for the additional elements
plt.show()

Python Geopandas: Single Legend for multiple plots

How do I use a single legend for multiple geopandas plots?
Right now I have a Figure like this:
This post explains how to set legend values to the same for each plot. Though, i would like to have single legend for all plots. Optimally it should be possible to have multiple legends for different df's that I want to plot. E.g. the lines you see in the pictures also have a description.
Here is my current code:
years = [2005, 2009, 2013]
# initialize figure
fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(10, 10), dpi=300, constrained_layout=True)
for i, year in enumerate(years):
# subset lines
lines_plot = lines[lines['year'] == year]
# subset controls plot
controls_plot = controls[controls['year'] == year]
# draw subfig
controls_plot.plot(column='pop_dens', ax=ax[i], legend=True, legend_kwds={'orientation': "horizontal"})
lines_plot.plot(ax=ax[i], color='red', lw=2, zorder=2)
Regarding the first of your questions 'How do I use a single legend for multiple geopandas plots?' you could make sure your plots all use the same colors (using the vmin and vmax args of the .plot() function) and then add a single colorbar to the figure like shown below. for the red lines you can just add another legend (the first thing is technically a colorbar not a legend).
import geopandas as gpd
from matplotlib import pyplot as plt
import matplotlib.cm as cm
import matplotlib.colors as mcolors
from matplotlib.lines import Line2D
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
f, ax = plt.subplots(nrows=1, ncols=3, figsize=(9, 4))
# define min and max values and colormap for the plots
value_min = 0
value_max = 1e7
cmap = 'viridis'
world.plot(ax=ax[0], column='pop_est', vmin=value_min, vmax=value_max, cmap=cmap)
world.plot(ax=ax[1], column='pop_est', vmin=value_min, vmax=value_max, cmap=cmap)
world.plot(ax=ax[2], column='pop_est', vmin=value_min, vmax=value_max, cmap=cmap)
# define a mappable based on which the colorbar will be drawn
mappable = cm.ScalarMappable(
norm=mcolors.Normalize(value_min, value_max),
cmap=cmap
)
# define position and extent of colorbar
cb_ax = f.add_axes([0.1, 0.1, 0.8, 0.05])
# draw colorbar
cbar = f.colorbar(mappable, cax=cb_ax, orientation='horizontal')
# add handles for the legend
custom_lines = [
Line2D([0], [0], color='r'),
Line2D([0], [0], color='b'),
]
# define labels for the legend
custom_labels = ['red line', 'blue line']
# plot legend, loc defines the location
plt.legend(
handles=custom_lines,
labels=custom_labels,
loc=(.4, 1.5),
title='2nd legend',
ncol=2
)
plt.tight_layout()
plt.show()

How to add multiple legends in Seaborn Jointplot?

How can we add multiple legends in Seaborn jointplot? I`m creating a hexbin jointplot with seaborn, and I want to have two legends, scaling in vertical direction instead of horizontal.
First, how can I have both legends as separate and legend for dates scaling vertically instead of horizontally.
I had a look at these links.
Correctly add a legend to a seaborn jointplot
and
Getting legend in seaborn jointplot
And would it also be possible to have the same background for Hexbins instead of white, which doesn`t match with Seaborn style? And can I also make grid square, instead of rectangular?
sns.set()
g = sns.jointplot(x=x,y=y, kind="hex",color="#4CB391", height=15)
ax = g.ax_joint
sns.regplot(x = new_x,y= new_y, ax=ax, scatter=False)
sns.regplot(x = y_true['fatigue_dem_1'],y= y_true['fatigue_dem_1'], color='red',ax=ax, scatter=False)
g.set_axis_labels('Estimated', 'Predicted', fontsize=20)
g.fig.suptitle("Fatigue Dem 1", fontsize=20)
# adjustment for colorbar
plt.subplots_adjust(left=0.2, right=0.8, top=0.95, bottom=0.2)
# make new ax object for the cbar
cbar_ax = g.fig.add_axes([.85, .2, .05, .6]) # x, y, width, height
plt.colorbar(cax=cbar_ax)
metric_legend, = g.ax_joint.plot([], [], linestyle="", alpha=0.5)
data_legend, = g.ax_joint.plot([], [], linestyle="", alpha=0.5)
g.ax_joint.legend([metric_legend],['rrmse={:f}, r2_score={:f}'.format(rrmse,r2)], fontsize=14)
g.ax_joint.legend([data_legend],['Start Date: {0}, End Date: {1}, purpose: {2}'.format(start_date, end_date, 'Test')], fontsize=14)

Customize Seaborn Pair Grid

I'm trying to use Seaborn Pair Grid to make a correlogram with scatterplots in one half, histograms on the diagonal and the pearson coefficient on the other half. I've managed to put together the following code which does what I need, but I'm really struggling with further customization
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import pearsonr
df = sns.load_dataset('iris')
def reg_coef(x,y,label=None,color=None,**kwargs):
ax = plt.gca()
r,p = pearsonr(x,y)
ax.annotate('{:.2f}'.format(r), xy=(0.5,0.5), xycoords='axes fraction', ha='center',fontsize=30,
bbox={'facecolor': 'red', 'alpha': 0.5, 'pad': 20})
ax.set_axis_off()
sns.set(font_scale=1.5)
sns.set_style("white")
g = sns.PairGrid(df)
g.map_diag(plt.hist)
g.map_lower(plt.scatter)
g.map_upper(reg_coef)
g.fig.subplots_adjust(top=0.9)
g.fig.suptitle('Iris Correlogram', fontsize=30)
plt.show()
This is the result
What I'd like to do:
Change the font used for the whole plot and assign my own defined rgb colour to the font and axes (same one)
Remove the X & Y tick labels
Change the colour of the scatter dots and histogram bars to my own defined rgb colour (same one)
Set a diverging colour map for the background of the pearson number to highlight the degree and type of correlation, again using my own defined rgb colours.
I know Im asking a lot but Ive spent hours going round in circles trying to figure this out!!
The color can be set as extra parameter in g.map_diag(plt.hist, color=...) and
g.map_lower(plt.scatter, color=...). The function reg_coef can be modified to take a colormap into account.
The font color and family can be set via the rcParams. The ticks can be removed via plt.setp(g.axes, xticks=[], yticks=[]). Instead of subplot_adjust, g.fig.tight_layout() usually fits all elements nicely into the plot. Here is an example:
import seaborn as sns
import matplotlib.pyplot as plt
from scipy.stats import pearsonr
def reg_coef(x, y, label=None, color=None, cmap=None, **kwargs):
ax = plt.gca()
r, p = pearsonr(x, y)
norm = plt.Normalize(-1, 1)
cmap = cmap if not cmap is None else plt.cm.coolwarm
ax.annotate(f'{r:.2f}', xy=(0.5, 0.5), xycoords='axes fraction', ha='center', fontsize=30,
bbox={'facecolor': cmap(norm(r)), 'alpha': 0.5, 'pad': 20})
ax.set_axis_off()
df = sns.load_dataset('iris')
sns.set(font_scale=1.5)
sns.set_style("white")
for param in ['text.color', 'axes.labelcolor', 'xtick.color', 'ytick.color']:
plt.rcParams[param] = 'cornflowerblue'
plt.rcParams['font.family'] = 'cursive'
g = sns.PairGrid(df, height=2)
g.map_diag(plt.hist, color='turquoise')
g.map_lower(plt.scatter, color='fuchsia')
g.map_upper(reg_coef, cmap=plt.get_cmap('PiYG'))
plt.setp(g.axes, xticks=[], yticks=[])
g.fig.suptitle('Iris Correlogram', fontsize=30)
g.fig.tight_layout()
plt.show()

Make legend correspond to colors of scatter points in matplotlib

I have a plot that I am generating through KMeans algorithm in scikit-learn. The clusters correspond to different colors. Here is the plot,
I need a legend for this plot which corresponds to the cluster number in the plot. Ideally, the legend should display the color of the cluster and the label should be the cluster number. Thanks.
EDIT: I think I should put some code since people are downvoting this
from sklearn.cluster import KMeans
km = KMeans(n_clusters=20, init='random')
km.fit(df) #df is the dataframe which contains points as coordinates
labels = km.labels_
plt.clf()
fig = plt.figure()
ax = fig.add_subplot(111, axisbg='w', frame_on=True)
fig.set_size_inches(18.5, 10.5)
# Plot the clusters on the map
# m is a basemap object
m.scatter(
[geom.x for geom in map_points],
[geom.y for geom in map_points],
20, marker='o', lw=.25,
c = labels.astype(float),
alpha =0.9, antialiased=True,
zorder=3)
m.fillcontinents(color='#555555')
plt.show()
I was able to make the legend correspond to the color. The key was using multiple scatterplots for each category in the data as mentioned by Rutger Kassies.
Here is the code:
import numpy as np
import matplotlib.pyplot as plt
# Setting various plot properties
plt.clf()
fig = plt.figure()
ax = fig.add_subplot(111, axisbg='w', frame_on=True)
fig.set_size_inches(18.5, 10.5)
# Creating a discrete colorbar
colors = plt.cm.rainbow(np.linspace(0, 1, 20))
current_plot_range = 0
previous_plot_range = 0
for i,c in enumerate(colors):
previous_plot_range += current_plot_range
current_plot_range = labels[labels==i].size
m.scatter(
[geom.x for geom in map_points[
previous_plot_range:previous_plot_range+current_plot_range]],
[geom.y for geom in map_points[
previous_plot_range:previous_plot_range+current_plot_range]],
20, lw=.25, marker='o',color = c, label=i, alpha =0.9, antialiased=True,
zorder=3)
plt.legend()
m.fillcontinents(color='#555555')
The result looks something like this:

Categories