Related
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:
I have created a confidence interval plot which is working exactly how I want:
month = ['Nov-20', 'Dec-20', 'Jan-21', 'Feb-21', 'Mar-21', 'Apr-21', 'May-21', 'Jun-21', 'Jul-21', 'Aug-21', 'Sep-21', 'Oct-21']
x = [0.85704744, 0.74785299, 0.68103776, 0.69793547, 0.8396294 ,
0.25560889, 0.37400785, 0.00742866, 0.84700224, 0.95142221,
0.08544432, 0.09068883]
y = [0.09448781, 0.69683102, 0.96261431, 0.93635227, 0.31503366,
0.38335671, 0.24244469, 0.36712811, 0.22270387, 0.01506295,
0.78433 , 0.38408096]
z = [0.84585527, 0.59615266, 0.60263581, 0.26366399, 0.42948978,
0.18138516, 0.54841131, 0.65201558, 0.03089001, 0.20581638,
0.57586628, 0.33622286]
fig, ax = plt.subplots(figsize=(17,8))
ax.plot(month, z)
ax.fill_between(month, x, y, color='b', alpha=.3)
ax.hlines(y=0.50, xmin=0, xmax=(len(month)), colors='orange', linestyles='--', lw=2, label="Target: 50%")
plt.xlabel('Month')
plt.ylabel('Target %')
plt.rcParams["font.size"] = "20"
plt.ylim((0.1, 1.0))
plt.legend(bbox_to_anchor=(1.04,0.5), loc="center left", borderaxespad=0)
plt.title("Target Forecast Nov20 - Nov21")
plt.show()
plt.close()
However, I want to add the following to the legend:
An indicator that the blue line is the "probable forecast"
An indicator that the blue fill_between is the confidence interval
I did read this matplotlib documentation, and so I tried:
fig, ax = plt.subplots(figsize=(17,8))
prob, = ax.plot(month, z)
btwn, = ax.fill_between(month, x, y, color='b', alpha=.3)
tgt, = ax.hlines(y=0.50, xmin=0, xmax=(len(month)), colors='orange', linestyles='--', lw=2, label="Target: 50%")
plt.xlabel('Month')
plt.ylabel('Target %')
plt.rcParams["font.size"] = "20"
plt.ylim((0.1, 1.0))
plt.legend([prob, btwn, tgt], ['Probable', 'Confidence Interval', 'Target'])
plt.title("Target Forecast Nov20 - Nov21")
plt.show()
plt.close()
But it ends in a TypeError:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-61-3ef952c0fc7f> in <module>
1 fig, ax = plt.subplots(figsize=(17,8))
2 prob, = ax.plot(month, z)
----> 3 btwn, = ax.fill_between(month, x, y, color='b', alpha=.3)
4 tgt, = ax.hlines(y=0.50, xmin=0, xmax=(len(month)), colors='orange', linestyles='--', lw=2, label="Target: 50%")
5 plt.xlabel('Month')
TypeError: 'PolyCollection' object is not iterable
How can I add these things to the legend?
The matplotlib documentation often suggests to use proxy artists.
Otherwise, in your case, you can just add the label argument and name it the way you want, and the legend should be updated automatically.
In your case:
ax.plot(month, z, label="Probable Forecast")
ax.fill_between(month, x, y, color='b', alpha=.3, label="Confidence Interval")
should work.
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?
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.
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.