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
+
1
34
import matplotlib .pyplot as plt
2
35
import numpy as np
3
36
4
37
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 :
8
39
"""
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(
12
53
... np.array([1., 1.]),
13
54
... np.array([[16.99, 10.34], [21.01,23.68], [24.59,25.69]]),
14
55
... 0.6
@@ -17,25 +58,30 @@ def weighted_matrix(
17
58
[0.00000000e+000, 0.00000000e+000, 0.00000000e+000],
18
59
[0.00000000e+000, 0.00000000e+000, 0.00000000e+000]])
19
60
"""
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
24
63
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
+
27
67
return weights
28
68
29
69
30
70
def 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 :
36
73
"""
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
39
85
>>> local_weight(
40
86
... np.array([1., 1.]),
41
87
... np.array([[16.99, 10.34], [21.01,23.68], [24.59,25.69]]),
@@ -45,97 +91,86 @@ def local_weight(
45
91
array([[0.00873174],
46
92
[0.08272556]])
47
93
"""
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
51
97
)
52
98
53
- return w
99
+ return weight
54
100
55
101
56
102
def 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 :
59
105
"""
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
61
116
>>> local_weight_regression(
62
117
... np.array([[16.99, 10.34], [21.01, 23.68], [24.59, 25.69]]),
63
118
... np.array([[1.01, 1.66, 3.5]]),
64
119
... 0.6
65
120
... )
66
121
array([1.07173261, 1.65970737, 3.50160179])
67
122
"""
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 )
70
126
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
77
128
78
129
79
130
def 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 ]:
82
133
"""
83
134
Load data from seaborn and split it into x and y points
135
+ >>> pass # No doctests, function is for demo purposes only
84
136
"""
85
137
import seaborn as sns
86
138
87
139
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 ])
95
142
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 ))
98
144
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 ))
100
147
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
114
149
115
150
116
151
def 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 :
124
159
"""
125
160
Plot predictions and display the graph
161
+ >>> pass # No doctests, function is for demo purposes only
126
162
"""
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" )
130
165
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 )],
133
168
color = "yellow" ,
134
169
linewidth = 5 ,
135
170
)
136
171
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 )
139
174
plt .show ()
140
175
141
176
@@ -144,6 +179,7 @@ def plot_preds(
144
179
145
180
doctest .testmod ()
146
181
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