Zoom in the pyplot selection with inset_axes - python

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:

Related

Having just one legend when using matplotlib zoomed_inset_axes

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()

python: How to plot and put annotation at a natural position

I am using python to plot and my codes are:
import matplotlib.pyplot as plt
import numpy as np
# these are the data to be plot
x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14]
x_test = ['grid50', 'grid100', 'grid150', 'grid250', 'grid500', 'grid750', 'NN5', 'NN10', 'NN15', 'NN20', 'NN50', 'NN100', 'CB', 'CBG']
clf = [0.58502, 0.60799, 0.60342, 0.59629, 0.56464, 0.53757, 0.62567, 0.63429, 0.63583, 0.63239, 0.63315, 0.63156, 0.60630, 0.52755]
hitrate = [0.80544, 0.89422, 0.94029, 0.98379, 0.99413, 0.99921, 0.99478, 0.99961, 0.99997, 0.99980, 0.99899, 0.99991, 0.88435, 1.0]
level = [23.04527, 9.90955, 4.35757, 1.46438, 0.51277, 0.15071, 1.30057, 0.00016, 0.00001, 0.00021, 0.00005, 0.00004, 6.38019, 0]
fig = plt.figure(figsize=(20,7))
ax = fig.add_subplot(111)
fig.subplots_adjust(right=0.8)
# this is the function to put annotation on bars
def autolabel(rects):
# attach some text labels
for ii,rect in enumerate(rects):
height = rect.get_height()
plt. text(rect.get_x()+rect.get_width()/2., 1.02*height, '%s'% (clf[ii]),ha='center', va='bottom')
plt.xticks(x,x_test)
# this part is to plot the red bar charts
ins1 = ax.bar(x,clf,color='Red', align='center',label='classification results')
ax.set_ylabel('classification results', color='Red')
ax.tick_params(axis='y',colors='Red')
ax.set_ylim(0,1.5)
autolabel(ins1)
# this part is to plot the green hitrate and the for-loop is to put annotation next to the line
ax2 = ax.twinx()
ins2, = ax2.plot(x,hitrate,marker='o',color='Green', linewidth=3.0, label='hitrate')
ax2.set_ylabel('hitrate', color='Green')
ax2.tick_params(axis='y',colors='Green')
ax2.set_ylim(0,1.5)
for i,j in zip(x, hitrate):
ax2.annotate(str(j),xy=(i,j+0.02))
# this part is to plot the blue level, forloop same as that of hitrate
ax3 = ax.twinx()
axes = [ax, ax2, ax3]
ax3.spines['right'].set_position(('axes', 1.1))
ax3.set_frame_on(True)
ax3.patch.set_visible(False)
ins3, = ax3.plot(x,level,marker='^', color='Blue', linewidth=3.0, label='obfuscation level')
ax3.set_ylabel('obfuscation level', color='Blue')
ax3.tick_params(axis='y',colors='Blue')
ax3.set_ylim(0,25)
for i,j in zip(x, level):
ax3.annotate(str(j),xy=(i,j+0.02))
ax.set_xlabel('Cell Configurations')
ax.set_xlim(0,15)
ax.set_title('benchmark')
ax.legend([ins1,ins2,ins3],['clf', 'hit', 'level'])
plt.grid()
plt.show()
And I got a figure like :
The problem is that, some numbers are not put in a good place so to be read clearly, but I don't know whether there is a method to put the annotation naturally at a blank area. Any ideas?

Axis limits for scatter plot - Matplotlib

I'm having the same problem presented here, however, the proposed solution didn't work for me.
I'm plotting a set of data which the main plot have this pattern:
Which is a plot which axis limits varies from (-1, 1) in both x and y, with a margin set with this piece of code:
plt.figure()
plt.show(data)
## Add some margin
l, r, b, t = plt.axis()
dx, dy = r-l, t-b
plt.axis([l-0.1*dx, r+0.1*dx, b-0.1*dy, t+0.1*dy])
The problem is 'cause I have more "complex" plot in which some changes had to me made. This is the code that produces it:
def plot_quiver_singularities(min_points, max_points, vector_field_x, vector_field_y, file_path):
"""
Plot the singularities of vector field
:param file_path : the path to save the data
:param vector_field_x : the vector field x component to be plot
:param vector_field_y : the vector field y component to be plot
:param min_points : a set (x, y) of min points field
:param max_points : a set (x, y) of max points field
"""
fig = plt.figure(figsize=(8, 8))
ax = fig.add_axes([.13, .3, .6, .6])
## Plot quiver
x, y = numpy.mgrid[-1:1:100*1j, -1:1:100*1j]
m = numpy.sqrt(numpy.power(vector_field_x, 2) + numpy.power(vector_field_y, 2))
quiver = ax.quiver(x, y, vector_field_x, vector_field_y, m, zorder=1)
## Plot critical points
x = numpy.linspace(-1, 1, x_steps)
y = numpy.linspace(-1, 1, y_steps)
# Draw the min points
x_indices = numpy.nonzero(min_points)[0]
y_indices = numpy.nonzero(min_points)[1]
ax.scatter(x[x_indices], y[y_indices], marker='$\\circlearrowright$', s=100, zorder=2)
# Draw the max points
x_indices = numpy.nonzero(max_points)[0]
y_indices = numpy.nonzero(max_points)[1]
ax.scatter(x[x_indices], y[y_indices], marker='$\\circlearrowleft$', s=100, zorder=2)
## Put legends
marker_min = plt.Line2D((0, 0), (0, 0), markeredgecolor=(1.0, 0.4, 0.0), linestyle='',
marker='$\\circlearrowright$', markeredgewidth=1, markersize=10)
marker_max = plt.Line2D((0, 0), (0, 0), markeredgecolor=(0.2, 0.2, 1.0), linestyle='',
marker='$\\circlearrowleft$', markeredgewidth=1, markersize=10)
plt.legend([marker_min, marker_max], ['CW rot. center', 'CCW rot. center'], numpoints=1,
loc='center left', bbox_to_anchor=(1, 0.5))
quiver_cax = fig.add_axes([.13, .2, .6, .03])
fig.colorbar(quiver, orientation='horizontal', cax=quiver_cax)
## Set axis limits
plt.xlim(-1, 1)
plt.ylim(-1, 1)
## Add some margin
# l, r, b, t = plt.axis()
# dx, dy = r-l, t-b
# plt.axis([l-0.1*dx, r+0.1*dx, b-0.1*dy, t+0.1*dy])
plt.savefig(file_path + '.png', dpi=dpi)
plt.close()
This produces the following image:
As can be seen, the axis limits do not hold and I didn't found why yet.
Any help would be appreciated.
Thank you in advance.
I was able to solve the problem putting this piece of code
plt.xlim(-1, 1)
plt.ylim(-1, 1)
Right after calling scatter().
You can also set those to the ax object:
ax.set_xlim((-1,1))
ax.set_ylim((-1,1))

Unable to change tick label properties in matplotlib

No matter what I seem to do, or whichever past questions I seem to look up - I seem unable to change simple properties of tick labels.
This code:
from mpl_toolkits.axes_grid.axislines import SubplotZero
from matplotlib.transforms import BlendedGenericTransform
import matplotlib.pyplot as plt
import numpy
if 1:
fig = plt.figure(1)
ax = SubplotZero(fig, 111)
fig.add_subplot(ax)
# thicken the axis lines
ax.axhline(linewidth=1.7, color="k")
ax.axvline(linewidth=1.7, color="k")
plt.xticks([-numpy.pi/2, -numpy.pi/4, 0, numpy.pi/4, numpy.pi/2], [r'$-\pi$', r'$-\pi/2$', r'$O$', r'$\pi/2$', r'$\pi$'], rotation=30)
plt.yticks([])
#ax.set_xticklabels([r'$-\pi$', r'$-\pi/2$', r'$0$', r'$\pi/2$', r'$\pi$'], rotation=40, ha='left')
# end-of-axis arrows
ax.text(0, 1.05, r'$y$', transform=BlendedGenericTransform(ax.transData, ax.transAxes), ha='center')
ax.text(1.03, 0, r'$x$', transform=BlendedGenericTransform(ax.transAxes, ax.transData), va='center')
plt.ylim(-5, 5)
plt.xlim(-numpy.pi/2, numpy.pi/2)
x_width = (abs(plt.xlim()[0]) + abs(plt.xlim()[1])) / 2
y_width = (abs(plt.ylim()[0]) + abs(plt.ylim()[1])) / 2
# end-of-axis arrows
plt.arrow(plt.xlim()[1], -0.003, x_width*0.01, 0,
width=x_width*0.0015, color="k", clip_on=False,
head_width=y_width*0.24/7, head_length=x_width*0.024)
plt.arrow(0.003, plt.ylim()[1], 0, y_width*0.01,
width=y_width*0.0015, color="k", clip_on=False,
head_width=x_width*0.24/7, head_length=y_width*0.024)
for direction in ["xzero", "yzero"]:
ax.axis[direction].set_visible(True)
for direction in ["left", "right", "bottom", "top"]:
ax.axis[direction].set_visible(False)
x = numpy.linspace(-numpy.pi/2, numpy.pi/2, 2500)
yy = numpy.tan(2*(x - numpy.pi/2))
threshold = 1000
yy[yy > threshold] = numpy.inf
yy[yy < -threshold] = numpy.inf
ax.plot(x, yy, linewidth=1.2, color="black")
ax.axvline(x=-3*numpy.pi/4, linewidth=1.0, color="k", linestyle="--")
ax.axvline(x=-numpy.pi/4, linewidth=1.0, color="k", linestyle="--")
ax.axvline(x=numpy.pi/4, linewidth=1.0, color="k", linestyle="--")
ax.axvline(x=3*numpy.pi/4, linewidth=1.0, color="k", linestyle="--")
plt.savefig('MC6.png')
with particular note of this line:
plt.xticks([-numpy.pi/2, -numpy.pi/4, 0, numpy.pi/4, numpy.pi/2], [r'$-\pi$', r'$-\pi/2$', r'$O$', r'$\pi/2$', r'$\pi$'], rotation=30)
does not rotate the labels by 30 degrees. Likewise if I do other workarounds to change the font-size, the labels always stay the same.
Am I missing something super simple about matplotlib???
Add this line:
ax.axis["xzero"].major_ticklabels.set_rotation(30)
For the reference, here's a mailing list item where this comes from: http://matplotlib.1069221.n5.nabble.com/rotating-x-tick-labels-bold-labels-with-axislines-toolkit-td7661.html
As to why what you do has no effect, I can only guess. A wild guess would be that the state machine (which works under the hood when you call functions from the pyplot namespace) assumes that plt.xticks relate to the bottom spine, which you've explicitly set to be invisible.

How to draw line inside a scatter plot

I can't believe that this is so complicated but I tried and googled for a while now.
I just want to analyse my scatter plot with a few graphical features.
For starters, I want to add simply a line.
So, I have a few (4) points and I want to add a line to it, like in this plot (source: http://en.wikipedia.org/wiki/File:ROC_space-2.png)
Now, this won't work. And frankly, the documentation-examples-gallery combo and content of matplotlib is a bad source for information.
My code is based upon a simple scatter plot from the gallery:
# definitions for the axes
left, width = 0.1, 0.85 #0.65
bottom, height = 0.1, 0.85 #0.65
bottom_h = left_h = left+width+0.02
rect_scatter = [left, bottom, width, height]
# start with a rectangular Figure
fig = plt.figure(1, figsize=(8,8))
axScatter = plt.axes(rect_scatter)
# the scatter plot:
p1 = axScatter.scatter(x[0], y[0], c='blue', s = 70)
p2 = axScatter.scatter(x[1], y[1], c='green', s = 70)
p3 = axScatter.scatter(x[2], y[2], c='red', s = 70)
p4 = axScatter.scatter(x[3], y[3], c='yellow', s = 70)
p5 = axScatter.plot([1,2,3], "r--")
plt.legend([p1, p2, p3, p4, p5], [names[0], names[1], names[2], names[3], "Random guess"], loc = 2)
# now determine nice limits by hand:
binwidth = 0.25
xymax = np.max( [np.max(np.fabs(x)), np.max(np.fabs(y))] )
lim = ( int(xymax/binwidth) + 1) * binwidth
axScatter.set_xlim( (-lim, lim) )
axScatter.set_ylim( (-lim, lim) )
xText = axScatter.set_xlabel('FPR / Specificity')
yText = axScatter.set_ylabel('TPR / Sensitivity')
bins = np.arange(-lim, lim + binwidth, binwidth)
plt.show()
Everything works, except the p5 which is a line.
Now how is this supposed to work?
What's good practice here?
plottakes either y values and uses x as index array 0..N-1 or x and y values as described in the documentation. So you could use
p5 = axScatter.plot((0, 1), "r--")
in your code to plot the line.
However, you are asking for "good practice".
The following code (hopefully) shows some "good practise" and some of the capabilities of matplotlib to create the plot you mention in your question.
import numpy as np
import matplotlib.pyplot as plt
# create some data
xy = np.random.rand(4, 2)
xy_line = (0, 1)
# set up figure and ax
fig, ax = plt.subplots(figsize=(8,8))
# create the scatter plots
ax.scatter(xy[:, 0], xy[:, 1], c='blue')
for point, name in zip(xy, 'ABCD'):
ax.annotate(name, xy=point, xytext=(0, -10), textcoords='offset points',
color='blue', ha='center', va='center')
ax.scatter([0], [1], c='black', s=60)
ax.annotate('Perfect Classification', xy=(0, 1), xytext=(0.1, 0.9),
arrowprops=dict(arrowstyle='->'))
# create the line
ax.plot(xy_line, 'r--', label='Random guess')
ax.annotate('Better', xy=(0.3, 0.3), xytext=(0.2, 0.4),
arrowprops=dict(arrowstyle='<-'), ha='center', va='center')
ax.annotate('Worse', xy=(0.3, 0.3), xytext=(0.4, 0.2),
arrowprops=dict(arrowstyle='<-'), ha='center', va='center')
# add labels, legend and make it nicer
ax.set_xlabel('FPR or (1 - specificity)')
ax.set_ylabel('TPR or sensitivity')
ax.set_title('ROC Space')
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.legend()
plt.tight_layout()
plt.savefig('scatter_line.png', dpi=80)
By the way: I think that matplotlibs documentation is quite useful nowadays.
the p5 line should be:
p5 = axScatter.plot([1,2,3],[1,2,3], "r--")
argument 1 is a list of the x values, and argument 2 is a list of y values
If you just want a straight line, you only need to provide values for the extremities of the line.

Categories

Resources