1+ """
2+ Locally weighted linear regression, also called local regression, is a type of
3+ non-parametric linear regression that prioritizes data closest to a given
4+ prediction point. The algorithm estimates the vector of model coefficients β
5+ using weighted least squares regression:
6+
7+ β = (XᵀWX)⁻¹(XᵀWy),
8+
9+ where X is the design matrix, y is the response vector, and W is the diagonal
10+ weight matrix.
11+
12+ This implementation calculates wᵢ, the weight of the ith training sample, using
13+ the Gaussian weight:
14+
15+ wᵢ = exp(-‖xᵢ - x‖²/(2τ²)),
16+
17+ where xᵢ is the ith training sample, x is the prediction point, τ is the
18+ "bandwidth", and ‖x‖ is the Euclidean norm (also called the 2-norm or the L²
19+ norm). The bandwidth τ controls how quickly the weight of a training sample
20+ decreases as its distance from the prediction point increases. One can think of
21+ the Gaussian weight as a bell curve centered around the prediction point: a
22+ training sample is weighted lower if it's farther from the center, and τ
23+ controls the spread of the bell curve.
24+
25+ Other types of locally weighted regression such as locally estimated scatterplot
26+ smoothing (LOESS) typically use different weight functions.
27+
28+ References:
29+ - https://en.wikipedia.org/wiki/Local_regression
30+ - https://en.wikipedia.org/wiki/Weighted_least_squares
31+ - https://cs229.stanford.edu/notes2022fall/main_notes.pdf
32+ """
33+
134import matplotlib .pyplot as plt
235import numpy as np
336
437
5- def weighted_matrix (
6- point : np .array , training_data_x : np .array , bandwidth : float
7- ) -> np .array :
38+ def weight_matrix (point : np .ndarray , x_train : np .ndarray , tau : float ) -> np .ndarray :
839 """
9- Calculate the weight for every point in the data set.
10- point --> the x value at which we want to make predictions
11- >>> weighted_matrix(
40+ Calculate the weight of every point in the training data around a given
41+ prediction point
42+
43+ Args:
44+ point: x-value at which the prediction is being made
45+ x_train: ndarray of x-values for training
46+ tau: bandwidth value, controls how quickly the weight of training values
47+ decreases as the distance from the prediction point increases
48+
49+ Returns:
50+ m x m weight matrix around the prediction point, where m is the size of
51+ the training set
52+ >>> weight_matrix(
1253 ... np.array([1., 1.]),
1354 ... np.array([[16.99, 10.34], [21.01,23.68], [24.59,25.69]]),
1455 ... 0.6
@@ -17,25 +58,30 @@ def weighted_matrix(
1758 [0.00000000e+000, 0.00000000e+000, 0.00000000e+000],
1859 [0.00000000e+000, 0.00000000e+000, 0.00000000e+000]])
1960 """
20- m , _ = np .shape (training_data_x ) # m is the number of training samples
21- weights = np .eye (m ) # Initializing weights as identity matrix
22-
23- # calculating weights for all training examples [x(i)'s]
61+ m = len (x_train ) # Number of training samples
62+ weights = np .eye (m ) # Initialize weights as identity matrix
2463 for j in range (m ):
25- diff = point - training_data_x [j ]
26- weights [j , j ] = np .exp (diff @ diff .T / (- 2.0 * bandwidth ** 2 ))
64+ diff = point - x_train [j ]
65+ weights [j , j ] = np .exp (diff @ diff .T / (- 2.0 * tau ** 2 ))
66+
2767 return weights
2868
2969
3070def local_weight (
31- point : np .array ,
32- training_data_x : np .array ,
33- training_data_y : np .array ,
34- bandwidth : float ,
35- ) -> np .array :
71+ point : np .ndarray , x_train : np .ndarray , y_train : np .ndarray , tau : float
72+ ) -> np .ndarray :
3673 """
37- Calculate the local weights using the weight_matrix function on training data.
38- Return the weighted matrix.
74+ Calculate the local weights at a given prediction point using the weight
75+ matrix for that point
76+
77+ Args:
78+ point: x-value at which the prediction is being made
79+ x_train: ndarray of x-values for training
80+ y_train: ndarray of y-values for training
81+ tau: bandwidth value, controls how quickly the weight of training values
82+ decreases as the distance from the prediction point increases
83+ Returns:
84+ ndarray of local weights
3985 >>> local_weight(
4086 ... np.array([1., 1.]),
4187 ... np.array([[16.99, 10.34], [21.01,23.68], [24.59,25.69]]),
@@ -45,97 +91,86 @@ def local_weight(
4591 array([[0.00873174],
4692 [0.08272556]])
4793 """
48- weight = weighted_matrix (point , training_data_x , bandwidth )
49- w = np .linalg .inv (training_data_x .T @ ( weight @ training_data_x ) ) @ (
50- training_data_x .T @ weight @ training_data_y .T
94+ weight_mat = weight_matrix (point , x_train , tau )
95+ weight = np .linalg .inv (x_train .T @ weight_mat @ x_train ) @ (
96+ x_train .T @ weight_mat @ y_train .T
5197 )
5298
53- return w
99+ return weight
54100
55101
56102def local_weight_regression (
57- training_data_x : np .array , training_data_y : np .array , bandwidth : float
58- ) -> np .array :
103+ x_train : np .ndarray , y_train : np .ndarray , tau : float
104+ ) -> np .ndarray :
59105 """
60- Calculate predictions for each data point on axis
106+ Calculate predictions for each point in the training data
107+
108+ Args:
109+ x_train: ndarray of x-values for training
110+ y_train: ndarray of y-values for training
111+ tau: bandwidth value, controls how quickly the weight of training values
112+ decreases as the distance from the prediction point increases
113+
114+ Returns:
115+ ndarray of predictions
61116 >>> local_weight_regression(
62117 ... np.array([[16.99, 10.34], [21.01, 23.68], [24.59, 25.69]]),
63118 ... np.array([[1.01, 1.66, 3.5]]),
64119 ... 0.6
65120 ... )
66121 array([1.07173261, 1.65970737, 3.50160179])
67122 """
68- m , _ = np .shape (training_data_x )
69- ypred = np .zeros (m )
123+ y_pred = np .zeros (len (x_train )) # Initialize array of predictions
124+ for i , item in enumerate (x_train ):
125+ y_pred [i ] = item @ local_weight (item , x_train , y_train , tau )
70126
71- for i , item in enumerate (training_data_x ):
72- ypred [i ] = item @ local_weight (
73- item , training_data_x , training_data_y , bandwidth
74- )
75-
76- return ypred
127+ return y_pred
77128
78129
79130def load_data (
80- dataset_name : str , cola_name : str , colb_name : str
81- ) -> tuple [np .array , np .array , np .array , np . array ]:
131+ dataset_name : str , x_name : str , y_name : str
132+ ) -> tuple [np .ndarray , np .ndarray , np .ndarray ]:
82133 """
83134 Load data from seaborn and split it into x and y points
135+ >>> pass # No doctests, function is for demo purposes only
84136 """
85137 import seaborn as sns
86138
87139 data = sns .load_dataset (dataset_name )
88- col_a = np .array (data [cola_name ]) # total_bill
89- col_b = np .array (data [colb_name ]) # tip
90-
91- mcol_a = col_a .copy ()
92- mcol_b = col_b .copy ()
93-
94- one = np .ones (np .shape (mcol_b )[0 ], dtype = int )
140+ x_data = np .array (data [x_name ])
141+ y_data = np .array (data [y_name ])
95142
96- # pairing elements of one and mcol_a
97- training_data_x = np .column_stack ((one , mcol_a ))
143+ one = np .ones (len (y_data ))
98144
99- return training_data_x , mcol_b , col_a , col_b
145+ # pairing elements of one and x_data
146+ x_train = np .column_stack ((one , x_data ))
100147
101-
102- def get_preds (training_data_x : np .array , mcol_b : np .array , tau : float ) -> np .array :
103- """
104- Get predictions with minimum error for each training data
105- >>> get_preds(
106- ... np.array([[16.99, 10.34], [21.01, 23.68], [24.59, 25.69]]),
107- ... np.array([[1.01, 1.66, 3.5]]),
108- ... 0.6
109- ... )
110- array([1.07173261, 1.65970737, 3.50160179])
111- """
112- ypred = local_weight_regression (training_data_x , mcol_b , tau )
113- return ypred
148+ return x_train , x_data , y_data
114149
115150
116151def plot_preds (
117- training_data_x : np .array ,
118- predictions : np .array ,
119- col_x : np .array ,
120- col_y : np .array ,
121- cola_name : str ,
122- colb_name : str ,
123- ) -> plt . plot :
152+ x_train : np .ndarray ,
153+ preds : np .ndarray ,
154+ x_data : np .ndarray ,
155+ y_data : np .ndarray ,
156+ x_name : str ,
157+ y_name : str ,
158+ ) -> None :
124159 """
125160 Plot predictions and display the graph
161+ >>> pass # No doctests, function is for demo purposes only
126162 """
127- xsort = training_data_x .copy ()
128- xsort .sort (axis = 0 )
129- plt .scatter (col_x , col_y , color = "blue" )
163+ x_train_sorted = np .sort (x_train , axis = 0 )
164+ plt .scatter (x_data , y_data , color = "blue" )
130165 plt .plot (
131- xsort [:, 1 ],
132- predictions [ training_data_x [:, 1 ].argsort (0 )],
166+ x_train_sorted [:, 1 ],
167+ preds [ x_train [:, 1 ].argsort (0 )],
133168 color = "yellow" ,
134169 linewidth = 5 ,
135170 )
136171 plt .title ("Local Weighted Regression" )
137- plt .xlabel (cola_name )
138- plt .ylabel (colb_name )
172+ plt .xlabel (x_name )
173+ plt .ylabel (y_name )
139174 plt .show ()
140175
141176
@@ -144,6 +179,7 @@ def plot_preds(
144179
145180 doctest .testmod ()
146181
147- training_data_x , mcol_b , col_a , col_b = load_data ("tips" , "total_bill" , "tip" )
148- predictions = get_preds (training_data_x , mcol_b , 0.5 )
149- plot_preds (training_data_x , predictions , col_a , col_b , "total_bill" , "tip" )
182+ # Demo with a dataset from the seaborn module
183+ training_data_x , total_bill , tip = load_data ("tips" , "total_bill" , "tip" )
184+ predictions = local_weight_regression (training_data_x , tip , 5 )
185+ plot_preds (training_data_x , predictions , total_bill , tip , "total_bill" , "tip" )
0 commit comments