annotations in annotated_heatmap plotly lost when adding a subplot - data-science

I'm trying to subplot annotated_heatmap instance with go table , when Im subploting both the annotations on the heatmap are gone and I can't reproduce it .
[I want my heatmap will be as this on ][1]
[but this is what I get][2]
[1]: https://i.stack.imgur.com/V8aEc.png
[2]: https://i.stack.imgur.com/wP9cp.png
here is my code-
#here I create the subplots
fig = make_subplots(
rows=2, cols=1,
shared_xaxes=False,
vertical_spacing=0.05,
specs=[[{"type": "table"}],
[{"type": "scatter"}]],
subplot_titles=("AA table","AA heatmap")
)
#here i add go table
fig.add_trace(fig1.data[0],
row=1, col=1)
#here i add the heatmap
fig.add_trace(fig2.data[0],row=2,col=1)
fig.update_yaxes(title_text="Wild Type amino acid", row=2, col=1)
fig.update_xaxes(title_text="New amino acid", row=2, col=1)
fig.update_layout(
height=800,
showlegend=False,
title_text="Amino acids changes",
)
fig.show()

you have not provided sample data or how fig1 and fig2 are created. So have used https://plotly.com/python/annotated-heatmap/
as stated annotated_heatmap() is deprecated and it's now recommended to use px.imshow()
using this approach it works (have not focused on table rows/cols being correct)
import plotly.express as px
from plotly.subplots import make_subplots
import plotly.graph_objects as go
z = [
[0.1, 0.3, 0.5, 0.7, 0.9],
[1, 0.8, 0.6, 0.4, 0.2],
[0.2, 0, 0.5, 0.7, 0.9],
[0.9, 0.8, 0.4, 0.2, 0],
[0.3, 0.4, 0.5, 0.7, 1],
]
fig1 = go.Figure(go.Table(cells={"values": z}))
# annotated heatmap
fig2 = px.imshow(z, text_auto=True)
fig = make_subplots(
rows=2,
cols=1,
shared_xaxes=False,
vertical_spacing=0.05,
specs=[[{"type": "table"}], [{"type": "scatter"}]],
subplot_titles=("AA table", "AA heatmap"),
)
# here i add go table
fig.add_trace(fig1.data[0], row=1, col=1)
# here i add the heatmap
fig.add_trace(fig2.data[0], row=2, col=1)
fig.update_yaxes(title_text="Wild Type amino acid", row=2, col=1)
fig.update_xaxes(title_text="New amino acid", row=2, col=1)
fig.update_layout(
height=800,
showlegend=False,
title_text="Amino acids changes",
)
fig

Related

Errorbars are over scatter plot points matplotlib

I have some issues using plt.errorbar and plt.scatter. I want to create a plot with points with their own error bars where both of them follow a certain color scale.
The problem is that to create this I have to use plt.errorbar and plt.scatter at the same time to report the points in the same color scale, but doing this the errorbar are above my points and overlap onto other points and I don't want this. Someone can help?
I add an image of what is happening at the moment and the code.
import matplotlib.pyplot as plt
import IPython
from astropy.cosmology import FlatLambdaCDM
import numpy as np
import math as ma
import matplotlib
import matplotlib.cm as cm
from matplotlib.colors import Normalize
cosmo = FlatLambdaCDM(H0=70, Om0=0.3)
Fu20_beam_814=np.array([0.91, 0.65, 0.58, 0.47, 0.64, 0.59, 0.77, 0.57, 0.59, 1.63])
err_Fu20_beam_814=np.array([0.13, 0.20, 0.14, 0.32, 0.17, 0.17, 0.16, 0.24, 0.15, 0.35])
Gav_beam_814=np.array([0.45, 0.41, 0.44, 0.55, 0.45, 0.47, 0.50, 0.56, 0.44, 0.45])
Z_beam_814=np.array([ 4.4105, 5.6700, 4.4381, 5.6704, 4.5134, 5.5448, 5.1818, 5.5420, 4.5785, 4.5802 ])
fake_814=np.array([0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2])*(-1/2)
M_beam_814=np.array([ 9.7, 9.8, 9.9, 10.2, 9.6, 10.2, 9.4, 10.2, 10.0, 9.7])
def f(Re,z):
return Re/(cosmo.arcsec_per_kpc_proper(z).value)
ReG_beam_814 = f(Gav_beam_814, Z_beam_814)
col='jet'
size=150
cap=1
norm = matplotlib.colors.Normalize(vmin=min(M_beam_814), vmax=max(M_beam_814), clip=True)
mapper = cm.ScalarMappable(norm=norm, cmap='jet')
M_color = np.array([(mapper.to_rgba(v)) for v in M_beam_814])
for x, y, ye, xe, color in zip(Fu20_beam_814, ReG_beam_814, fake_814, err_Fu20_beam_814, M_color):
plt.errorbar(x, y, ye, xe, c=color)
Beam_814 = plt.scatter(Fu20_beam_814, ReG_beam_814, s=size, marker='s', c=M_beam_814, cmap=col, edgecolors='#000000', linewidths=0.7)
plt.colorbar()
[1]: https://i.stack.imgur.com/EmkNT.png

create legend for markercolor and size

I've created the following figure:
With following code:
matplotlib.rcParams.update({'font.size': 10})
fig = plt.figure(figsize=(16, 9), dpi=300, facecolor='white')
ax = plt.subplot(111, projection=ccrs.PlateCarree())
ax.set_extent(extent)
# cartopy layers
country_10m = cartopy.feature.NaturalEarthFeature('cultural', 'admin_0_countries', '10m')
ax.add_feature(country_10m, edgecolor='w', linewidth=0.75, facecolor='#EEEFEE', label='country border')
ax.coastlines(resolution='10m', color='#EEEFEE', linewidth=0.75)
ax.imshow(np.tile(np.array([[[191, 210, 217]]], dtype=np.uint8), [2, 2, 1]), origin='lower', transform=cartopy.crs.PlateCarree(), extent=extent)
ax.scatter(gdf_ldb.x, gdf_ldb.y, c= gdf_ldb.Color, s= gdf_ldb.Markersize, zorder=30)
# ax.scatter(gdf_ports_filt.longitude, gdf_ports_filt.latitude, s= 10, color= 'k', zorder= 30)
ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=1, color='gray', alpha=0.5, linestyle='-')
ax.text(-0.08, 0.5, 'latitude [°]', va='bottom', ha='center',rotation='vertical', rotation_mode='anchor',transform=ax.transAxes);
ax.text(0.5, -0.09, 'longitude [°]', va='bottom', ha='center', rotation='horizontal', rotation_mode='anchor', transform=ax.transAxes);
How do I create a legend for the markersize as well for the color, so like this:
With x, x1, and x2 representing the values of the markersizes.
gdf_ldb looks like:
x y Type Color Markersize geometry
prograding_feature_polygon_29 12.857701 56.648035 Updrift grey 3.0 POINT (12.85770 56.64804)
prograding_feature_polygon_57 17.781445 54.808079 Updrift grey 3.0 POINT (17.78144 54.80808)
prograding_feature_polygon_58 17.438390 54.754518 Updrift grey 3.0 POINT (17.43839 54.75452)
prograding_feature_polygon_63 4.708077 52.880322 Updrift grey 3.0 POINT (4.70808 52.88032)
prograding_feature_polygon_72 3.953364 51.842299 Updrift grey 3.0 POINT (3.95336 51.84230)
... ... ... ... ... ... ...
retreating_feature_polygon_2018 -10.148432 53.415224 Double Updrift grey 3.0 POINT (-10.14843 53.41522)
retreating_feature_polygon_2019 -9.954510 54.197329 Double Updrift grey 3.0 POINT (-9.95451 54.19733)
retreating_feature_polygon_2119 15.095564 37.389535 Double Updrift grey 3.0 POINT (15.09556 37.38953)
retreating_feature_polygon_2120 14.317893 37.025026 Double Updrift grey 3.0 POINT (14.31789 37.02503)
retreating_feature_polygon_2121 13.952111 37.101009 Updrift grey 3.0 POINT (13.95211 37.10101)
Thanks in advance,
Dante
The key is to capture the artist (PathCollection in this case) returned by the scatter command. That has a method to retrieve the legend items manually, and it has keywords to distinguish between size and color (default). The num keyword can be used to reduce the amount of items returned, which is useful in the case of a (semi)continuous property as the size can be.
The example below plots two separate legends for both properties. You can also combine the handles and labels of both and plot them in a single legend if needed.
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import cartopy.crs as ccrs
import cartopy
import numpy as np
lons = np.random.randint(-170, 170, 100)
lats = np.random.randint(-80, 80, 100)
sizes = np.random.rand(100) * 100 + 5
colors = np.random.randint(0, 3, 100)
fig, ax = plt.subplots(
figsize=(8,4), dpi=86, facecolor='w',
subplot_kw=dict(projection=ccrs.PlateCarree()),
)
fig.subplots_adjust(left=0, right=1, bottom=0, top=1)
ax.add_feature(cartopy.feature.LAND, ec='none', fc='#EEEFEE', label='country border')
ax.add_feature(cartopy.feature.BORDERS, ec='w', fc='k', lw=0.75, label='country border')
ax.coastlines(resolution='10m', color='#EEEFEE', lw=0.75)
m = ax.scatter(lons, lats, s=sizes, c=colors, zorder=5, label="points")
l1 = ax.legend(
*m.legend_elements(prop="colors", num="auto"), title="Colors", framealpha=1,
loc="upper right", bbox_to_anchor=(0.88, 0.8, 0.12, 0.2), mode="expand",
)
ax.add_artist(l1) # prevent overwriting with second legend
l2 = ax.legend(
*m.legend_elements(prop="sizes", num=5), title="Sizes", framealpha=1,
loc="upper right", bbox_to_anchor=(0.88, 0.55, 0.12, 0.2), mode="expand",
)
The documentation about this shows some variations on this:
https://matplotlib.org/stable/gallery/lines_bars_and_markers/scatter_with_legend.html#automated-legend-creation
The answer by Rutger Kassies is excellent for many use cases. However, he mentions that One can also combine the handles and labels of both and plot them in a single legend if needed.
Here I offer another answer that shows the steps to create the single legend manually. Inside the single legend, 2 groups of sub legends are created and arranged as needed.
With single legend, you don't need to find the values of bbox_to_anchor for the second (or third and so on) to position them properly.
With manual creation of items into a single legend, you have full control of the items' you need in the legend. However, it need some extra coding to achieve the goal.
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
import cartopy.crs as ccrs
import cartopy.feature as cfeature
# For `Categories` symbol
# Each item of legends requires 3 properties: color/text/marker_shape
color_V = ["green", "orange", "purple", "red", "cyan", "magenta"]
text_V = ["cat_4", "cat_9", "cat_13", "cat_15", "cat_19", "cat_33"]
marker_V = ["o", "o", "o", "o", "o", "o"]
len_V = len(color_V)
# For `Size/values` symbol
color_S = ["gray", "gray", "gray", "gray"]
sizes_S = [4, 8, 12, 16] #increasing values ...
text_S = ["4", "8", "12", "16"] #cover `sizes1` below
marker_S = ["o", "o", "o", "o"] #use disk shape
len_S = len(color_S)
# Demo data locations and attributes
xs = [23,12,4,25,24,52,17,33]
ys = [41,12,32,15,35,21,23,43]
colors1 = ["green", "orange", "purple", "red", "cyan", "magenta", "green", "orange"]
#texts1 = ["4", "9", "13", "15", "19", "33", "4", "9"]
markers1 = ["o", "o", "o", "o", "o", "o", "o", "o"]
sizes1 = [10,16,9,12,7,4,2,6]
len1 = len(xs)
all_patches = [] #for items in a single legend
# Create figure and `ax` for map plotting
# This form can create a single axes or an array of axes
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8,6), subplot_kw={'projection': ccrs.PlateCarree()})
# All steps of plots will be done on `ax`
# [1] Add an invisible object as a spacer in the legend box
#rect = mpatches.Rectangle([0, 0], 0.01, 0.01, ec="none", color="lightgray")
all_patches.append(mlines.Line2D([0, 0], [1, 0], color="none"))
# Explicitly defining the elements in the legend
# [2] Add proxied text: 'Categories' to the legend
line = mlines.Line2D([0, 0], [1, 0], lw=.5, alpha=0.9, color="none")
line.set_label('Categories') # Title for 1st group of symbols in the legend
all_patches.append(line)
# [3] Plot (on the axes) `none` data point and
# save the output patches for `Categories` group
patches_V = [ ax.plot([],[], marker=marker_V[i], ms=8, ls="", color=color_V[i], \
label="{:s}".format(text_V[i]) )[0] \
for i in range(len_V) ]
all_patches += patches_V
# [4] Add an invisible object as a spacer in the legend box
all_patches.append(mlines.Line2D([0, 0], [1, 0], color="none"))
# [5] Add proxied text: 'Sizes' to the legend
x, y = ([0, 1], [0, 0])
line = mlines.Line2D([0, 0], [1, 0], lw=.5, alpha=0.9, color="none")
line.set_label('Sizes') # Title for 2nd group of symbols in the legend
all_patches.append(line)
# [6] Create patches for `Sizes` group
patches_S = [ ax.plot([],[], marker=marker_S[i], ms=sizes_S[i], ls="", \
color=color_S[i], \
label="{:s}".format(text_S[i]) )[0] for i in range(len_S) ]
all_patches += patches_S
# Plot point data using the demo data
for i in range(len1):
ax.plot(xs[i], ys[i], marker=markers1[i], ms=sizes1[i], color=colors1[i])
ax.set_extent([0, 80, 0, 60])
# Plot the legend in the upper-right corner
combined_legend = ax.legend(handles=all_patches,
bbox_to_anchor=(1, 1),
title="The Legend",
loc='upper right',
ncol=1,
numpoints=1,
facecolor="lightgray",
fontsize = 10,
title_fontsize= 12,
labelspacing = 0.55,
shadow=True)
# Draw some basemap features
ax.coastlines(lw=0.3, color="k")
ax.add_feature(cfeature.LAND)
ax.add_feature(cfeature.OCEAN)
plt.title("Legend for Categories and Sizes")
plt.show()
The output map:

Plot circle at the title in matplotlib python

I have a 2 line title and first line has a number at the end of the line.
Can we plot a circle around the number?
Here is the code to generate the figure.
from matplotlib import rcParams
from matplotlib import pyplot as plt
import numpy as np
import os
rcParams.update({'figure.autolayout': True})
some_text = 'XXX'
any_number=15
title = '%s: %d\n YYY ZZZZ WWWWW' % (some_text,any_number)
fig = plt.figure(figsize=(8, 8), dpi=100)
plt.tick_params(axis='y', which='major', labelsize=60, width=3, length=10, pad=40)
plt.tick_params(axis='y', which='minor', labelsize=60, width=3, length=10, pad=40)
ax = plt.gca()
plt.title(title, fontsize=60, pad=40, loc='center', fontweight='semibold')
plt.style.use('ggplot')
ax.set_facecolor('white')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.spines['left'].set_visible(True)
for edge_i in ['left']:
ax.spines[edge_i].set_edgecolor("black")
ax.spines[edge_i].set_linewidth(3)
ax.spines[edge_i].set_bounds(0, 1)
x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
plt.yticks(np.arange(0, 1.01, step=0.2))
data_list= np.array([1,1,1,1,1,0.9, 0.8, 0.7, 0.8,0.85])
plt.bar(x, data_list, 0.9, color='indianred',edgecolor="black", linewidth=3,zorder=1)
plt.tick_params(
axis='x', # changes apply to the x-axis
which='both', # both major and minor ticks are affected
bottom=False, # ticks along the bottom edge are off
top=False, # ticks along the top edge are off
labelbottom=False) # labels along the bottom edge are off
figure_name = 'figure_with_circle.png'
figure_file = os.path.join('/Users/burcakotlu/Desktop',figure_name)
fig.savefig(figure_file, dpi=100, bbox_inches="tight")
plt.close(fig)
Here is the current figure and the wanted circle.
One could use the following without ax.bar():
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.set_title('title')
circle1 = plt.Circle((2,4.15), 0.2, color='k', clip_on=False, zorder=100, fill=False)
ax.add_patch(circle1)
ax.set_xlim(0,4)
ax.set_ylim(0,4)
plt.show()
I have found a way to plot circle together with bar plots without distorting bars. Here is the code below:
from matplotlib import rcParams
from matplotlib import pyplot as plt
import numpy as np
import os
import matplotlib.patches as patches
from matplotlib.offsetbox import AnchoredText
rcParams.update({'figure.autolayout': True})
some_text = 'XXX'
any_number=15
title = '%s: %d\n YYY ZZZZ WWWWW' % (some_text,any_number)
fig = plt.figure(figsize=(12,12), dpi=100)
plt.tick_params(axis='y', which='major', labelsize=60, width=3, length=10, pad=40)
plt.tick_params(axis='y', which='minor', labelsize=60, width=3, length=10, pad=40)
ax = plt.gca()
number_of_xxx = '12'
anchored_text_number_of_xxx = AnchoredText(number_of_xxx,
frameon=False, borderpad=0, pad=0.1,
loc='upper right',
bbox_to_anchor=[0.95, 1.3],
bbox_transform=plt.gca().transAxes,
prop={'fontsize': 60,
'fontweight': 'semibold'})
ax.add_artist(anchored_text_number_of_xxx)
circle1 = patches.Circle((0.88, 1.25), radius=0.1, transform=ax.transAxes, zorder=100, fill=False, color='gold', lw=8, clip_on=False)
ax.add_patch(circle1)
ax.set_title(title, fontsize=60, pad=40, loc='center', fontweight='semibold', zorder=50)
ax.set_facecolor('white')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.spines['left'].set_visible(True)
for edge_i in ['left']:
ax.spines[edge_i].set_edgecolor("black")
ax.spines[edge_i].set_linewidth(3)
ax.spines[edge_i].set_bounds(0, 1)
x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
ax.set_yticks(np.arange(0, 1.01, step=0.2))
data_list= np.array([1,1,1,1,1,0.9, 0.8, 0.7, 0.8,0.85])
ax.bar(x, data_list, 0.9, color='indianred',edgecolor="black", linewidth=3,zorder=1)
ax.tick_params(
axis='x', # changes apply to the x-axis
which='both', # both major and minor ticks are affected
bottom=False, # ticks along the bottom edge are off
top=False, # ticks along the top edge are off
labelbottom=False) # labels along the bottom edge are off
figure_name = 'figure_with_circle.png'
figure_file = os.path.join('/Users/burcakotlu/Desktop',figure_name)
fig.savefig(figure_file, dpi=100, bbox_inches="tight")
plt.close(fig)

Align bar and line plot on x axis without the use of rank and pointplot

Please note, I've looked at other questions like question and my problem is different and not a duplicate!
I would like to have two plots, with the same x axis in matplotlib. I thought this should be achieved via constrained_layout, but apparently this is not the case. Here is an example code.
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.gridspec as grd
x = np.arange(0, 30, 0.001)
df_line = pd.DataFrame({"x": x, "y": np.sin(x)})
df_bar = pd.DataFrame({
"x_bar": [1, 7, 10, 20, 30],
"y_bar": [0.0, 0.3, 0.4, 0.1, 0.2]
})
fig = plt.subplots(constrained_layout=True)
gs = grd.GridSpec(2, 1, height_ratios=[3, 2], wspace=0.1)
ax1 = plt.subplot(gs[0])
sns.lineplot(data=df_line, x=df_line["x"], y=df_line["y"], ax=ax1)
ax1.set_xlabel("time", fontsize="22")
ax1.set_ylabel("y values", fontsize="22")
plt.yticks(fontsize=16)
plt.xticks(fontsize=16)
plt.setp(ax1.get_legend().get_texts(), fontsize="22")
ax2 = plt.subplot(gs[1])
sns.barplot(data=df_bar, x="x_bar", y="y_bar", ax=ax2)
ax2.set_xlabel("time", fontsize="22")
ax2.set_ylabel("y values", fontsize="22")
plt.yticks(fontsize=16)
plt.xticks(fontsize=16)
this leads to the following figure.
However, I would like to see the corresponding x values of both plot aligned. How can I achieve this? Note, I've tried to use the following related question. However, this doesn't fully apply to my situation. First with the high number of x points (which I need in reality) point plots is make the picture to big and slow for loading. On top, I can't use the rank method as my categories for the barplot are not evenly distributed. They are specific points on the x axis which should be aligned with the corresponding point on the lineplot
x = np.arange(0, 30, 0.001)
df_line = pd.DataFrame({"x": x, "y": np.sin(x)})
df_bar = pd.DataFrame({
"x_bar": [1, 7, 10, 20, 30],
"y_bar": [0.0, 0.3, 0.4, 0.1, 0.2]
})
fig, (ax1, ax2) = plt.subplots(2,1)
ax1.plot(df_line['x'], df_line['y'])
for i in range(len(df_bar['x_bar'])):
ax2.axvline(x=df_bar['x_bar'][i], ymin=0, ymax=df_bar['y_bar'][i])
Output:
---edit---
I incorporated #mozway advice for linewidth:
lw = (300/ax1.get_xlim()[1])
ax2.axvline(x=df_bar['x_bar'][i], ymin=0, ymax=df_bar['y_bar'][i], solid_capstyle='butt', lw=lw)
Output:
or:

Matplotlib subplots how to align colorbars with other legends, or how to justify subplots to left

How can I add a colorbar scale to the 2nd & 3rd subplots, such that it is inline with my legends in the 1st and 4th subplots? Or, another way to say the question: how can I add a colorbar scale without changing the alignment/justification of the 2nd & 3rd subplots?
There are good examples available on setting colorbar locations (e.g., here on stackoverflow and in the matplotlib docs), but I still haven't been able to solve this.
Below is a reproducible example. The real data are more complicated, and this is part of a loop to produce many figures, so the "extra" stuff about setting axis limits and subplot aspect ratios is needed and will change with different datasets.
Using Python 3.8.
Reproducible example without colorbar
## Specify axes limits, tick intervals, and aspect ratio
xl, yl, xytick, ar = [-40000,120000], [-30000,10000], 20000, 0.8
## Global plot layout stuff
fig = plt.figure(figsize=(10, 7.5), constrained_layout=True)
gs = fig.add_gridspec(4, 1)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[1, 0], sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[2, 0], sharex = ax1)
ax4 = fig.add_subplot(gs[3, 0], sharex = ax1, sharey = ax3)
fig.execute_constrained_layout()
fig.suptitle('Suptitle')
## First Plot
ax1.plot([-30000, 500], [-2000, -21000], c='red', label='A')
ax1.plot([80000, 110000], [-9000, 800], c='blue', label='B')
ax1.set_title('ax1', style='italic');
ax1.set_xlabel('x');
ax1.set_ylabel('beta');
ax1.set_xlim(xl)
ax1.set_ylim(yl)
ax1.xaxis.set_major_locator(ticker.MultipleLocator(xytick))
ax1.yaxis.set_major_locator(ticker.MultipleLocator(xytick))
ax1.legend(handles=leg, bbox_to_anchor=(1.05, 1), loc='upper left')
ax1.set_aspect(aspect=ar)
## Dummy data for plots 2/3/4
x = [-15000, -2000, 0, 5000, 6000, 11000, 18000, 21000, 25000, 36000, 62000]
beta = [1000, 200, -800, 100, 1000, -2000, -5000, -5000, -15000, -21000, -1500]
y = [0.01, 0.2, 1.3, 0.35, 0.88, 2.2, 2.5, 1.25, 3.4, 4.1, 2.1]
## Second Plot
vals = ax2.scatter(x, beta, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax2.set_title('ax2', style='italic');
ax2.set_xlabel('x');
ax2.set_ylabel('beta');
ax2.set_aspect(aspect=ar)
## Attempt to add colorbar
#cbar = fig.colorbar(vals, ax=ax2, format = '%1.2g', location='right', aspect=25)
#cbar.ax.set_ylabel('y')
#cbar.ax.yaxis.set_label_position('left')
#cbar_range = [min(y), max(y)]
#ticklabels = cbar.ax.get_ymajorticklabels()
#cbarticks = list(cbar.get_ticks())
#cbar.set_ticks(cbar_range + cbarticks)
## Third Plot
ax3.scatter(x, y, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax3.set_title('ax3', style='italic');
ax3.set_xlabel('x');
ax3.set_ylabel('y');
ax3.yaxis.set_major_formatter(FormatStrFormatter('%1.2g'))
## Fourth Plot
ax4.scatter(x, y, c='black', label='Dots')
ax4.set_title('ax4', style='italic');
ax4.set_xlabel('x');
ax4.set_ylabel('y');
ax4.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
## Clean-up, set aspect ratios
figW, figH = ax1.get_figure().get_size_inches()
_, _, w, h = ax1.get_position().bounds
disp_ratio = (figH * h) / (figW * w)
data_ratio = sub(*ax3.get_ylim()) / sub(*ax3.get_xlim())
ax3.set_aspect(aspect=disp_ratio / data_ratio )
ax4.set_aspect(aspect=disp_ratio / data_ratio)
## Clean-up, turn axis ticks back on after messing with cbar
#ax1.tick_params(axis='both', which='both', labelbottom='on')
#ax2.tick_params(axis='both', which='both', labelbottom='on')
#ax3.tick_params(axis='both', which='both', labelbottom='on')
Result when trying colorbar, note misalignment of second plot
Suggest you simplify your code and make sure it all works; for instance I have no idea what sub does.
A partial solution to your problem could be panchor=False, which is a bit of an obscure kwarg, but...
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
## Specify axes limits, tick intervals, and aspect ratio
ar = 1.2
## Global plot layout stuff
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 4), constrained_layout=True, sharex=True, sharey=True)
## First Plot
ax1.plot([-20_000, 20_000], [-20_000, 20_000] )
ax1.set_aspect(aspect=ar)
## Dummy data for plots 2/3/4
x = [-15000, -2000, 0, 5000, 6000, 11000, 18000, 21000, 25000, 36000, 62000]
beta = [1000, 200, -800, 100, 1000, -2000, -5000, -5000, -15000, -21000, -1500]
y = [0.01, 0.2, 1.3, 0.35, 0.88, 2.2, 2.5, 1.25, 3.4, 4.1, 2.1]
## Second Plot
vals = ax2.scatter(x, beta, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax2.set_aspect(aspect=ar)
cbar = fig.colorbar(vals, ax=ax2, format = '%1.2g', location='right',
aspect=25, panchor=False)
plt.show()
Depending on the size of the figure, this could comically place the colorbar far to the right. The problem here is the aspect ratio of your plots, which makes the actual axes more narrow than the figure. But the colorbar doesn't really know about that, and places itself on the outside of the space allocated for the axes.
If this is displeasing, then you can also specify an inset axes for the colorbar.
cbax = ax2.inset_axes([1.05, 0.2, 0.05, 0.6], transform=ax2.transAxes)
cbar = fig.colorbar(vals, cax=cbax, format = '%1.2g', orientation='vertical')
Using inset_axes() solves this, as suggested in the other answer, but the parameters relative to the transform were not explained in the example, but I was able to figure it out with some research.
The parameters in inset_axes are [x-corner, y-corner, width, height] and the transform is like a local reference. So, using [1,0,0.5,0.75] means: x = 100% or end of parent ax; y = 0% or bottom of parent ax; width = 50% of parent ax; and height = 75% of parent ax.
Here I wanted the colorbar to be the same height as the parent ax (ax2 and ax3), very thin, and offset a little bit to be more in line with the other legends. Using cbax = ax2.inset_axes([1.1, 0, 0.03, 1], transform=ax2.transAxes) achieves this.
This code works for any aspect ratio ar.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.colors as mcolors
from operator import sub
%matplotlib inline
plt.style.use('seaborn-whitegrid')
## Specify axes limits, tick intervals, and aspect ratio
xl, yl, ar = [-40000,120000], [-30000,10000], .5
## Global plot layout stuff
fig = plt.figure(figsize=(10, 7.5), constrained_layout=True)
gs = fig.add_gridspec(4, 1)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[1, 0], sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[2, 0], sharex = ax1)
ax4 = fig.add_subplot(gs[3, 0], sharex = ax1, sharey = ax3)
fig.execute_constrained_layout()
fig.suptitle('Suptitle')
## First Plot
ax1.plot([-30000, 500], [-2000, -21000], c='red', label='A')
ax1.plot([80000, 110000], [-9000, 800], c='blue', label='B')
ax1.set_title('ax1', style='italic');
ax1.set_xlim(xl)
ax1.set_ylim(yl)
ax1.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
ax1.set_aspect(aspect=ar)
## Dummy data for plots 2/3/4
x = [-15000, -2000, 0, 5000, 6000, 11000, 18000, 21000, 25000, 36000, 62000]
beta = [1000, 200, -800, 100, 1000, -2000, -5000, -5000, -15000, -21000, -1500]
y = [0.01, 0.2, 1.3, 0.35, 0.88, 2.2, 2.5, 1.25, 3.4, 4.1, 2.1]
## Second Plot
vals = ax2.scatter(x, beta, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax2.set_title('ax2', style='italic');
ax2.set_aspect(aspect=ar)
cbax = ax2.inset_axes([1.1, 0, 0.03, 1], transform=ax2.transAxes)
cbar2 = fig.colorbar(vals, cax=cbax, format = '%1.2g', orientation='vertical')
## Third Plot
ax3.scatter(x, y, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax3.set_title('ax3', style='italic');
cbax = ax3.inset_axes([1.1, 0, 0.03, 1], transform=ax3.transAxes)
cbar3 = fig.colorbar(vals, cax=cbax, format = '%1.2g', orientation='vertical')
## Fourth Plot
ax4.scatter(x, y, c='black', label='Dots')
ax4.set_title('ax4', style='italic');
ax4.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
## Clean-up, set aspect ratios
figW, figH = ax1.get_figure().get_size_inches()
_, _, w, h = ax1.get_position().bounds
disp_ratio = (figH * h) / (figW * w)
data_ratio = sub(*ax3.get_ylim()) / sub(*ax3.get_xlim())
ax3.set_aspect(aspect=disp_ratio / data_ratio )
ax4.set_aspect(aspect=disp_ratio / data_ratio)
## Colorbars
cbar2.ax.set_ylabel('y')
cbar2.ax.yaxis.set_label_position('left')
cbar3.ax.set_ylabel('y')
cbar3.ax.yaxis.set_label_position('left')
Result with aspect ratio = 0.5 for top 2 plots
Result with aspect ratio = 2 for top 2 plots