matrix-hat 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- matrix_hat/__init__.py +29 -0
- matrix_hat/_validation.py +34 -0
- matrix_hat/core.py +354 -0
- matrix_hat/py.typed +0 -0
- matrix_hat-0.1.0.dist-info/METADATA +137 -0
- matrix_hat-0.1.0.dist-info/RECORD +8 -0
- matrix_hat-0.1.0.dist-info/WHEEL +4 -0
- matrix_hat-0.1.0.dist-info/licenses/LICENSE +21 -0
matrix_hat/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""matrix-hat: hat matrix and leverage diagnostics for linear regression.
|
|
2
|
+
|
|
3
|
+
Quick start
|
|
4
|
+
-----------
|
|
5
|
+
>>> import numpy as np
|
|
6
|
+
>>> from matrix_hat import leverage, hat_matrix
|
|
7
|
+
>>> X = np.array([[1.0], [2.0], [3.0], [10.0]])
|
|
8
|
+
>>> leverage(X, add_intercept=True).round(2)
|
|
9
|
+
array([0.43, 0.33, 0.27, 0.97])
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .core import (
|
|
13
|
+
effective_rank,
|
|
14
|
+
hat_matrix,
|
|
15
|
+
high_leverage_points,
|
|
16
|
+
leverage,
|
|
17
|
+
orthonormal_basis,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__version__ = "0.1.0"
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"hat_matrix",
|
|
24
|
+
"leverage",
|
|
25
|
+
"high_leverage_points",
|
|
26
|
+
"orthonormal_basis",
|
|
27
|
+
"effective_rank",
|
|
28
|
+
"__version__",
|
|
29
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Private input validation and design-matrix helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numpy.typing import ArrayLike, NDArray
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _as_2d(X: ArrayLike) -> NDArray[np.floating]:
|
|
10
|
+
"""Return ``X`` as a 2-D float array, promoting 1-D input to a column."""
|
|
11
|
+
arr = np.asarray(X, dtype=float)
|
|
12
|
+
if arr.ndim == 1:
|
|
13
|
+
arr = arr.reshape(-1, 1)
|
|
14
|
+
if arr.ndim != 2:
|
|
15
|
+
raise ValueError(f"X must be 1- or 2-dimensional, got {arr.ndim} dimensions")
|
|
16
|
+
if arr.size == 0:
|
|
17
|
+
raise ValueError("X must not be empty")
|
|
18
|
+
return arr
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _design(X: ArrayLike, add_intercept: bool) -> NDArray[np.floating]:
|
|
22
|
+
"""Build the design matrix, optionally prepending an intercept column."""
|
|
23
|
+
arr = _as_2d(X)
|
|
24
|
+
if add_intercept:
|
|
25
|
+
intercept = np.ones((arr.shape[0], 1), dtype=float)
|
|
26
|
+
arr = np.hstack([intercept, arr])
|
|
27
|
+
return arr
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _default_tol(s: NDArray[np.floating], shape: tuple[int, int]) -> float:
|
|
31
|
+
"""Default singular-value tolerance, matching ``numpy.linalg.matrix_rank``."""
|
|
32
|
+
if s.size == 0:
|
|
33
|
+
return 0.0
|
|
34
|
+
return float(s.max()) * max(shape) * np.finfo(float).eps
|
matrix_hat/core.py
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"""Core hat matrix and leverage computations.
|
|
2
|
+
|
|
3
|
+
The *hat matrix* of a design matrix ``X`` is the orthogonal projection onto
|
|
4
|
+
the column space of ``X``:
|
|
5
|
+
|
|
6
|
+
.. math::
|
|
7
|
+
|
|
8
|
+
H = X (X^{\\top} X)^{-1} X^{\\top}
|
|
9
|
+
|
|
10
|
+
It is called the *hat* matrix because it maps the observed response ``y`` onto
|
|
11
|
+
its fitted values (``y-hat``): :math:`\\hat{y} = H y`. The diagonal entries
|
|
12
|
+
:math:`h_{ii}` are the *leverages*, a standard regression diagnostic measuring
|
|
13
|
+
how far the ``i``-th observation lies from the centre of the predictor space.
|
|
14
|
+
|
|
15
|
+
Computations here are based on a rank-revealing SVD rather than an explicit
|
|
16
|
+
matrix inverse, which is numerically stable and gracefully handles
|
|
17
|
+
rank-deficient design matrices.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
from numpy.typing import ArrayLike, NDArray
|
|
24
|
+
|
|
25
|
+
from ._validation import _default_tol, _design
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"orthonormal_basis",
|
|
29
|
+
"hat_matrix",
|
|
30
|
+
"leverage",
|
|
31
|
+
"high_leverage_points",
|
|
32
|
+
"effective_rank",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def orthonormal_basis(
|
|
37
|
+
X: ArrayLike,
|
|
38
|
+
add_intercept: bool = False,
|
|
39
|
+
tol: float | None = None,
|
|
40
|
+
) -> NDArray[np.floating]:
|
|
41
|
+
"""Return an orthonormal basis ``Q`` for the column space of ``X``.
|
|
42
|
+
|
|
43
|
+
The basis is obtained from the SVD and spans the same subspace as the
|
|
44
|
+
(possibly rank-deficient) columns of the design matrix. The hat matrix
|
|
45
|
+
satisfies :math:`H = Q Q^{\\top}`.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
X : array_like
|
|
50
|
+
Design matrix of shape ``(n_samples, n_features)``. A 1-D array is
|
|
51
|
+
treated as a single column.
|
|
52
|
+
add_intercept : bool, default False
|
|
53
|
+
If True, prepend a column of ones to ``X``.
|
|
54
|
+
tol : float, optional
|
|
55
|
+
Singular values below ``tol`` are treated as zero when determining the
|
|
56
|
+
rank. Defaults to a value scaled by the largest singular value and the
|
|
57
|
+
matrix size (same rule as :func:`numpy.linalg.matrix_rank`).
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
ndarray
|
|
62
|
+
Array of shape ``(n_samples, rank)`` with orthonormal columns.
|
|
63
|
+
|
|
64
|
+
Raises
|
|
65
|
+
------
|
|
66
|
+
ValueError
|
|
67
|
+
If ``X`` is empty or has more than two dimensions.
|
|
68
|
+
|
|
69
|
+
See Also
|
|
70
|
+
--------
|
|
71
|
+
hat_matrix : Full projection matrix ``H = Q @ Q.T``.
|
|
72
|
+
leverage : Diagonal of ``H`` without forming the full matrix.
|
|
73
|
+
effective_rank : Numerical rank equal to ``Q.shape[1]``.
|
|
74
|
+
|
|
75
|
+
Notes
|
|
76
|
+
-----
|
|
77
|
+
Using a truncated SVD avoids forming :math:`(X^{\\top} X)^{-1}` and remains
|
|
78
|
+
well-behaved for ill-conditioned or rank-deficient designs.
|
|
79
|
+
|
|
80
|
+
References
|
|
81
|
+
----------
|
|
82
|
+
.. [1] Hoaglin, D. C. and Welsch, R. E. (1978). "The Hat Matrix in
|
|
83
|
+
Regression and ANOVA". *The American Statistician*, 32(1), 17--22.
|
|
84
|
+
|
|
85
|
+
Examples
|
|
86
|
+
--------
|
|
87
|
+
>>> import numpy as np
|
|
88
|
+
>>> from matrix_hat import orthonormal_basis
|
|
89
|
+
>>> X = np.array([[1.0], [2.0], [3.0]])
|
|
90
|
+
>>> Q = orthonormal_basis(X, add_intercept=True)
|
|
91
|
+
>>> Q.shape
|
|
92
|
+
(3, 2)
|
|
93
|
+
>>> np.allclose(Q.T @ Q, np.eye(Q.shape[1]))
|
|
94
|
+
True
|
|
95
|
+
"""
|
|
96
|
+
design = _design(X, add_intercept)
|
|
97
|
+
U, s, _ = np.linalg.svd(design, full_matrices=False)
|
|
98
|
+
if tol is None:
|
|
99
|
+
tol = _default_tol(s, design.shape)
|
|
100
|
+
rank = int(np.sum(s > tol))
|
|
101
|
+
return U[:, :rank]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def effective_rank(
|
|
105
|
+
X: ArrayLike,
|
|
106
|
+
add_intercept: bool = False,
|
|
107
|
+
tol: float | None = None,
|
|
108
|
+
) -> int:
|
|
109
|
+
"""Return the numerical rank of the (optionally augmented) design matrix.
|
|
110
|
+
|
|
111
|
+
Equivalent to the number of columns of :func:`orthonormal_basis`, i.e. the
|
|
112
|
+
number of singular values strictly larger than ``tol``.
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
X : array_like
|
|
117
|
+
Design matrix of shape ``(n_samples, n_features)``. A 1-D array is
|
|
118
|
+
treated as a single column.
|
|
119
|
+
add_intercept : bool, default False
|
|
120
|
+
If True, prepend a column of ones to ``X`` before ranking.
|
|
121
|
+
tol : float, optional
|
|
122
|
+
Singular-value cut-off passed to :func:`orthonormal_basis`.
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
int
|
|
127
|
+
Numerical rank of the design matrix.
|
|
128
|
+
|
|
129
|
+
Raises
|
|
130
|
+
------
|
|
131
|
+
ValueError
|
|
132
|
+
If ``X`` is empty or has more than two dimensions.
|
|
133
|
+
|
|
134
|
+
See Also
|
|
135
|
+
--------
|
|
136
|
+
orthonormal_basis : Orthonormal basis whose width equals the rank.
|
|
137
|
+
leverage : Leverages sum to this rank.
|
|
138
|
+
|
|
139
|
+
Notes
|
|
140
|
+
-----
|
|
141
|
+
For a full-rank design with an intercept, the rank is
|
|
142
|
+
``n_features + 1``.
|
|
143
|
+
|
|
144
|
+
References
|
|
145
|
+
----------
|
|
146
|
+
.. [1] Hoaglin, D. C. and Welsch, R. E. (1978). "The Hat Matrix in
|
|
147
|
+
Regression and ANOVA". *The American Statistician*, 32(1), 17--22.
|
|
148
|
+
|
|
149
|
+
Examples
|
|
150
|
+
--------
|
|
151
|
+
>>> import numpy as np
|
|
152
|
+
>>> from matrix_hat import effective_rank
|
|
153
|
+
>>> X = np.array([[1.0, 2.0], [2.0, 4.0], [3.0, 6.0]]) # rank 1
|
|
154
|
+
>>> effective_rank(X)
|
|
155
|
+
1
|
|
156
|
+
>>> effective_rank(X, add_intercept=True)
|
|
157
|
+
2
|
|
158
|
+
"""
|
|
159
|
+
return orthonormal_basis(X, add_intercept=add_intercept, tol=tol).shape[1]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def hat_matrix(
|
|
163
|
+
X: ArrayLike,
|
|
164
|
+
add_intercept: bool = False,
|
|
165
|
+
tol: float | None = None,
|
|
166
|
+
) -> NDArray[np.floating]:
|
|
167
|
+
"""Compute the full hat (projection) matrix ``H``.
|
|
168
|
+
|
|
169
|
+
Parameters
|
|
170
|
+
----------
|
|
171
|
+
X : array_like
|
|
172
|
+
Design matrix of shape ``(n_samples, n_features)``. A 1-D array is
|
|
173
|
+
treated as a single column.
|
|
174
|
+
add_intercept : bool, default False
|
|
175
|
+
If True, prepend a column of ones before projecting.
|
|
176
|
+
tol : float, optional
|
|
177
|
+
Rank tolerance passed to :func:`orthonormal_basis`.
|
|
178
|
+
|
|
179
|
+
Returns
|
|
180
|
+
-------
|
|
181
|
+
ndarray
|
|
182
|
+
Symmetric idempotent matrix of shape ``(n_samples, n_samples)``.
|
|
183
|
+
|
|
184
|
+
Raises
|
|
185
|
+
------
|
|
186
|
+
ValueError
|
|
187
|
+
If ``X`` is empty or has more than two dimensions.
|
|
188
|
+
|
|
189
|
+
See Also
|
|
190
|
+
--------
|
|
191
|
+
leverage : Prefer this for large ``n``; returns only the diagonal.
|
|
192
|
+
orthonormal_basis : Factors ``H`` as ``Q @ Q.T``.
|
|
193
|
+
high_leverage_points : Indices with large diagonal entries.
|
|
194
|
+
|
|
195
|
+
Notes
|
|
196
|
+
-----
|
|
197
|
+
The full hat matrix is ``n_samples`` by ``n_samples``. For large samples
|
|
198
|
+
prefer :func:`leverage`, which returns only the diagonal without forming
|
|
199
|
+
the full matrix. Algebraically,
|
|
200
|
+
:math:`H` is the orthogonal projector onto the column space of ``X``, so
|
|
201
|
+
:math:`H = H^{\\top} = H^{2}`.
|
|
202
|
+
|
|
203
|
+
References
|
|
204
|
+
----------
|
|
205
|
+
.. [1] Hoaglin, D. C. and Welsch, R. E. (1978). "The Hat Matrix in
|
|
206
|
+
Regression and ANOVA". *The American Statistician*, 32(1), 17--22.
|
|
207
|
+
|
|
208
|
+
Examples
|
|
209
|
+
--------
|
|
210
|
+
>>> import numpy as np
|
|
211
|
+
>>> from matrix_hat import hat_matrix
|
|
212
|
+
>>> X = np.array([[1.0], [2.0], [3.0]])
|
|
213
|
+
>>> H = hat_matrix(X, add_intercept=True)
|
|
214
|
+
>>> H.shape
|
|
215
|
+
(3, 3)
|
|
216
|
+
>>> np.allclose(H, H.T) and np.allclose(H @ H, H)
|
|
217
|
+
True
|
|
218
|
+
"""
|
|
219
|
+
Q = orthonormal_basis(X, add_intercept=add_intercept, tol=tol)
|
|
220
|
+
return Q @ Q.T
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def leverage(
|
|
224
|
+
X: ArrayLike,
|
|
225
|
+
add_intercept: bool = False,
|
|
226
|
+
tol: float | None = None,
|
|
227
|
+
) -> NDArray[np.floating]:
|
|
228
|
+
"""Compute the leverages ``h_ii`` (diagonal of the hat matrix).
|
|
229
|
+
|
|
230
|
+
Computed as the row-wise sum of squares of an orthonormal basis for the
|
|
231
|
+
column space, avoiding formation of the full ``n_samples`` square hat
|
|
232
|
+
matrix.
|
|
233
|
+
|
|
234
|
+
Parameters
|
|
235
|
+
----------
|
|
236
|
+
X : array_like
|
|
237
|
+
Design matrix of shape ``(n_samples, n_features)``. A 1-D array is
|
|
238
|
+
treated as a single column.
|
|
239
|
+
add_intercept : bool, default False
|
|
240
|
+
If True, prepend a column of ones before projecting.
|
|
241
|
+
tol : float, optional
|
|
242
|
+
Rank tolerance passed to :func:`orthonormal_basis`.
|
|
243
|
+
|
|
244
|
+
Returns
|
|
245
|
+
-------
|
|
246
|
+
ndarray
|
|
247
|
+
Vector of length ``n_samples`` with values in ``[0, 1]`` that sum to
|
|
248
|
+
the rank of the design matrix.
|
|
249
|
+
|
|
250
|
+
Raises
|
|
251
|
+
------
|
|
252
|
+
ValueError
|
|
253
|
+
If ``X`` is empty or has more than two dimensions.
|
|
254
|
+
|
|
255
|
+
See Also
|
|
256
|
+
--------
|
|
257
|
+
hat_matrix : Full projection matrix whose diagonal is returned here.
|
|
258
|
+
high_leverage_points : Flag observations above a leverage cut-off.
|
|
259
|
+
effective_rank : Equal to ``leverage(...).sum()`` (within rounding).
|
|
260
|
+
|
|
261
|
+
Notes
|
|
262
|
+
-----
|
|
263
|
+
Leverages satisfy :math:`0 \\le h_{ii} \\le 1` and
|
|
264
|
+
:math:`\\sum_i h_{ii} = \\mathrm{rank}(X)`. Their average is therefore
|
|
265
|
+
:math:`p / n`. Memory use is ``O(n p)`` rather than ``O(n^2)``.
|
|
266
|
+
|
|
267
|
+
References
|
|
268
|
+
----------
|
|
269
|
+
.. [1] Hoaglin, D. C. and Welsch, R. E. (1978). "The Hat Matrix in
|
|
270
|
+
Regression and ANOVA". *The American Statistician*, 32(1), 17--22.
|
|
271
|
+
.. [2] Belsley, D. A., Kuh, E. and Welsch, R. E. (1980). *Regression
|
|
272
|
+
Diagnostics*. Wiley.
|
|
273
|
+
|
|
274
|
+
Examples
|
|
275
|
+
--------
|
|
276
|
+
>>> import numpy as np
|
|
277
|
+
>>> from matrix_hat import leverage
|
|
278
|
+
>>> X = np.array([[1.0], [2.0], [3.0], [10.0]])
|
|
279
|
+
>>> leverage(X, add_intercept=True).round(2)
|
|
280
|
+
array([0.43, 0.33, 0.27, 0.97])
|
|
281
|
+
"""
|
|
282
|
+
Q = orthonormal_basis(X, add_intercept=add_intercept, tol=tol)
|
|
283
|
+
return np.einsum("ij,ij->i", Q, Q)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def high_leverage_points(
|
|
287
|
+
X: ArrayLike,
|
|
288
|
+
add_intercept: bool = False,
|
|
289
|
+
threshold: float | None = None,
|
|
290
|
+
factor: float = 2.0,
|
|
291
|
+
tol: float | None = None,
|
|
292
|
+
) -> NDArray[np.integer]:
|
|
293
|
+
"""Return indices of observations whose leverage exceeds a threshold.
|
|
294
|
+
|
|
295
|
+
Parameters
|
|
296
|
+
----------
|
|
297
|
+
X : array_like
|
|
298
|
+
Design matrix of shape ``(n_samples, n_features)``. A 1-D array is
|
|
299
|
+
treated as a single column.
|
|
300
|
+
add_intercept : bool, default False
|
|
301
|
+
If True, prepend a column of ones before projecting.
|
|
302
|
+
threshold : float, optional
|
|
303
|
+
Absolute leverage cut-off. If omitted, a rule-of-thumb threshold of
|
|
304
|
+
``factor * p / n`` is used, where ``p`` is the rank (mean leverage is
|
|
305
|
+
``p / n``) and ``n`` is the number of observations.
|
|
306
|
+
factor : float, default 2.0
|
|
307
|
+
Multiplier for the automatic ``factor * p / n`` threshold. Common
|
|
308
|
+
choices are 2 or 3. Ignored when ``threshold`` is given.
|
|
309
|
+
tol : float, optional
|
|
310
|
+
Rank tolerance passed to :func:`orthonormal_basis`.
|
|
311
|
+
|
|
312
|
+
Returns
|
|
313
|
+
-------
|
|
314
|
+
ndarray
|
|
315
|
+
Integer indices of the flagged observations, in ascending order.
|
|
316
|
+
|
|
317
|
+
Raises
|
|
318
|
+
------
|
|
319
|
+
ValueError
|
|
320
|
+
If ``X`` is empty or has more than two dimensions.
|
|
321
|
+
|
|
322
|
+
See Also
|
|
323
|
+
--------
|
|
324
|
+
leverage : Leverage values compared against the threshold.
|
|
325
|
+
hat_matrix : Full projection matrix (prefer :func:`leverage` for large ``n``).
|
|
326
|
+
|
|
327
|
+
Notes
|
|
328
|
+
-----
|
|
329
|
+
A common rule of thumb flags observations with
|
|
330
|
+
:math:`h_{ii} > 2p/n` (or :math:`3p/n`) as high leverage [2]_. High
|
|
331
|
+
leverage indicates unusual predictor values, not necessarily an outlier in
|
|
332
|
+
the response.
|
|
333
|
+
|
|
334
|
+
References
|
|
335
|
+
----------
|
|
336
|
+
.. [1] Hoaglin, D. C. and Welsch, R. E. (1978). "The Hat Matrix in
|
|
337
|
+
Regression and ANOVA". *The American Statistician*, 32(1), 17--22.
|
|
338
|
+
.. [2] Belsley, D. A., Kuh, E. and Welsch, R. E. (1980). *Regression
|
|
339
|
+
Diagnostics*. Wiley.
|
|
340
|
+
|
|
341
|
+
Examples
|
|
342
|
+
--------
|
|
343
|
+
>>> import numpy as np
|
|
344
|
+
>>> from matrix_hat import high_leverage_points
|
|
345
|
+
>>> X = np.array([[1.0], [2.0], [3.0], [4.0], [50.0]])
|
|
346
|
+
>>> high_leverage_points(X, add_intercept=True)
|
|
347
|
+
array([4])
|
|
348
|
+
"""
|
|
349
|
+
h = leverage(X, add_intercept=add_intercept, tol=tol)
|
|
350
|
+
if threshold is None:
|
|
351
|
+
n = h.shape[0]
|
|
352
|
+
p = float(np.sum(h)) # rank == trace of the hat matrix
|
|
353
|
+
threshold = factor * p / n
|
|
354
|
+
return np.flatnonzero(h > threshold)
|
matrix_hat/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: matrix-hat
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Hat matrix and leverage diagnostics for linear regression.
|
|
5
|
+
Project-URL: Homepage, https://github.com/aokienz/matrix-hat
|
|
6
|
+
Project-URL: Repository, https://github.com/aokienz/matrix-hat
|
|
7
|
+
Project-URL: Issues, https://github.com/aokienz/matrix-hat/issues
|
|
8
|
+
Author-email: Enzo Aoki <enzoaoki02@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: diagnostics,hat-matrix,influence,leverage,outliers,regression,statistics
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Requires-Dist: numpy>=1.21
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: numpydoc>=1.6; extra == 'dev'
|
|
27
|
+
Requires-Dist: pydata-sphinx-theme>=0.15; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: sphinx>=7.0; extra == 'dev'
|
|
30
|
+
Provides-Extra: docs
|
|
31
|
+
Requires-Dist: numpydoc>=1.6; extra == 'docs'
|
|
32
|
+
Requires-Dist: pydata-sphinx-theme>=0.15; extra == 'docs'
|
|
33
|
+
Requires-Dist: sphinx>=7.0; extra == 'docs'
|
|
34
|
+
Provides-Extra: test
|
|
35
|
+
Requires-Dist: pytest>=7.0; extra == 'test'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# matrix-hat
|
|
39
|
+
|
|
40
|
+
[](https://github.com/aokienz/matrix-hat/actions/workflows/ci.yml)
|
|
41
|
+
[](https://pypi.org/project/matrix-hat/)
|
|
42
|
+
[](https://pypi.org/project/matrix-hat/)
|
|
43
|
+
[](LICENSE)
|
|
44
|
+
|
|
45
|
+
Small, dependency-light toolkit for the **hat matrix** and **leverage**
|
|
46
|
+
diagnostics used in linear regression.
|
|
47
|
+
|
|
48
|
+
The hat matrix of a design matrix `X` is the orthogonal projection onto its
|
|
49
|
+
column space:
|
|
50
|
+
|
|
51
|
+
$$H = X (X^\top X)^{-1} X^\top$$
|
|
52
|
+
|
|
53
|
+
It earns its name because it turns observed responses into fitted values
|
|
54
|
+
("y-hat"): $\hat{y} = Hy$. Its diagonal entries $h_{ii}$ are the **leverages**,
|
|
55
|
+
which measure how far each observation sits from the centre of the predictor
|
|
56
|
+
space — a key tool for spotting influential points and outliers.
|
|
57
|
+
|
|
58
|
+
**Full documentation** (user guide, math background, API reference) lives in
|
|
59
|
+
[`docs/`](docs/). Build locally with `pip install -e ".[docs]"` then
|
|
60
|
+
`sphinx-build -W -b html docs docs/_build/html`.
|
|
61
|
+
|
|
62
|
+
## Why this package
|
|
63
|
+
|
|
64
|
+
- **Numerically stable.** Rank-revealing SVD instead of an explicit
|
|
65
|
+
`(XᵀX)⁻¹`, so it stays well-behaved on ill-conditioned or rank-deficient
|
|
66
|
+
designs.
|
|
67
|
+
- **Memory-aware.** `leverage()` returns just the diagonal without ever forming
|
|
68
|
+
the full `n × n` hat matrix.
|
|
69
|
+
- **Tiny footprint.** NumPy is the only runtime dependency.
|
|
70
|
+
|
|
71
|
+
## Installation
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install matrix-hat
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
From source (tests + docs):
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pip install -e ".[dev]"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Quickstart
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import numpy as np
|
|
87
|
+
from matrix_hat import leverage, hat_matrix, high_leverage_points
|
|
88
|
+
|
|
89
|
+
X = np.array([[1.0], [2.0], [3.0], [4.0], [50.0]])
|
|
90
|
+
|
|
91
|
+
# Leverages (diagonal of the hat matrix), with an intercept column added.
|
|
92
|
+
h = leverage(X, add_intercept=True)
|
|
93
|
+
# array([0.267, 0.255, 0.245, 0.235, 0.998]) approx.
|
|
94
|
+
|
|
95
|
+
# Flag high-leverage observations (default rule of thumb: 2 * p / n).
|
|
96
|
+
high_leverage_points(X, add_intercept=True)
|
|
97
|
+
# array([4])
|
|
98
|
+
|
|
99
|
+
# The full projection matrix, when you actually need it.
|
|
100
|
+
H = hat_matrix(X, add_intercept=True)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## API
|
|
104
|
+
|
|
105
|
+
| Function | Description |
|
|
106
|
+
| --- | --- |
|
|
107
|
+
| `leverage(X, add_intercept=False)` | Leverages `h_ii` (diagonal of `H`) as a length-`n` vector. |
|
|
108
|
+
| `hat_matrix(X, add_intercept=False)` | The full `n × n` projection matrix `H`. |
|
|
109
|
+
| `high_leverage_points(X, threshold=None, factor=2.0)` | Indices of observations above a leverage cut-off. |
|
|
110
|
+
| `orthonormal_basis(X, add_intercept=False)` | Orthonormal basis `Q` for the column space (`H = Q Qᵀ`). |
|
|
111
|
+
| `effective_rank(X, add_intercept=False)` | Numerical rank of the design matrix. |
|
|
112
|
+
|
|
113
|
+
All functions accept a `tol` argument to control the singular-value cut-off
|
|
114
|
+
used for rank determination. See the Sphinx docs for full numpydoc reference
|
|
115
|
+
pages.
|
|
116
|
+
|
|
117
|
+
## Development
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pip install -e ".[dev]"
|
|
121
|
+
pytest
|
|
122
|
+
sphinx-build -W -b html docs docs/_build/html
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
126
|
+
|
|
127
|
+
## Citation
|
|
128
|
+
|
|
129
|
+
If you use this package in research, see [CITATION.cff](CITATION.cff).
|
|
130
|
+
|
|
131
|
+
## Changelog
|
|
132
|
+
|
|
133
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
matrix_hat/__init__.py,sha256=U47RPmJ1B9fs65p_1DL6pCq1hrBVzYdnR2iRFnUZ-2Y,587
|
|
2
|
+
matrix_hat/_validation.py,sha256=geA4U5oI0-9d4wLVzw64adWx0IC4Dj_TskxdPc0EERA,1145
|
|
3
|
+
matrix_hat/core.py,sha256=X_6b1AUOX7CuU5AXvEMdwebawVLnWX7Eh8GKAKCg-0E,11038
|
|
4
|
+
matrix_hat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
matrix_hat-0.1.0.dist-info/METADATA,sha256=we31r6XpmNNebQkmKqFuKWDG75wIEK-rPfhnyGVry8Q,4795
|
|
6
|
+
matrix_hat-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
7
|
+
matrix_hat-0.1.0.dist-info/licenses/LICENSE,sha256=yxcYk9fBYppe_dZZt15gsLiMTKRJiUbx0kSKoL3yoQw,1061
|
|
8
|
+
matrix_hat-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Enzo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|