Axis limits for scatter plot - Matplotlib - python

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

Related

Want to plot a histogram showing zoom part of main graph

I want to plot a histogram showing the zoom part of the main graph(scatter plot)
Graph from below code Graph from below code
Ideal graph Ideal graph
I want to plot histogram showing it is some point of scatter plot but it creates a different plot
Here actual and Pred is dataframe
actual = [380.40191971 145.59225239 190.70737789 112.4604138
244.80801269
65.35493987 17.44314577 192.42304652 266.70685679 321.51874529
290.25216447 352.55512476 182.61866021 208.35782926 219.94210006
99.79159246 355.93408355 309.99909547 191.54331448 186.93046933
306.24333458 122.12239012 215.55874595 49.22826277 235.22153724 ]
pred = [354.82370437 160.74472764 193.68395055 143.28111271
239.29022664
98.48337116 74.63047227 203.70767313 244.81584894 297.74615263
276.67065251 312.58961576 194.0053793 217.6234544 217.89758764
153.87776083 341.69926994 280.82340828 205.21125304 212.45958412
292.26134849 136.84410903 206.75852888 93.80596594 252.11452717 ]
min_range=pred.min()-10
max_range=pred.max()+10
min_domain=actual.min()-10
max_domain=actual.max()+10
#scaling and creating scatter plot
plt.axes([0, 0, 4, 2])
plt.scatter(x=actual,y=pred, marker="o") #(y = predicted)
plt.gca().set_aspect('equal', adjustable='box')
plt.grid()
plt.xlabel('Actual Values', fontsize = 20)
plt.ylabel('Predicted Values', fontsize = 20)
#adding regression line
plt.plot([min_domain, max_domain], [min_range, max_range], color='g', linestyle='-', linewidth=1,label='regression')
#adding line passing minimum and maximum actual points
plt.plot([min_domain, max_domain],[min_domain, max_domain],color='r',linestyle='-',linewidth=1,label='actual point line')
#adding legend
plt.legend(loc='lower right')
#calculating x and y range
axes = plt.gca()
y_min, y_max = axes.get_ylim()
x_min, x_max = axes.get_xlim()
#Coordinates of interested area
#percentile = 10
#nth_percentile = np.percentile(actual,percentile)
bottom, left, width, height = 0, 0, 100,100
x_hist = x_min +(x_max - x_min)/9 #may have to change value 9
#calculating lines for selected area
x1, y1 = [left, x_hist], [bottom+height, (y_max + y_min)/2]
x2, y2 = [left + width, x_hist], [bottom + height, (y_max + y_min)/2]
L_act = []
L_pred = []
for x, y in zip(actual, pred):
if left <= x <= width+left:
if bottom<= y <= height + bottom:
L_act.append(x)
L_pred.append(y)
#adding rectangle for selected area
rect=mpatches.Rectangle((left, bottom),width, height, fill = False, color = "black",linewidth = 2)
plt.gca().add_patch(rect)
#adding lines to indicated the selected area
plt.plot(x1, y1, x2, y2, color = 'black', linewidth = 2)
#adding histogram
plt.axes([0.2, 1, .6, .6], facecolor='w')
plt.hist(L_act, 30)
plt.xticks([])
plt.yticks([])
plt.show()

Creating a colorbar shifts two formerly aligned axis objects relative to each other - Matplotlib

My in my "real world" problem i want to recalculate the x y values written in the tick labeling of my figure after i have zoomed in it in such a way that the origin is always at (0,0) and obviously the relative distances of the values on the x and y axis stay the same.
My problem was solved in this thread:
Initial solution
The solution includes the creation of one invisible axis that holds the plot and one visible axis that gets different tick lables after zooming.
In my case i want to superimpose multiple countor and coutourf plots. For only one of those plots i want to add a colorbar to the figure!
But when i create the colorbar in my script, the two axis objects i have created shift relative to each other. Without the colorbar they are perfectly aligned!
Here is a MWE that roughly recreates the behavior:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import mlab, cm
# Default delta is large because that makes it fast, and it illustrates
# the correct registration between image and contours.
delta = 0.5
extent = (-3, 4, -4, 3)
x = np.arange(-3.0, 4.001, delta)
y = np.arange(-4.0, 3.001, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = (Z1 - Z2) * 10
levels = np.arange(-2.0, 1.601, 0.4) # Boost the upper limit to avoid truncation errors.
norm = cm.colors.Normalize(vmax=abs(Z).max(), vmin=-abs(Z).max())
cmap = cm.PRGn
# ax is empty
fig, ax = plt.subplots()
ax.set_navigate(False)
# ax2 will hold the plot, but has invisible labels
ax2 = fig.add_subplot(111,zorder=2)
ax2.contourf(X, Y, Z, levels,
cmap=cm.get_cmap(cmap, len(levels) - 1),
norm=norm,
)
ax2.axis("off")
ax.set_xlim(ax2.get_xlim())
ax.set_ylim(ax2.get_ylim())
#
# Declare and register callbacks
def on_lims_change(axes):
# change limits of ax, when ax2 limits are changed.
a=ax2.get_xlim()
ax.set_xlim(0, a[1]-a[0])
a=ax2.get_ylim()
ax.set_ylim(0, a[1]-a[0])
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm ) #Do not show unnecessary parts of the colormap
sm._A = []
cb = plt.colorbar(sm,extend="both", label="units")
cb.ax.tick_params(labelsize=10)
ax2.callbacks.connect('xlim_changed', on_lims_change)
ax2.callbacks.connect('ylim_changed', on_lims_change)
ax.axis('scaled')
plt.axis('scaled')
# Show
plt.show()
Now the contourplot seems to be shifted realtive to the visible axis. I found a few hints online that suggest, that the "colorbar box automatically eats up space from the axes to which it is attached"
Link1
Link2
But i do not really know what i need to do to change this behavior nor do i understand if my issue is related.
Please note, that the part:
ax.axis('scaled')
plt.axis('scaled')
is necessary as i need to keep the aspect ratio exactly like it is in the data set!
Thank you in advance!
You can change the position of ax (the empty axes with the labels) to the position of ax2 (the axes showing the data) after adding the colorbar via
ax.set_position(ax2.get_position())
Alternatively, create the colorbar by "steeling" the space from both axes,
cb = fig.colorbar(sm,ax=[ax,ax2], extend="both", label="units")
Both solutions are found in the answers to this linked question.
The following are some additional improvements outside the actual scope of the question:
ax.axis('scaled')
ax2.axis('scaled')
Additionally, put the ax on top if the ax2, such that the contourf plot does not overlap the axes spines.
# put `ax` on top, to let the contours not overlap the shown axes
ax.set_zorder(2)
ax.patch.set_visible(False)
# ax2 will hold the plot, but has invisible labels
ax2 = fig.add_subplot(111,zorder=1)
Complete code:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import mlab, cm
delta = 0.5
extent = (-3, 4, -4, 3)
x = np.arange(-3.0, 4.001, delta)
y = np.arange(-4.0, 3.001, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = (Z1 - Z2) * 10
levels = np.arange(-2.0, 1.601, 0.4)
norm = cm.colors.Normalize(vmax=abs(Z).max(), vmin=-abs(Z).max())
cmap = cm.PRGn
# ax is empty
fig, ax = plt.subplots()
ax.set_navigate(False)
# put `ax` on top, to let the contours not overlap the shown axes
ax.set_zorder(2)
ax.patch.set_visible(False)
# ax2 will hold the plot, but has invisible labels
ax2 = fig.add_subplot(111,zorder=1)
ax2.contourf(X, Y, Z, levels,
cmap=cm.get_cmap(cmap, len(levels) - 1),
norm=norm,
)
ax2.axis("off")
ax.set_xlim(ax2.get_xlim())
ax.set_ylim(ax2.get_ylim())
#
# Declare and register callbacks
def on_lims_change(axes):
# change limits of ax, when ax2 limits are changed.
a=ax2.get_xlim()
ax.set_xlim(0, a[1]-a[0])
a=ax2.get_ylim()
ax.set_ylim(0, a[1]-a[0])
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm )
sm._A = []
cb = fig.colorbar(sm,ax=[ax,ax2], extend="both", label="units")
cb.ax.tick_params(labelsize=10)
ax2.callbacks.connect('xlim_changed', on_lims_change)
ax2.callbacks.connect('ylim_changed', on_lims_change)
ax.axis('scaled')
ax2.axis('scaled')
#ax.set_position(ax2.get_position())
# Show
plt.show()

How to totally remove x-axis (and y-axis) from the plot and also draw tangent lines at certain points using Python or R-programming?

I have to create a plot that has axes suppressed and tangents drawn at regular intervals as shown in figure presented below.
Using R-programming, I know how to suppress tick marks and create the plot.
But I don't know how to suppress the whole axes.
Here, I need to omit the whole a-axis as well other axes such as top and right axes.
My initial try is this:
tau <- seq(-5,5,0.01)
a <- 0.4 # a is a constant parameter
sigma <- a*tau # tau is a variable, sigma = a*tau
x <- 1/a*cosh(sigma)
y <- 1/a*sinh(sigma)
# plot
plot(x,y,type="l",xaxt="n",yaxt="n")
abline(h=0,lty=1)
The plot also requires dots and tangents at points where a*tau = -1,-0.5, 0, 0.5 and 1.
The links I followed are following:
Drawing a Tangent to the Plot and Finding the X-Intercept using R
Lines between certain points in a plot, based on the data? (with R)
The required plot looks like below:
Any suggestion both in python or R are truly appreciated!!
Using Python,
import numpy as np
import matplotlib.pyplot as plt
tau = np.arange(-5, 5, 0.01)
a = 0.4
sigma = a*tau
x = 1/a*np.cosh(sigma)
y = 1/a*np.sinh(sigma)
fig, ax = plt.subplots()
ax.plot(x, y, c='black')
# approximate the curve by a cubic
dxds = np.poly1d(np.polyfit(sigma, x, 3)).deriv()
dyds = np.poly1d(np.polyfit(sigma, y, 3)).deriv()
xs, ys, dxs, dys = [], [], [], []
for s in np.linspace(-1, 1, 5):
# evaluate the derivative at s
dx = np.polyval(dxds, s)
dy = np.polyval(dyds, s)
# record the x, y location and dx, dy tangent vector associated with s
xi = 1/a*np.cosh(s)
yi = 1/a*np.sinh(s)
xs.append(xi)
ys.append(yi)
dxs.append(dx)
dys.append(dy)
if s == 0:
ax.text(xi-0.75, yi+1.5, '$u$', transform=ax.transData)
ax.annotate('$a^{-1}$',
xy=(xi, yi), xycoords='data',
xytext=(25, -5), textcoords='offset points',
verticalalignment='top', horizontalalignment='left',
arrowprops=dict(arrowstyle='-', shrinkB=7))
ax.quiver(xs, ys, dxs, dys, scale=1.8, color='black', scale_units='xy', angles='xy',
width=0.01)
ax.plot(xs, ys, 'ko')
# http://stackoverflow.com/a/13430772/190597 (lucasg)
ax.set_xlim(-0.1, x.max())
left, right = ax.get_xlim()
low, high = ax.get_ylim()
ax.arrow(0, 0, right, 0, length_includes_head=True, head_width=0.15 )
ax.arrow(0, low, 0, high-low, length_includes_head=True, head_width=0.15 )
ax.text(0.03, 1, '$t$', transform=ax.transAxes)
ax.text(1, 0.47, '$x$', transform=ax.transAxes)
plt.axis('off')
ax.set_aspect('equal')
plt.show()
yields

Legend combined with Text, how to find the legend width and height

I would like to set legend and text boxes locations and styles exactly same, the latter especially to make text aligned.
import matplotlib.pyplot as plt
x = np.arange(10)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
for i in range(3):
ax.plot(x, i * x ** 2, label = '$y = %i x^2$'%i)
ax.set_title('example plot')
# Shrink the axis by 20% to put legend and text at the bottom
#+ of the figure
vspace = .2
box = ax.get_position()
ax.set_position([box.x0, box.y0 + box.height * vspace,
box.width, box.height * (1 - vspace)])
# Put a legend to the bottom left of the current axis
x, y = 0, 0
# First solution
leg = ax.legend(loc = 'lower left', bbox_to_anchor = (x, y), \
bbox_transform = plt.gcf().transFigure)
# Second solution
#leg = ax.legend(loc = (x, y)) , bbox_transform = plt.gcf().transFigure)
# getting the legend location and size properties using a code line I found
#+ somewhere in SoF
bb = leg.legendPatch.get_bbox().inverse_transformed(ax.transAxes)
ax.text(x + bb.width, y, 'some text', transform = plt.gcf().transFigure, \
bbox = dict(boxstyle = 'square', ec = (0, 0, 0), fc = (1, 1, 1)))
plt.show()
This should place the text at the right of the legend box but that's not what it does. And the two boxes are not vertically aligned.
The second solution does not actually anchoring the legend to the figure, but to the axes instead.
You can use the frame data to get the right width in order to position the Text() object correctly.
In the example below I had to apply a 1.1 factor for the width (this value I haven't found how to get, and if you don't apply the factor the text clashes with the legend).
Note also that you must plt.draw() before getting the right width value.
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(10)
fig = plt.figure(figsize=(3, 2))
ax = fig.add_subplot(1, 1, 1)
for i in range(3):
ax.plot(x, i*x**2, label=r'$y = %i \cdot x^2$'%i)
ax.set_title('example plot')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
x, y = 0.2, 0.5
leg = ax.legend(loc='lower left', bbox_to_anchor=(x, y),
bbox_transform=fig.transFigure, fontsize=8)
plt.draw()
f = leg.get_frame()
w0, h0 = f.get_width(), f.get_height()
inv = fig.transFigure.inverted()
w, h = inv.transform((w0, h0))
ax.text(x+w*1.1, y+h/2., 'some text', transform=fig.transFigure,
bbox=dict(boxstyle='square', ec=(0, 0, 0), fc=(1, 1, 1)),
fontsize=7)
fig.savefig('test.jpg', bbox_inches='tight')
for x, y = 0.2, 0.5:
for x, y = -0.3, -0.3:

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