Having just one legend when using matplotlib zoomed_inset_axes - python

I'd like to make a fairly straight forward plot with 4 sets of data, two in the main plot, and two in the insert (on a different scale).
This is the starting code::
w1snr_limit = np.arange(0, 50, 0.5)
w2snr_limit = np.arange(0, 50, 0.5)
w3snr_limit = np.arange(0, 10, 0.5)
w4snr_limit = np.arange(0, 10, 0.5)
w1snr_percent = w1snr_limit**(1/2.)
w2snr_percent = w2snr_limit**(1/2.)
w3snr_percent = w3snr_limit**(1/3.)
w4snr_percent = w4snr_limit**(1/4.)
fig, ax = plt.subplots(figsize=(8.0, 8.0))
xmin = 0.00
xmax = 50.00
ymin = 0.00
ymax = 100.00
ax.scatter(w1snr_limit, w1snr_percent, s=ms, alpha=0.85, label='W1 SNR')
ax.scatter(w2snr_limit, w2snr_percent, s=ms, alpha=0.85, label='W2 SNR')
ax.set_xlim((xmin, xmax))
ax.set_ylim((ymin, ymax))
Then there would be this inset plot::
axins = zoomed_inset_axes(ax, 2.0, loc='lower right')
# sub region of the original image
x1, x2, y1, y2 = 0.0, 10., 0.0, 20.0
axins.set_xlim(x1, x2)
axins.set_ylim(y1, y2)
axins.scatter(w3snr_limit, w3snr_percent, s=ms, alpha=0.85, label='W3 SNR', color='green')
axins.scatter(w4snr_limit, w4snr_percent, s=ms, alpha=0.85, label='W4 SNR', color='red')
But then I first struggle to get the inset plot properly placed (i.e. the two x-axes are all munched up) and also it's unclear to me how you get all 4 datasets into the same legend
handles, labels = ax.get_legend_handles_labels()
handles = [handles[0], handles[1], handles[2], handles[3]]
labels = [labels[0], labels[1], labels[2],labels[3]]
ax.legend(handles,labels,loc=2)
leads to a
IndexError: list index out of range
error. Little help just to get these things sorted?

Not sure what "properly placed" means, but since the data is in the lower part of the figure, I'd suggest to use "upper right" as loc.
You need to supply the handles and labels of both the ax and the axins to the legend to have them all in the legend.
Complete example:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
w1snr_limit = w2snr_limit = np.arange(0, 50, 0.5)
w3snr_limit = w4snr_limit = np.arange(0, 10, 0.5)
w1snr_percent = w1snr_limit**(1/2.)
w2snr_percent = w2snr_limit**(1/2.)
w3snr_percent = w3snr_limit**(1/3.)
w4snr_percent = w4snr_limit**(1/4.)
fig, ax = plt.subplots(figsize=(8.0, 8.0))
xmin, xmax = 0., 50.
ymin, ymax = 0., 100.
ax.scatter(w1snr_limit, w1snr_percent, s=6, alpha=0.85, label='W1 SNR')
ax.scatter(w2snr_limit, w2snr_percent, s=6, alpha=0.85, label='W2 SNR')
ax.set_xlim((xmin, xmax))
ax.set_ylim((ymin, ymax))
axins = zoomed_inset_axes(ax, 2.0, loc='upper right')
# sub region of the original image
x1, x2, y1, y2 = 0.0, 10., 0.0, 20.0
axins.set_xlim(x1, x2)
axins.set_ylim(y1, y2)
axins.scatter(w3snr_limit, w3snr_percent, s=6, alpha=0.85, label='W3 SNR', color='green')
axins.scatter(w4snr_limit, w4snr_percent, s=6, alpha=0.85, label='W4 SNR', color='red')
handles, labels = ax.get_legend_handles_labels()
handles1, labels1 = axins.get_legend_handles_labels()
ax.legend(handles+handles1, labels+labels1, loc=2)
plt.show()

Related

Zoom in the pyplot selection with inset_axes

I have a plot consisting of multiple elements and I wish to have a selection enlarged with inset_axes. I have followed the manual and several other posts but it is only creating an empty square.
I have 1500 lines of code where I add elements to that plot at different places, thus I wish to create a zoom at the end to the whole plot.
Here is what is my output:
and here is the code that leads to this plot (data omitted, too large)
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
# this is done in a separate function (called only once)
# variables defined earlier, omitting - some variables are set manually, some are observed
for i_plot_buildings in range(0, len(Building_center_coords_main)):
ax.plot(x_building_corners_main[i_plot_buildings], y_building_corners_main[i_plot_buildings], 'k-')
plt.scatter(UEs_coordinates[:, 1], UEs_coordinates[:, 0], s=50, marker='.', c="c")
plt.scatter(BSs_coordinates[:, 0], BSs_coordinates[:, 1], marker='^', c="r", zorder=3)
ax.set_aspect('equal', adjustable='box')
Plot_last_drop_indicator = 0
arry = np.empty((1000, 1000), int) #this fills with info about wind in later code (omitted)
plt.imshow(arry, cmap=plt.cm.Greens, interpolation='nearest')
plt.ylabel("Y [m]")
plt.xlabel("X [m]")
bar = plt.colorbar()
bar.set_label(r'Wind speed $[ms^{-1}]$', rotation=270)
#and now I want the zoom, not working...
axins = ax.inset_axes([0.55, 0.55, 0.4, 0.4]) # set the area where to enlarge selection
# the selection
x1, x2, y1, y2 = 0,100,0,100
axins.set_xlim(x1, x2)
axins.set_ylim(y1, y2)
axins.set_xticklabels([])
axins.set_yticklabels([])
ax.indicate_inset_zoom(axins, edgecolor="black")
In case anyone would ever try to do some complex stuff like me I keep this question and provide answer I have managed to run. As mentioned by comments, you have to add a brand new plot inside the original plot with new data. Here is the code:
fig, ax = plt.subplots()
# this is done in a separate function (called only once)
# variables defined earlier, omitting - some variables are set manually, some are observed
for i_plot_buildings in range(0, len(Building_center_coords_main)):
ax.plot(x_building_corners_main[i_plot_buildings], y_building_corners_main[i_plot_buildings], 'k-')
plt.scatter(UEs_coordinates[:, 1], UEs_coordinates[:, 0], s=50, marker='.', c="c")
plt.scatter(BSs_coordinates[:, 0], BSs_coordinates[:, 1], marker='^', c="r", zorder=3)
ax.set_aspect('equal', adjustable='box')
Plot_last_drop_indicator = 0
arry = np.empty((1000, 1000), int) #this fills with info about wind in later code (omitted)
plt.imshow(arry, cmap=plt.cm.Greens, interpolation='nearest')
plt.ylabel("Y [m]")
plt.xlabel("X [m]")
bar = plt.colorbar()
bar.set_label(r'Wind speed $[ms^{-1}]$', rotation=270)
# adding circles and crosses
plt.scatter(47, 64, marker='x',c="g")
circle1 = plt.Circle((47, 64), 8*4, color='g', fill=False)
plt.gca().add_patch(circle1)
plt.scatter(41, 56, marker='x',c="r")
circle1 = plt.Circle((41, 56), 6*4, color='r', fill=False)
plt.gca().add_patch(circle1)
plt.scatter(726, 672, marker='x',c="g")
plt.scatter(755, 658, marker='x',c="r")
circle1 = plt.Circle((726, 672), 9*4, color='g' ,fill=False)
plt.gca().add_patch(circle1)
circle1 = plt.Circle((755, 658), 7*4, color='r', fill=False)
plt.gca().add_patch(circle1)
plt.scatter(920, 51, marker='x',c="g")
circle1 = plt.Circle((920, 51), 8*4, color='g', fill=False)
plt.gca().add_patch(circle1)
plt.scatter(927, 41, marker='x',c="r")
circle1 = plt.Circle((927, 41), 5*4, color='r', fill=False)
plt.gca().add_patch(circle1)
#inverting y axis
plt.gca().invert_yaxis()
#selecting parent axis for later use
parent_axes = plt.gca()
axins = ax.inset_axes([0.1, 0.5, 0.4, 0.4]) # enlargement area
# area to zoom
x1, x2, y1, y2 = 680,780,620,720
axins.set_xlim(x1, x2)
axins.set_ylim(y1, y2)
# new ax
ax3 = plt.gcf().add_axes([50,500,400,400])
# adding arry selection to the new plot
ax3.imshow(arry[680:780, 620:720], cmap=plt.cm.Greens, interpolation='nearest')
running through coordinates to select which to add (yes this is slow and can be done more smart but I am lazy)
for coord in UEcoords:
if coord[1] >= 680 and coord [1] < 780:
if coord[0] >= 620 and coord [0] < 720:
ax3.scatter(coord[1]-680, coord[0]-620, s=100, marker='.', c="c")
#show selected crosses and circles in area
ax3.scatter(726-680, 672-620, marker='x',c="g")
ax3.scatter(755-680, 658-620, marker='x',c="r")
circle1 = plt.Circle((726-680, 672-620), 9*4, color='g' ,fill=False)
ax3.add_patch(circle1)
circle1 =plt.Circle((755-680, 658-620), 7*4, color='r', fill=False)
ax3.add_patch(circle1)
#set limits and turn off labels
ax3.set_xlim(0,100)
ax3.set_ylim(0,100)
ax3.set_xticklabels([])
ax3.set_yticklabels([])
#set up inset position
ip = InsetPosition(parent_axes,[0.1, 0.5, 0.4, 0.4])
axins.set_axes_locator(ip)
axins.set_xticklabels([])
axins.set_yticklabels([])
# set the new axes (ax3) to the position of the linked axes
ax3.set_axes_locator(ip)
ax.indicate_inset_zoom(axins, edgecolor="black")
The final output looks like this:

normal distribution curve doesn't fit well over histogram in subplots using matplotlib

I am using "plt.subplots(2, 2, sharex=True, sharey=True)" to draw a 2*2 subplots. Each subplot has two Y axis and contains normal distribution curve over a histogram. Noting I particularly set "sharex=True, sharey=True" here in order to make all subplots share the same X axis and Y axis.
After running my code, everything is fine except the second, three, and fourth subplots where the normal distribution curve doesn't fit the histogram very well (please see the figure here)
I did googling but failed to get this issue solved. However, if I set "sharex=True, sharey=False" in my code, then the figure looks correct, but all subplots use their own Y axix which isn't what I want. Please see the figure here
Hope this issue can be fixed by experts in StackOverflow. Many thanks in advance!
Below is my code:
import matplotlib.pyplot as plt
from scipy.stats import norm
def align_yaxis(ax1, v1, ax2, v2):
#adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
inv = ax2.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, y1-y2))
miny, maxy = ax2.get_ylim()
ax2.set_ylim(miny+dy, maxy+dy)
def drawSingle(myax, mydf , title, offset):
num_bins = 200
xs = mydf["gap"]
x = np.linspace(-1,1,1000)
mu =np.mean(x)
sigma =np.std(xs)
n, bins, patche = myax.hist(xs, num_bins, alpha=0.8, facecolor='blue', density=False)
myax.set_ylabel('frequency',color="black",fontsize=12, weight = "bold")
myax.set_xlabel('X', fontsize=12, weight = "bold",horizontalalignment='center')
ax_twin = myax.twinx()
y_normcurve = norm.pdf(bins, mu, sigma)
ax_twin.plot(bins, y_normcurve, 'r--')
align_yaxis(myax,0,ax_twin,0)
peakpoint = norm.pdf(mu,loc=mu,scale=sigma)
plt.vlines(mu, 0, peakpoint, 'y', '--', label='example')
ax_twin.set_ylabel("probablility dense",color="black",fontsize=12, weight = "bold")
def drawSubplots(mydf1,mydf2,mydf3,mydf4, pos1,pos2,pos3,pos4, title, filename):
plt.rcParams['figure.figsize'] = (18,15 )
my_x_ticks = np.arange(-0.8, 0.8,0.1)
rows, cols = 2, 2
fig, ax = plt.subplots(2, 2, sharex=True, sharey=True)
drawSingle(ax[0][0], mydf1, "Subplot1", pos1)
drawSingle(ax[0][1], mydf2, "Subplot2", pos2)
drawSingle(ax[1][0], mydf3, "Subplot3", pos3)
drawSingle(ax[1][1], mydf4, "Subplot4", pos4)
plt.text(-1, -1, title, horizontalalignment='center', fontsize=18)
plt.show()
drawSubplots(df1, df2,df3,df4,3.2,3.1,2.7,2.85,"test9", "test9")
Here is an attempt to:
have the left y-axes being "frequency" (which is very uninformative in the case of the current bin widths) and shared among the 4 subplots
have the right y-axes be a "probability density"; note how the top of all gaussians is around y=0.02 (the twin axes can only be set at the end because the shared y axes can be updated via later subplots)
have the histogram and the normal curve aligned
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from scipy.stats import norm
def drawSingle(myax, mydf, title):
num_bins = 200
xs = mydf["gap"]
x = np.linspace(-1, 1, 1000)
mu = np.mean(x)
sigma = np.std(xs)
n, bins, patches = myax.hist(xs, num_bins, alpha=0.8, facecolor='blue', density=False)
myax.set_ylabel('frequency', color="black", fontsize=12, weight="bold")
myax.set_xlabel('X', fontsize=12, weight="bold", horizontalalignment='center')
normalization_factor = len(xs) * (bins[1] - bins[0])
y_normcurve = norm.pdf(x, mu, sigma) * normalization_factor
myax.plot(x, y_normcurve, 'r--')
myax.vlines(mu, 0, y_normcurve.max(), 'y', '--', color='lime', label='example')
return normalization_factor
def drawSubplots(mydf1, mydf2, mydf3, mydf4, title):
plt.rcParams['figure.figsize'] = (18, 15)
fig, ax = plt.subplots(nrows=2, ncols=2, sharex=True, sharey=True)
dfs = [mydf1, mydf2, mydf3, mydf4]
norm_factors = [drawSingle(ax_i, df, title)
for ax_i, df, title in zip(ax.ravel(), dfs, ["Subplot1", "Subplot2", "Subplot3", "Subplot4"])]
for ax_i, norm_factor in zip(ax.ravel(), norm_factors):
ax_twin = ax_i.twinx()
ymax = ax_i.get_ylim()[1]
ax_twin.set_ylim(0, ymax / norm_factor)
plt.suptitle(title, fontsize=18)
plt.tight_layout()
plt.show()
df1, df2, df3, df4 = [pd.DataFrame({"gap": np.random.normal(0, 0.2, n)}) for n in [6000, 4000, 1800, 1200]]
drawSubplots(df1, df2, df3, df4, "Title")
Many thanks JohanC, you are amazing.
Based on your code, I just added a few lines of code within drawSubplots function in order to make 95% of the Gaussian curve area shaded between the lower bound and upper bound for each subplot. The following is my try. It seems that ax_twin.fill_between doesn't work normally here. As you could see from the figure that the shaded area is out of the Gaussian curve enter image description here. What I want is only to shade the area under the Gaussian curve between the lower bound and upper bound. If you don't mind, would you please check it out my mistake? Thank you very much!
import matplotlib.pyplot as plt
import math
from scipy.stats import norm
def align_yaxis(ax1, v1, ax2, v2):
#adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
inv = ax2.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, y1-y2))
miny, maxy = ax2.get_ylim()
ax2.set_ylim(miny+dy, maxy+dy)
def drawSingle(myax, mydf , title):
num_bins = 200
xs = mydf["gap"]
x = np.linspace(-1,1,1000)
mu =np.mean(xs)
sigma =np.std(xs)
n, bins, patches = myax.hist(xs, num_bins, alpha=0.8, facecolor='blue', density=False)
myax.set_ylabel('Frequency', color="black", fontsize=12, weight="bold")
myax.set_xlabel(title, fontsize=12, weight="bold", horizontalalignment='center')
normalization_factor = len(xs) * (bins[1] - bins[0])
y_normcurve = norm.pdf(x, mu, sigma) * normalization_factor
myax.plot(x, y_normcurve, 'r--')
myax.vlines(mu, 0, y_normcurve.max(), 'y', '--', color='lime', label='example')
plt.xlim(-0.8,0.8)
my_x_ticks = np.arange(-0.8, 0.8,0.1)
plt.xticks(my_x_ticks)
return normalization_factor, mu, sigma
def drawSubplots(mydf1,mydf2,mydf3,mydf4, title):
plt.rcParams['figure.figsize'] = (18,15 )
norm_factors = []
mus = []
sigmas = []
my_x_ticks = np.arange(-0.8, 0.8,0.1)
rows, cols = 2, 2
fig, ax = plt.subplots(nrows=rows, ncols=cols, sharex=True, sharey=True)
dfs = [mydf1, mydf2, mydf3, mydf4]
#norm_factors = [drawSingle(ax_i, df, title)
#for ax_i, df, title in zip(ax.ravel(), dfs, ["Subplot1", "Subplot2", "Subplot3", "Subplot4"])]
for ax_i, df, title in zip(ax.ravel(), dfs, ["Subplot1", "Subplot2", "Subplot3", "Subplot4"]):
norm_factor, mu, sigma = drawSingle(ax_i, df, title)
norm_factors.append(norm_factor)
mus.append(mu)
sigmas.append(sigma)
for ax_i, norm_factor, mu, sigma in zip(ax.ravel(), norm_factors, mus, sigmas ):
ax_twin = ax_i.twinx()
xmax = ax_i.get_xlim()[1]
ax_twin.set_ylim(0, xmax / norm_factor)
ax_twin.set_ylabel("probablility dense",color="black",fontsize=12, weight = "bold")
CI_95_lower = mu - (1.96*sigma)
CI_95_upper = mu + (1.96*sigma)
px_shaded = np.arange(CI_95_lower,CI_95_upper,0.1)
ax_twin.fill_between(px_shaded,norm.pdf(px_shaded,loc=mu,scale=sigma) * norm_factor,alpha=0.75, color='pink')
area_shaded_95_CI = norm.cdf(x=CI_95_upper, loc=mu, scale=sigma)-norm.cdf(x=CI_95_lower, loc=mu, scale=sigma)
ax_twin.text(-0.06,0.01,str(round(area_shaded_95_CI*100,1))+"%", fontsize=20)
ax_twin.annotate(s=f'lower bound= {CI_95_lower:.3f}',xy=(CI_95_lower,norm.pdf(CI_95_lower,loc=mu,scale=sigma)),xytext=(-0.75,0.01),weight='bold',color='blue',\
arrowprops=dict(arrowstyle='-|>',connectionstyle='arc3',color='green'),\
fontsize=12
)
ax_twin.annotate(s=f'upper bound= {CI_95_upper:.3f}',xy=(CI_95_upper,norm.pdf(CI_95_upper,loc=mu,scale=sigma)),xytext=(0.28,0.01),weight='bold',color='blue',\
arrowprops=dict(arrowstyle='-|>',connectionstyle='arc3',color='green'),\
fontsize=12
)
ax_twin.text(0.05, 0.03, r"$\mu=" + f'{mu:.6f}' + ", \sigma=" + f'{sigma:.6f}' + "$" + ", confidence interval=95%" ,
horizontalalignment='center', fontsize=15)
plt.suptitle(title, fontsize=18)
plt.tight_layout()
plt.show()
df1, df2, df3, df4 = [pd.DataFrame({"gap": np.random.normal(0, 0.2, n)}) for n in [6000, 4000, 1800, 1200]]
drawSubplots(df1, df2, df3, df4, "Title")

Bar text is above the plot matplotlib

Each bar on my plot has a value above (ax.text). But if the bar is tall, the text is above the plot. How can I resize the plot (figsize doesn't help) so that the text will be inside the picture?
import numpy as np
x = np.arange(len(l))
l = [200,240,302,371,478]
l2 = [17, 20, 26, 23, 29]
fig = plt.figure(figsize=(10,8))
ax1 = fig.add_subplot(111)
ax1.bar(x,l)
totals=[]
for i in ax1.patches:
totals.append(i.get_height())
total = sum(totals)
for i in ax1.patches:
ax1.text(i.get_x()+0.1, i.get_height()+20, str(int(i.get_height())), fontsize=14, color='black')
ax2 = ax1.twinx()
ax2.plot(l2, color = 'b')
for x1, y1 in zip(x, l2):
ax2.annotate(str(y1)+'%', xy = (x1-0.1,y1+1 ))
ax2.grid(False)
ax2.set_yticks([-25, -10, -5,0,5,10,15,20,25,30])
plt.show()
UPD: added code and picture
You have to use plt.ylim(ymin, ymax) to change minimum and maximum of you showed Y axis. I added two next lines for both plots, they add extra 10% of Y range at the top:
ymin, ymax = plt.ylim()
plt.ylim(ymin, ymax + 0.1 * (ymax - ymin))
You may also add different (not both 10%) amount of percents for both plots to avoid collision of them.
Full fixed code below:
import numpy as np, matplotlib.pyplot as plt
l = [200,240,302,371,478]
l2 = [17, 20, 26, 23, 29]
x = np.arange(len(l))
fig = plt.figure(figsize=(10,8))
ax1 = fig.add_subplot(111)
ax1.bar(x,l)
# Next two lines added
ymin, ymax = plt.ylim()
plt.ylim(ymin, ymax + 0.1 * (ymax - ymin))
totals=[]
for i in ax1.patches:
totals.append(i.get_height())
total = sum(totals)
for i in ax1.patches:
ax1.text(i.get_x()+0.1, i.get_height()+20, str(int(i.get_height())), fontsize=14, color='black')
ax2 = ax1.twinx()
ax2.plot(l2, color = 'b')
for x1, y1 in zip(x, l2):
ax2.annotate(str(y1)+'%', xy = (x1-0.1,y1+1 ))
ax2.grid(False)
ax2.set_yticks([-25, -10, -5,0,5,10,15,20,25,30])
# Next two lines added
ymin, ymax = plt.ylim()
plt.ylim(ymin, ymax + 0.1 * (ymax - ymin))
plt.show()
Result:

Advanced horizontal bar chart with Python?

I want to make a graph like the two below.
How can I achieve that with python? I am sorry that I can´t provide any implementation because I don´t have any idea at all. I think my question is something different to this.
https://matplotlib.org/gallery/lines_bars_and_markers/barh.html#sphx-glr-gallery-lines-bars-and-markers-barh-py
Could someone give me some suggestions with just some simple numbers?
The tutorial for vertical gradient bars can be adapted to draw horizontal bars with the darkest spot in the middle:
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.colors as mcolors
import numpy as np
def hor_gradient_image(ax, extent, darkest, **kwargs):
'''
puts a horizontal gradient in the rectangle defined by extent (x0, x1, y0, y1)
darkest is a number between 0 (left) and 1 (right) setting the spot where the gradient will be darkest
'''
ax = ax or plt.gca()
img = np.interp(np.linspace(0, 1, 100), [0, darkest, 1], [0, 1, 0]).reshape(1, -1)
return ax.imshow(img, extent=extent, interpolation='bilinear', vmin=0, vmax=1, **kwargs)
def gradient_hbar(y, x0, x1, ax=None, height=0.8, darkest=0.5, cmap=plt.cm.PuBu):
hor_gradient_image(ax, extent=(x0, x1, y - height / 2, y + height / 2), cmap=cmap, darkest=darkest)
rect = mpatches.Rectangle((x0, y - height / 2), x1 - x0, height, edgecolor='black', facecolor='none')
ax.add_patch(rect)
# cmap = mcolors.LinearSegmentedColormap.from_list('turq', ['paleturquoise', 'darkturquoise'])
cmap = mcolors.LinearSegmentedColormap.from_list('turq', ['#ACFAFA', '#3C9E9E'])
fig, ax = plt.subplots()
for y in range(1, 11):
x0, x1 = np.sort(np.random.uniform(1, 9, 2))
gradient_hbar(y, x0, x1, ax=ax, height=0.7, darkest=0.5, cmap=cmap)
ax.set_aspect('auto')
ax.use_sticky_edges = False
ax.autoscale(enable=True, tight=False)
ax.grid(axis='x')
plt.show()

ticker.FixedFormatter strategy to show the tics of the ax2 axis

Given 3 arrays:
X1 = 10.00, 30.10, 50.20, 70.30 ...
X2 = 1.9976433815311, 2.0109630315475, 2.0372702369401, 2.0665284897891 ...
Y = -0.0000008764356, -0.0000149459573, -0.0000326996870, -0.0000513717121 ...
There is a one-to-one correspondence between X1, X2 and Y, i.e.
the i-th element of X1 has an i-th associated value of X2 and a i-th value of Y.
The following is the plot of Y as a function of X1 (blue dots).
I would need the X2 axis to show all the corresponding X2 values for each X1 value.
Following the second answer on this post,
I have partially accomplished this thorugh the ticker.FixedFormatter strategy,
by which: the X2 array needs to be transformed to a tuple, and each element of this tuple needs to be a string.
As can be seen, not all red values of X2 are displayed for each value of X1, e.g. for X1 = 10.0 the corresponding X2 = 2.00 appears to be displaced.
I do not understand very well why this is occurring. I would appreciate if you could help me.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import sys
X1 = np.array([10.0000000000000, 30.1000000000000, 50.2000000000000, 70.3000000000000, 90.4000000000000, 110.5100000000000, 130.6100000000000, 150.7100000000000, 170.8100000000000, 190.9100000000000, 211.0100000000000, 231.1100000000000, 251.2100000000000, 271.3100000000000, 291.4100000000000, 311.5200000000000, 331.6200000000000 ])
Y = np.array([-0.0000008764356, -0.0000149459573, -0.0000326996870, -0.0000513717121, -0.0000652350399, -0.0000842214902, -0.0001003825474, -0.0001214363281, -0.0001376971422, -0.0001572720132, -0.0001971891337, -0.0002203926200, -0.0002747064193, -0.0003217228112, -0.0003764577474, -0.0004657478828, -0.0006232016207])
X2 = np.array([1.9976433815311, 2.0109630315475, 2.0372702369401, 2.0665284897891, 2.0995743328944, 2.1392386324550, 2.1789200955649, 2.2290243968267, 2.2872281293691, 2.3180577547912, 2.4100643103912, 2.4826981368480, 2.5794602952095, 2.6764219232389, 2.7963983991814, 2.9740753305878, 3.3107035136072])
##### Plotting:
fig, ax1 = plt.subplots()
ax1.plot(X1, Y, linestyle='--', marker="o", markersize=6, color='blue')
ax1.set_ylabel('Y', fontsize=20)
# Make the ax1-ticks and ax1-tick-labels match the line color (blue):
ax1.set_xlabel('X1', fontsize=20, color='blue')
plt.setp(ax1.get_xticklabels(), rotation='45') # rotate them
# Create a new axis:
ax2 = ax1.twiny()
# Make the ax2-ticks and ax2-tick-labels match the red color:
ax2.set_xlabel('X2', fontsize=20, color='red')
ax2.tick_params('x', colors='red')
fig.tight_layout()
ax2.set_xlim(1.9, 3.4)
ax1.set_ylim(-0.0007, 1.1e-5)
ax2.set_ylim(-0.0007, 1.1e-5)
ax1.grid()
# Convert all X2 elements to a list of strings:
X2_string_all = []
for i in X2:
aux = "%.2f" % i
X2_string = str(aux)
X2_string_all.append(X2_string)
# Convert that list into a tuple:
X2_string_all_tuple = tuple(X2_string_all)
ax1.xaxis.set_major_locator(ticker.FixedLocator((X1)))
ax2.xaxis.set_major_formatter(ticker.FixedFormatter((X2_string_all_tuple)))
plt.show()
Something like this would be the desired plot (the red lines that come across the plot are not necessary):
In your code ax2 does not know that it should behave exactly as ax1, just with different labels. So you need to tell it,
ax2.set_xlim(ax1.get_xlim())
Then just use the same tick locations for both axes,
ax1.set_xticks(X1)
ax2.set_xticks(X1)
and label the ticks of ax2 with values from X2
ax2.set_xticklabels(["%.2f" % i for i in X2])
Complete code:
import numpy as np
import matplotlib.pyplot as plt
X1 = np.array([10., 30.1, 50.2, 70.3, 90.4, 110.510, 130.610, 150.710, 170.810,
190.910, 211.010, 231.110, 251.210, 271.310, 291.410, 311.52, 331.62])
Y = np.array([-0.00000087, -0.0000149, -0.0000326, -0.0000513, -0.00006523, -0.0000842,
-0.0001003, -0.0001214, -0.00013769, -0.0001572, -0.0001971, -0.0002203,
-0.00027470, -0.0003217, -0.0003764, -0.0004657, -0.00062320])
X2 = np.array([1.997, 2.0109, 2.0372, 2.0665, 2.099, 2.1392, 2.1789, 2.2290,
2.287, 2.3180, 2.4100, 2.4826, 2.579, 2.6764, 2.7963, 2.9740, 3.310])
##### Plotting:
fig, ax1 = plt.subplots()
ax1.grid()
ax2 = ax1.twiny()
ax1.plot(X1, Y, linestyle='--', marker="o", markersize=6, color='blue')
ax1.set_ylabel('Y', fontsize=20)
ax1.set_xlabel('X1', fontsize=20, color='blue')
plt.setp(ax1.get_xticklabels(), rotation='45') # rotate them
ax2.set_xlabel('X2', fontsize=20, color='red')
plt.setp(ax2.get_xticklabels(), rotation='45', color='red')
# Set xlimits of ax2 the same as ax1
ax2.set_xlim(ax1.get_xlim())
# Set ticks at desired position
ax1.set_xticks(X1)
ax2.set_xticks(X1)
# Label ticks of ax2 with values from X2
ax2.set_xticklabels(["%.2f" % i for i in X2])
fig.tight_layout()
plt.show()

Categories

Resources