In [ ]:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
# When you run the following program, a new window should open displaying the mb-plane.
# Every time you click on the mb-plane, you are making a choice of m and b that determines a
# linear model y=mx+b of your data. Your goal is to find two numbers m and b that make the
# error for your model as small as possible. The contour map helps you decide where to click
# to obtain a small error and therefore helps you decide which values of m and b yield the best
# model.
#
# Note: I chose a simple error function so one could easily track the values.
def error_func(m,b):
return (1*m+b-4)**2+(2*m+b-6)**2+(3*m+b-5)**2
# Create a 'figure object'. It is basically a 'blank canvas' upon which one can
# draw multiple graphs on the same plot, or on multiple plots that partition the canvas.
# We will be adding 'axes objects' to fig. These objects are usually graphs.
fig = plt.figure()
# Create more space between my two plots.
plt.subplots_adjust(hspace=0.4)
################################################ Linear Regression Plot
# This determines the length of the line segment that you are graphing as 'regression line'
t = np.arange(-30.0, 30.0, 0.001)
# The initial values for regression line:
b0 = 0
m0 = 1
# The equation of the regression line:
s = b0 + m0*t
# Break fig into 2x2 grid and define 'axis' (i.e. a graph) in 3rd cell of grid.
ax = fig.add_subplot(223)
# Stackexchange tells me this will suppress the coordinates displaying on the bottom
# of the screen. I find them distracting.
ax.format_coord = lambda x, y: ''
# The x and y coordinates of the three data points that we are fitting:
d_x = [1, 2, 3]
d_y = [4, 6,5]
# Drawing scatter plot with labeled data points.
for xy in zip(d_x, d_y):
ax.annotate('(%s, %s)' % xy, xy=xy, xytext =(xy[0]+.05, xy[1]) , textcoords='data',fontsize=12)
ax.scatter(d_x, d_y, s=10, color='black')
ax.set_xlabel('x')
ax.set_ylabel('y', labelpad = 15, rotation=0)
ax.set_title('Data Points and Regression Line', fontsize=12)
# Initial heights of our line at x-values of data points. Needed to define segments.
p1 = 1
p2 = 2
p3= 3
# Vertical line segments indicating error defined below.
# The comma somehow allows the parameters of segment_i to update upon a clicking event(?).
# Note below the order is [x1, x2], [y1, y2] for two points (x1,y1),(x2,y2).
segment1, = plt.plot([d_x[0], d_x[0]], [p1, d_y[0]], 'r-', lw=2)
segment2, = plt.plot([d_x[1], d_x[1]], [p2, d_y[1]], 'r-', lw=2)
segment3, = plt.plot([d_x[2], d_x[2]], [p3, d_y[2]], 'r-', lw=2)
# Now we plot the regression line.
l, = plt.plot(t, s, lw=2, color='black')
# Here we specify the dimensions of our plot.
#axcolor = 'lightgoldenrodyellow'
#plt.axis([0, 4, 0, 8],axisbg=axcolor)
#plt.axis([0, 4, 3, 7])
plt.axis([0, 4, 0, 8])
#################################################### Contour Plot
axx = fig.add_subplot(221)
# See comment for similar expression above.
axx.format_coord = lambda x, y: ''
# '122' means: break the canvas up into a 1x2 grid and draw this stuff
# in the 2nd square
delta = .03
x = np.arange(0, 2.01, delta)
y = np.arange(0.0, 6.01, delta)
#x = y = np.arange(-8.0, 8.01, delta)
X, Y = np.meshgrid(x, y)
Z = error_func(X, Y)
# Setting up colormap/colorbar.
bounds = np.linspace(0, 15, 10)
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
pcm = axx.pcolormesh(X, Y, Z, norm=norm,cmap='PiYG')
fig.colorbar(pcm, ax=axx, extend='both', orientation='vertical')
axx.set_title( 'Contour Plot of Error Function', fontsize = 12 )
axx.set_xlabel('m')
axx.set_ylabel('b',labelpad = 15, rotation=0)
#axx.xaxis.tick_top()
#axx.xaxis.set_label_position('top')
#axx.invert_yaxis()
# Initialized values of variables defined so that they can be used in onclick below.
coord = axx.annotate('' , xy=(0,0), textcoords='data') # <--
coord1 = axx.annotate('' , xy=(0,0), textcoords='data') # <--
scat = axx.scatter([], [])
######################################## Initial Error/Regression Line
## intitial error
txt = 'The error function is: \n \n $(1m+b-4)^2+(2m+b-6)^2+(3m+b-5)^2$ \n $= \ %.2f$'%33
txt_message = fig.text(.55,.65,txt,fontsize = 12)
#initial regression line
txt1 = 'The black line to the left is defined by: \n \n $y=mx+b$ \n $m=%.2f \ \ b=%.2f$'%(1, 0)
txt_message1 = fig.text(.55,.2,txt1,fontsize=12)
# Considering a title.
#txt2 = 'Linear Regression'
#txt_message2 = fig.text(.4,.95,txt2,fontsize = 20)
######################################## Click event!
def onclick(event):
# You can get all this good info from a click:
# (event.button, event.x, event.y, event.xdata, event.ydata, event.inaxes)
axis_i_clicked = event.inaxes
if (axis_i_clicked == axx) and (event.xdata != None) and (event.ydata != None):
# Updates regression line.
l.set_ydata(event.ydata + event.xdata*t)
# Updates segment lengths.
segment1.set_xdata( [d_x[0],d_x[0]])
segment1.set_ydata([event.ydata + event.xdata*(d_x[0]),d_y[0]])
segment2.set_xdata( [d_x[1],d_x[1]])
segment2.set_ydata([event.ydata + event.xdata*(d_x[1]),d_y[1]])
segment3.set_xdata( [d_x[2],d_x[2]])
segment3.set_ydata([event.ydata + event.xdata*(d_x[2]),d_y[2]])
#ax.annotate('local max', xy=(2, 1), xytext=(3, 1.5),arrowprops=dict(facecolor='black', shrink=0.05),)
##################### the code below will tell you error of first segment
#global coord1
#coord1.remove()
#seg_len_1 = round(np.absolute( event.ydata + event.xdata*(-5) - 5) ,2 )
#coord1 = ax.annotate('%.2f'%seg_len_1 , xy=(-5, 5-seg_len_1),xytext=(-7, 5 - seg_len_1 ),color='red', fontsize=8)
fig.canvas.draw_idle()
# Updates error calculation and display of m and b.
txt = 'The error function is: \n \n $(1m+b-4)^2+(2m+b-6)^2+(3m+b-5)^2$ \n $= \ %.2f$'%error_func(event.xdata, event.ydata)
txt_message.set_text(txt)
#m = event.xdata
#b = event.ydata
#txt = 'Our error is: \n \n $(1m+b-4)^2+(2m+b-6)^2+(3m+b-5)^2$ \n $= \ %.2f^2 + %.2f^2 + %.2f^2$ \n $= \ %.2f$'%((m+b-4), (2*m+b-6), (3*m+b-5), error_func(m, b))
#txt_message.set_text(txt)
txt1 = 'The black line to the left is defined by: \n \n $y=mx+b$ \n $m=%.2f \ \ b=%.2f$'%(event.xdata, event.ydata)
txt_message1.set_text(txt1)
# Updates coordinate display on contour plot.
#
# The first time one clicks on the contour plot, 'coord' is not defined locally
# and python starts by looking locally. It won't find 'coord' and an error occurs.
# By telling python that 'coord' is global, python now does the following: It still
# first looks locally (in body of onclick function). The first time it does, it doesnt
# find coord defined there, so the 'global' tells python to NOW start 'at the top',
# outside all functions, where it finds my 'initialized' definition for coord. The
# second time one clicks on the contour plot, Python still looks locally first, and
# finds coord defined there, so things proceed as expected, and my 'initialized' def
# is never used again.
x_y = (event.xdata, event.ydata)
global coord
coord.remove()
axx.autoscale(enable=False)
coord = axx.annotate('(%s, %s)' % (round(x_y[0],2), round(x_y[1],2)), xytext=(x_y[0]+.05, x_y[1]), xy=x_y, textcoords='data', fontsize=12)
# Now I will try to add dot where I click on contour plot.
global scat
scat.remove()
scat = axx.scatter([event.xdata], [event.ydata], s=2)
#figg.canvas.draw_idle() # 'fig' is name of specific object
fig.canvas.draw_idle()
cid = fig.canvas.mpl_connect('button_press_event', onclick)
###################################################################
#fig.tight_layout()
#plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=1.0)
plt.show()
In [ ]: