(
X: Sequence[Sequence[float]],
variance_threshold: float = 0.95,
)
| 490 | |
| 491 | |
| 492 | def _norm(v: Sequence[float]) -> float: |
| 493 | return sqrt(sum(x * x for x in v)) |
| 494 | |
| 495 | |
| 496 | def _power_iteration(a: list[list[float]], iters: int = 200) -> tuple[float, list[float]]: |
| 497 | n = len(a) |
| 498 | v = [1.0 / sqrt(n)] * n |
| 499 | for _ in range(iters): |
| 500 | av = _mat_vec(a, v) |
| 501 | nrm = _norm(av) |
| 502 | if nrm <= _EPS: |
| 503 | break |
| 504 | v = [x / nrm for x in av] |
| 505 | av = _mat_vec(a, v) |
| 506 | eig = _dot(v, av) |
| 507 | return eig, v |
| 508 | |
| 509 | |
| 510 | def orthogonalize_features_pca( |
| 511 | X: Sequence[Sequence[float]], |
| 512 | variance_threshold: float = 0.95, |
| 513 | ) -> dict[str, object]: |
| 514 | if variance_threshold <= 0.0 or variance_threshold > 1.0: |
| 515 | raise ValueError("variance_threshold must be in (0, 1]") |
| 516 | |
| 517 | x = _as_matrix(X) |
| 518 | z, means, stds = _standardize(x) |
| 519 | n = len(z) |
| 520 | p = len(z[0]) |
| 521 | |
| 522 | cov = [[0.0 for _ in range(p)] for _ in range(p)] |
| 523 | for i in range(p): |
| 524 | for j in range(p): |
| 525 | cov[i][j] = sum(row[i] * row[j] for row in z) / max(n - 1, 1) |
| 526 | |
| 527 | work = [row[:] for row in cov] |
| 528 | eigvals: list[float] = [] |
| 529 | eigvecs: list[list[float]] = [] |
| 530 | |
| 531 | for _ in range(p): |
| 532 | eig, vec = _power_iteration(work) |
| 533 | if eig <= _EPS: |
| 534 | break |
| 535 | eigvals.append(eig) |
| 536 | eigvecs.append(vec) |
| 537 | # deflation: A = A - lambda * v v^T |
| 538 | for i in range(p): |
| 539 | for j in range(p): |
| 540 | work[i][j] -= eig * vec[i] * vec[j] |
| 541 | |
| 542 | total = sum(max(v, 0.0) for v in eigvals) |
| 543 | if total <= _EPS: |
| 544 | raise ValueError("PCA failed: zero variance matrix") |
| 545 | |
| 546 | kept = 0 |
| 547 | running = 0.0 |
| 548 | explained_ratio: list[float] = [] |
| 549 | for eig in eigvals: |
no test coverage detected