diff --git a/Orange/widgets/unsupervised/owcorrespondence.py b/Orange/widgets/unsupervised/owcorrespondence.py index 965f1621e00..46f35576896 100644 --- a/Orange/widgets/unsupervised/owcorrespondence.py +++ b/Orange/widgets/unsupervised/owcorrespondence.py @@ -7,7 +7,7 @@ from AnyQt.QtCore import QEvent, QItemSelectionModel, QItemSelection import pyqtgraph as pg -import Orange.data +from Orange.data import Table, Domain, ContinuousVariable, StringVariable from Orange.statistics import contingency from Orange.widgets import widget, gui, settings @@ -15,7 +15,8 @@ from Orange.widgets.utils.widgetpreview import WidgetPreview from Orange.widgets.visualize.owscatterplotgraph import ScatterPlotItem -from Orange.widgets.widget import Input +from Orange.widgets.widget import Input, Output +from Orange.widgets.settings import Setting class ScatterPlotItem(pg.ScatterPlotItem): @@ -49,13 +50,17 @@ class OWCorrespondenceAnalysis(widget.OWWidget): keywords = [] class Inputs: - data = Input("Data", Orange.data.Table) + data = Input("Data", Table) + + class Outputs: + coordinates = Output("Coordinates", Table) Invalidate = QEvent.registerEventType() settingsHandler = settings.DomainContextHandler() selected_var_indices = settings.ContextSetting([]) + auto_commit = Setting(True) graph_name = "plot.plotItem" @@ -96,6 +101,8 @@ def __init__(self): gui.vBox(self.controlArea, "Contribution to Inertia"), "\n" ) + gui.auto_send(self.controlArea, self, "auto_commit") + gui.rubber(self.controlArea) self.plot = pg.PlotWidget(background="w") @@ -127,6 +134,24 @@ def set_data(self, data): self._restore_selection() self._update_CA() + def commit(self): + output_table = None + if self.ca is not None: + sel_vars = self.selected_vars() + if len(sel_vars) == 2: + rf = np.vstack((self.ca.row_factors, self.ca.col_factors)) + else: + rf = self.ca.row_factors + vars_data = [(val.name, var) for val in sel_vars for var in val.values] + output_table = Table( + Domain([ContinuousVariable(f"Component {i + 1}") + for i in range(rf.shape[1])], + metas=[StringVariable("Variable"), + StringVariable("Value")]), + rf, metas=vars_data + ) + self.Outputs.coordinates.send(output_table) + def clear(self): self.data = None self.ca = None @@ -145,8 +170,7 @@ def restore(view, indices): restore(self.varview, self.selected_var_indices) def _p_axes(self): -# return (0, 1) - return (self.component_x, self.component_y) + return self.component_x, self.component_y def _var_changed(self): self.selected_var_indices = sorted( @@ -182,6 +206,7 @@ def _update_CA(self): self._setup_plot() self._update_info() + self.commit() def update_XY(self): self.axis_x_cb.clear() @@ -406,4 +431,4 @@ def inertia_of_axis(self): if __name__ == "__main__": # pragma: no cover - WidgetPreview(OWCorrespondenceAnalysis).run(Orange.data.Table("smokers_ct")) + WidgetPreview(OWCorrespondenceAnalysis).run(Table("titanic")) diff --git a/Orange/widgets/unsupervised/tests/test_owcorrespondence.py b/Orange/widgets/unsupervised/tests/test_owcorrespondence.py index 4a912110fa4..7ee0fd2ed2a 100644 --- a/Orange/widgets/unsupervised/tests/test_owcorrespondence.py +++ b/Orange/widgets/unsupervised/tests/test_owcorrespondence.py @@ -3,12 +3,13 @@ from Orange.data import Table, Domain, DiscreteVariable, ContinuousVariable from Orange.widgets.tests.base import WidgetTest from Orange.widgets.unsupervised.owcorrespondence \ - import OWCorrespondenceAnalysis + import OWCorrespondenceAnalysis, select_rows class TestOWCorrespondence(WidgetTest): def setUp(self): self.widget = self.create_widget(OWCorrespondenceAnalysis) + self.data = Table("titanic") def test_no_data(self): """Check that the widget doesn't crash on empty data""" @@ -73,3 +74,17 @@ def test_no_discrete_variables(self): self.assertTrue(self.widget.Error.no_disc_vars.is_shown()) self.send_signal(self.widget.Inputs.data, Table("iris")) self.assertFalse(self.widget.Error.no_disc_vars.is_shown()) + + def test_outputs(self): + w = self.widget + + self.assertIsNone(self.get_output(w.Outputs.coordinates), None) + self.send_signal(self.widget.Inputs.data, self.data) + self.assertTupleEqual(self.get_output(w.Outputs.coordinates).X.shape, + (6, 2)) + select_rows(w.varview, [0, 1, 2]) + w.commit() + self.assertTupleEqual(self.get_output(w.Outputs.coordinates).X.shape, + (8, 8)) + self.send_signal(self.widget.Inputs.data, None) + self.assertIsNone(self.get_output(w.Outputs.coordinates), None) diff --git a/doc/visual-programming/source/widgets/unsupervised/correspondenceanalysis.md b/doc/visual-programming/source/widgets/unsupervised/correspondenceanalysis.md index 5e0241df7fc..ced266e4d3f 100644 --- a/doc/visual-programming/source/widgets/unsupervised/correspondenceanalysis.md +++ b/doc/visual-programming/source/widgets/unsupervised/correspondenceanalysis.md @@ -7,6 +7,10 @@ Correspondence analysis for categorical multivariate data. - Data: input dataset +**Outputs** + +- Coordinates: coordinates of all components + [Correspondence Analysis](https://en.wikipedia.org/wiki/Correspondence_analysis) (CA) computes the CA linear transformation of the input data. While it is similar to PCA, CA computes linear transformation on discrete rather than on continuous data. ![](images/CorrespondenceAnalysis-stamped.png)