|
1 | 1 | from AnyQt.QtCore import Qt |
| 2 | +import numpy as np |
| 3 | +import scipy.stats as ss |
2 | 4 | import scipy.sparse as sp |
3 | 5 |
|
4 | | -from Orange.data import Table, Domain |
| 6 | +from Orange.data import Table, Domain, DiscreteVariable |
| 7 | +from Orange.data.util import get_unique_names |
5 | 8 | from Orange.regression import PLSRegressionLearner |
6 | 9 | from Orange.widgets import gui |
7 | 10 | from Orange.widgets.settings import Setting |
@@ -62,11 +65,32 @@ def update_model(self): |
62 | 65 |
|
63 | 66 | def _create_output_data(self) -> Table: |
64 | 67 | projection = self.model.project(self.data) |
| 68 | + inside = np.full(len(self.data), np.nan) |
| 69 | + if self.n_components > 1: |
| 70 | + inside = self._inside_ellipsis(projection.X[:, :2]) |
65 | 71 | data_domain = self.data.domain |
66 | 72 | proj_domain = projection.domain |
67 | 73 | metas = proj_domain.metas + proj_domain.attributes |
68 | 74 | domain = Domain(data_domain.attributes, data_domain.class_vars, metas) |
69 | | - return self.data.transform(domain) |
| 75 | + data: Table = self.data.transform(domain) |
| 76 | + inside_name = get_unique_names(domain, "Inside Ellipsis") |
| 77 | + inside_var = DiscreteVariable(inside_name, values=("No", "Yes")) |
| 78 | + return data.add_column(inside_var, inside, to_metas=True) |
| 79 | + |
| 80 | + @staticmethod |
| 81 | + def _inside_ellipsis(scores: np.ndarray) -> np.ndarray: |
| 82 | + # https://stats.stackexchange.com/questions/577628/trying-to-understand-how-to-calculate-a-hotellings-t2-confidence-ellipse |
| 83 | + # https://stackoverflow.com/questions/66179256/how-to-check-if-a-point-is-in-an-ellipse-in-python |
| 84 | + assert scores.shape[1] > 1 |
| 85 | + |
| 86 | + f = ss.f.ppf(0.95, len(scores[0]), len(scores) - len(scores[0])) |
| 87 | + m = [np.pi * x / 100 for x in range(201)] |
| 88 | + cx = np.cos(m) * np.std(scores[:, 0]) * f |
| 89 | + cy = np.sin(m) * np.std(scores[:, 1]) * f |
| 90 | + |
| 91 | + a = ((scores[:, 0] - np.mean(cx)) ** 2) / (np.min(cx) ** 2) |
| 92 | + b = ((np.mean(cy) - scores[:, 1]) ** 2) / (np.min(cy) ** 2) |
| 93 | + return a + b <= 1 |
70 | 94 |
|
71 | 95 | @OWBaseLearner.Inputs.data |
72 | 96 | def set_data(self, data): |
|
0 commit comments