forked from keon/algorithms
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlinear_regression.py
More file actions
60 lines (49 loc) · 1.74 KB
/
linear_regression.py
File metadata and controls
60 lines (49 loc) · 1.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
"""Simple linear regression — fit a line to (x, y) data.
Computes the ordinary least-squares regression line y = mx + b
without external libraries.
Inspired by PR #871 (MakanFar).
"""
from __future__ import annotations
import math
def linear_regression(x: list[float], y: list[float]) -> tuple[float, float]:
"""Return (slope, intercept) for the best-fit line.
>>> m, b = linear_regression([1, 2, 3, 4, 5], [2, 4, 5, 4, 5])
>>> round(m, 4)
0.6
>>> round(b, 4)
2.2
"""
n = len(x)
if n != len(y) or n < 2:
msg = "x and y must have at least 2 equal-length elements"
raise ValueError(msg)
sum_x = sum(x)
sum_y = sum(y)
sum_xy = sum(xi * yi for xi, yi in zip(x, y, strict=False))
sum_x2 = sum(xi * xi for xi in x)
denom = n * sum_x2 - sum_x * sum_x
if denom == 0:
msg = "Vertical line — slope is undefined"
raise ValueError(msg)
slope = (n * sum_xy - sum_x * sum_y) / denom
intercept = (sum_y - slope * sum_x) / n
return slope, intercept
def r_squared(x: list[float], y: list[float]) -> float:
"""Return the R-squared (coefficient of determination) for the fit."""
slope, intercept = linear_regression(x, y)
y_mean = sum(y) / len(y)
ss_tot = sum((yi - y_mean) ** 2 for yi in y)
ss_res = sum(
(yi - (slope * xi + intercept)) ** 2 for xi, yi in zip(x, y, strict=False)
)
if ss_tot == 0:
return 1.0
return 1.0 - ss_res / ss_tot
def rmse(x: list[float], y: list[float]) -> float:
"""Return the root mean squared error for the fit."""
slope, intercept = linear_regression(x, y)
n = len(y)
return math.sqrt(
sum((yi - (slope * xi + intercept)) ** 2 for xi, yi in zip(x, y, strict=False))
/ n
)