From d45f5076255e0891e2ac102783494a69199ad45b Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Thu, 10 Nov 2016 12:05:13 +0100 Subject: [PATCH 1/5] Table: Add transpose method --- Orange/data/table.py | 98 ++++++++- Orange/tests/test_table.py | 410 ++++++++++++++++++++++++++++++++++++- 2 files changed, 505 insertions(+), 3 deletions(-) diff --git a/Orange/data/table.py b/Orange/data/table.py index 67bc41685b0..f9bfa3a2708 100644 --- a/Orange/data/table.py +++ b/Orange/data/table.py @@ -15,7 +15,8 @@ from Orange.data import ( _contingency, _valuecount, - Domain, Variable, Storage, StringVariable, Unknown, Value, Instance + Domain, Variable, Storage, StringVariable, Unknown, Value, Instance, + ContinuousVariable, DiscreteVariable, MISSING_VALUES ) from Orange.data.util import SharedComputeValue from Orange.statistics.util import bincount, countnans, contingency, stats as fast_stats @@ -1432,6 +1433,101 @@ def _compute_contingency(self, col_vars=None, row_var=None): return contingencies, unknown_rows + @classmethod + def transpose(cls, table, feature_names_column="", + meta_attr_name="Feature name"): + """ + Transpose the table. + + :param table: Table - table to transpose + :param feature_names_column: str - name of (String) meta attribute to + use for feature names + :param meta_attr_name: str - name of new meta attribute into which + feature names are mapped + :return: Table - transposed table + """ + self = cls() + n_cols, self.n_rows = table.X.shape + old_domain = table.attributes.get("old_domain") + + # attributes + # - classes and metas to attributes of attributes + # - arbitrary meta column to feature names + self.X = table.X.T + attributes = [ContinuousVariable(str(row[feature_names_column])) + for row in table] if feature_names_column else \ + [ContinuousVariable("Feature " + str(i + 1).zfill( + int(np.ceil(np.log10(n_cols))))) for i in range(n_cols)] + if old_domain and feature_names_column: + for i in range(len(attributes)): + if attributes[i].name in old_domain: + var = old_domain[attributes[i].name] + attr = ContinuousVariable(var.name) if var.is_continuous \ + else DiscreteVariable(var.name, var.values) + attr.attributes = var.attributes.copy() + attributes[i] = attr + + def set_attributes_of_attributes(_vars, _table): + for i, variable in enumerate(_vars): + if variable.name == feature_names_column: + continue + for j, row in enumerate(_table): + value = variable.repr_val(row) if np.isscalar(row) \ + else row[i] if isinstance(row[i], str) \ + else variable.repr_val(row[i]) + + if value not in MISSING_VALUES: + attributes[j].attributes[variable.name] = value + + set_attributes_of_attributes(table.domain.class_vars, table.Y) + set_attributes_of_attributes(table.domain.metas, table.metas) + + # weights + self.W = np.empty((self.n_rows, 0)) + + def get_table_from_attributes_of_attributes(_vars, _dtype=float): + T = np.empty((self.n_rows, len(_vars)), dtype=_dtype) + for i, _attr in enumerate(table.domain.attributes): + for j, _var in enumerate(_vars): + val = str(_attr.attributes.get(_var.name, "")) + if not _var.is_string: + val = np.nan if val in MISSING_VALUES else \ + _var.values.index(val) if \ + _var.is_discrete else float(val) + T[i, j] = val + return T + + # class_vars - attributes of attributes to class - from old domain + class_vars = [] + if old_domain: + class_vars = old_domain.class_vars + self.Y = get_table_from_attributes_of_attributes(class_vars) + + # metas + # - feature names and attributes of attributes to metas + self.metas, metas = np.empty((self.n_rows, 0), dtype=object), [] + if meta_attr_name not in [m.name for m in table.domain.metas]: + self.metas = np.array([[a.name] for a in table.domain.attributes], + dtype=object) + metas.append(StringVariable(meta_attr_name)) + + names = chain.from_iterable(list(attr.attributes) + for attr in table.domain.attributes) + names = sorted(set(names) - {var.name for var in class_vars}) + _metas = [StringVariable(n) for n in names] + if old_domain: + _metas = [m for m in old_domain.metas if m.name != meta_attr_name] + M = get_table_from_attributes_of_attributes(_metas, _dtype=object) + if _metas: + self.metas = np.hstack((self.metas, M)) + metas.extend(_metas) + + self.domain = Domain(attributes, class_vars, metas) + cls._init_ids(self) + self.attributes = table.attributes.copy() + self.attributes["old_domain"] = table.domain + return self + def _check_arrays(*arrays, dtype=None): checked = [] diff --git a/Orange/tests/test_table.py b/Orange/tests/test_table.py index 9ff48c6d778..af30407c30e 100644 --- a/Orange/tests/test_table.py +++ b/Orange/tests/test_table.py @@ -12,8 +12,8 @@ import numpy as np from Orange import data -from Orange.data import filter, Variable -from Orange.data import Unknown +from Orange.data import (filter, Unknown, Variable, Table, DiscreteVariable, + ContinuousVariable, Domain, StringVariable) from Orange.tests import test_dirname @@ -2150,6 +2150,412 @@ def test_iteration_with_assignment(self): np.testing.assert_array_equal(table.X[:, 0], np.arange(len(table))) +class TestTableTranspose(unittest.TestCase): + def test_transpose_no_class(self): + attrs = [ContinuousVariable("c1"), ContinuousVariable("c2")] + data = Table(Domain(attrs), np.arange(8).reshape((4, 2))) + + att = [ContinuousVariable("Feature 1"), ContinuousVariable("Feature 2"), + ContinuousVariable("Feature 3"), ContinuousVariable("Feature 4")] + domain = Domain(att, metas=[StringVariable("Feature name")]) + result = Table(domain, np.arange(8).reshape((4, 2)).T, + metas=np.array(["c1", "c2"])[:, None]) + + # transpose and compare + self._compare_tables(result, Table.transpose(data)) + + # transpose of transpose is original + t = Table.transpose(Table.transpose(data), "Feature name") + self._compare_tables(data, t) + + # original should not change + self.assertDictEqual(data.domain.attributes[0].attributes, {}) + + def test_transpose_discrete_class(self): + attrs = [ContinuousVariable("c1"), ContinuousVariable("c2")] + domain = Domain(attrs, [DiscreteVariable("cls", values=["a", "b"])]) + data = Table(domain, np.arange(8).reshape((4, 2)), + np.array([1, 1, 0, 0])) + + att = [ContinuousVariable("Feature 1"), ContinuousVariable("Feature 2"), + ContinuousVariable("Feature 3"), ContinuousVariable("Feature 4")] + att[0].attributes = {"cls": "b"} + att[1].attributes = {"cls": "b"} + att[2].attributes = {"cls": "a"} + att[3].attributes = {"cls": "a"} + domain = Domain(att, metas=[StringVariable("Feature name")]) + result = Table(domain, np.arange(8).reshape((4, 2)).T, + metas=np.array(["c1", "c2"])[:, None]) + + # transpose and compare + self._compare_tables(result, Table.transpose(data)) + + # transpose of transpose is original + t = Table.transpose(Table.transpose(data), "Feature name") + self._compare_tables(data, t) + + # original should not change + self.assertDictEqual(data.domain.attributes[0].attributes, {}) + + def test_transpose_continuous_class(self): + attrs = [ContinuousVariable("c1"), ContinuousVariable("c2")] + domain = Domain(attrs, [ContinuousVariable("cls")]) + data = Table(domain, np.arange(8).reshape((4, 2)), np.arange(4, 0, -1)) + + att = [ContinuousVariable("Feature 1"), ContinuousVariable("Feature 2"), + ContinuousVariable("Feature 3"), ContinuousVariable("Feature 4")] + att[0].attributes = {"cls": "4.000"} + att[1].attributes = {"cls": "3.000"} + att[2].attributes = {"cls": "2.000"} + att[3].attributes = {"cls": "1.000"} + domain = Domain(att, metas=[StringVariable("Feature name")]) + result = Table(domain, np.arange(8).reshape((4, 2)).T, + metas=np.array(["c1", "c2"])[:, None]) + + # transpose and compare + self._compare_tables(result, Table.transpose(data)) + + # transpose of transpose + t = Table.transpose(Table.transpose(data), "Feature name") + self._compare_tables(data, t) + + # original should not change + self.assertDictEqual(data.domain.attributes[0].attributes, {}) + + def test_transpose_missing_class(self): + attrs = [ContinuousVariable("c1"), ContinuousVariable("c2")] + domain = Domain(attrs, [ContinuousVariable("cls")]) + data = Table(domain, np.arange(8).reshape((4, 2)), + np.array([np.nan, 3, 2, 1])) + + att = [ContinuousVariable("Feature 1"), ContinuousVariable("Feature 2"), + ContinuousVariable("Feature 3"), ContinuousVariable("Feature 4")] + att[1].attributes = {"cls": "3.000"} + att[2].attributes = {"cls": "2.000"} + att[3].attributes = {"cls": "1.000"} + domain = Domain(att, metas=[StringVariable("Feature name")]) + result = Table(domain, np.arange(8).reshape((4, 2)).T, + metas=np.array(["c1", "c2"])[:, None]) + + # transpose and compare + self._compare_tables(result, Table.transpose(data)) + + # transpose of transpose + t = Table.transpose(Table.transpose(data), "Feature name") + self._compare_tables(data, t) + + # original should not change + self.assertDictEqual(data.domain.attributes[0].attributes, {}) + + def test_transpose_multiple_class(self): + attrs = [ContinuousVariable("c1"), ContinuousVariable("c2")] + class_vars = [ContinuousVariable("cls1"), ContinuousVariable("cls2")] + domain = Domain(attrs, class_vars) + data = Table(domain, np.arange(8).reshape((4, 2)), + np.arange(8).reshape((4, 2))) + + att = [ContinuousVariable("Feature 1"), ContinuousVariable("Feature 2"), + ContinuousVariable("Feature 3"), ContinuousVariable("Feature 4")] + att[0].attributes = {"cls1": "0.000", "cls2": "1.000"} + att[1].attributes = {"cls1": "2.000", "cls2": "3.000"} + att[2].attributes = {"cls1": "4.000", "cls2": "5.000"} + att[3].attributes = {"cls1": "6.000", "cls2": "7.000"} + domain = Domain(att, metas=[StringVariable("Feature name")]) + result = Table(domain, np.arange(8).reshape((4, 2)).T, + metas=np.array(["c1", "c2"])[:, None]) + + # transpose and compare + self._compare_tables(result, Table.transpose(data)) + + # transpose of transpose + t = Table.transpose(Table.transpose(data), "Feature name") + self._compare_tables(data, t) + + # original should not change + self.assertDictEqual(data.domain.attributes[0].attributes, {}) + + def test_transpose_metas(self): + attrs = [ContinuousVariable("c1"), ContinuousVariable("c2")] + metas = [StringVariable("m1")] + domain = Domain(attrs, metas=metas) + X = np.arange(8).reshape((4, 2)) + M = np.array(["aa", "bb", "cc", "dd"])[:, None] + data = Table(domain, X, metas=M) + + att = [ContinuousVariable("Feature 1"), ContinuousVariable("Feature 2"), + ContinuousVariable("Feature 3"), ContinuousVariable("Feature 4")] + att[0].attributes = {"m1": "aa"} + att[1].attributes = {"m1": "bb"} + att[2].attributes = {"m1": "cc"} + att[3].attributes = {"m1": "dd"} + domain = Domain(att, metas=[StringVariable("Feature name")]) + result = Table(domain, np.arange(8).reshape((4, 2)).T, + metas=np.array(["c1", "c2"])[:, None]) + + # transpose and compare + self._compare_tables(result, Table.transpose(data)) + + # transpose of transpose + t = Table.transpose(Table.transpose(data), "Feature name") + self._compare_tables(data, t) + + # original should not change + self.assertDictEqual(data.domain.attributes[0].attributes, {}) + + def test_transpose_discrete_metas(self): + attrs = [ContinuousVariable("c1"), ContinuousVariable("c2")] + metas = [DiscreteVariable("m1", values=["aa", "bb"])] + domain = Domain(attrs, metas=metas) + X = np.arange(8).reshape((4, 2)) + M = np.array([0, 1, 0, 1])[:, None] + data = Table(domain, X, metas=M) + + att = [ContinuousVariable("Feature 1"), ContinuousVariable("Feature 2"), + ContinuousVariable("Feature 3"), ContinuousVariable("Feature 4")] + att[0].attributes = {"m1": "aa"} + att[1].attributes = {"m1": "bb"} + att[2].attributes = {"m1": "aa"} + att[3].attributes = {"m1": "bb"} + domain = Domain(att, metas=[StringVariable("Feature name")]) + result = Table(domain, np.arange(8).reshape((4, 2)).T, + metas=np.array(["c1", "c2"])[:, None]) + + # transpose and compare + self._compare_tables(result, Table.transpose(data)) + + # transpose of transpose is original + t = Table.transpose(Table.transpose(data), "Feature name") + self._compare_tables(data, t) + + # original should not change + self.assertDictEqual(data.domain.attributes[0].attributes, {}) + + def test_transpose_continuous_metas(self): + attrs = [ContinuousVariable("c1"), ContinuousVariable("c2")] + metas = [ContinuousVariable("m1")] + domain = Domain(attrs, metas=metas) + X = np.arange(8).reshape((4, 2)) + M = np.array([0, 1, 0, 1])[:, None] + data = Table(domain, X, metas=M) + + att = [ContinuousVariable("Feature 1"), ContinuousVariable("Feature 2"), + ContinuousVariable("Feature 3"), ContinuousVariable("Feature 4")] + att[0].attributes = {"m1": "0.000"} + att[1].attributes = {"m1": "1.000"} + att[2].attributes = {"m1": "0.000"} + att[3].attributes = {"m1": "1.000"} + domain = Domain(att, metas=[StringVariable("Feature name")]) + result = Table(domain, np.arange(8).reshape((4, 2)).T, + metas=np.array(["c1", "c2"])[:, None]) + + # transpose and compare + self._compare_tables(result, Table.transpose(data)) + + # transpose of transpose is original + t = Table.transpose(Table.transpose(data), "Feature name") + self._compare_tables(data, t) + + # original should not change + self.assertDictEqual(data.domain.attributes[0].attributes, {}) + + def test_transpose_missing_metas(self): + attrs = [ContinuousVariable("c1"), ContinuousVariable("c2")] + metas = [StringVariable("m1")] + domain = Domain(attrs, metas=metas) + X = np.arange(8).reshape((4, 2)) + M = np.array(["aa", "bb", "", "dd"])[:, None] + data = Table(domain, X, metas=M) + + att = [ContinuousVariable("Feature 1"), ContinuousVariable("Feature 2"), + ContinuousVariable("Feature 3"), ContinuousVariable("Feature 4")] + att[0].attributes = {"m1": "aa"} + att[1].attributes = {"m1": "bb"} + att[3].attributes = {"m1": "dd"} + domain = Domain(att, metas=[StringVariable("Feature name")]) + result = Table(domain, np.arange(8).reshape((4, 2)).T, + metas=np.array(["c1", "c2"])[:, None]) + + # transpose and compare + self._compare_tables(result, Table.transpose(data)) + + # transpose of transpose + t = Table.transpose(Table.transpose(data), "Feature name") + self._compare_tables(data, t) + + # original should not change + self.assertDictEqual(data.domain.attributes[0].attributes, {}) + + def test_transpose_multiple_metas(self): + attrs = [ContinuousVariable("c1"), ContinuousVariable("c2")] + metas = [StringVariable("m1"), StringVariable("m2")] + domain = Domain(attrs, metas=metas) + X = np.arange(8).reshape((4, 2)) + M = np.array([["aa", "aaa"], ["bb", "bbb"], + ["cc", "ccc"], ["dd", "ddd"]]) + data = Table(domain, X, metas=M) + + att = [ContinuousVariable("Feature 1"), ContinuousVariable("Feature 2"), + ContinuousVariable("Feature 3"), ContinuousVariable("Feature 4")] + att[0].attributes = {"m1": "aa", "m2": "aaa"} + att[1].attributes = {"m1": "bb", "m2": "bbb"} + att[2].attributes = {"m1": "cc", "m2": "ccc"} + att[3].attributes = {"m1": "dd", "m2": "ddd"} + domain = Domain(att, metas=[StringVariable("Feature name")]) + result = Table(domain, np.arange(8).reshape((4, 2)).T, + metas=np.array(["c1", "c2"])[:, None]) + + # transpose and compare + self._compare_tables(result, Table.transpose(data)) + + # transpose of transpose + t = Table.transpose(Table.transpose(data), "Feature name") + self._compare_tables(data, t) + + # original should not change + self.assertDictEqual(data.domain.attributes[0].attributes, {}) + + def test_transpose_class_and_metas(self): + attrs = [ContinuousVariable("c1"), ContinuousVariable("c2")] + metas = [StringVariable("m1"), StringVariable("m2")] + domain = Domain(attrs, [ContinuousVariable("cls")], metas) + M = np.array([["aa", "aaa"], ["bb", "bbb"], + ["cc", "ccc"], ["dd", "ddd"]]) + data = Table(domain, np.arange(8).reshape((4, 2)), np.arange(1, 5), M) + + att = [ContinuousVariable("Feature 1"), ContinuousVariable("Feature 2"), + ContinuousVariable("Feature 3"), ContinuousVariable("Feature 4")] + att[0].attributes = {"cls": "1.000", "m1": "aa", "m2": "aaa"} + att[1].attributes = {"cls": "2.000", "m1": "bb", "m2": "bbb"} + att[2].attributes = {"cls": "3.000", "m1": "cc", "m2": "ccc"} + att[3].attributes = {"cls": "4.000", "m1": "dd", "m2": "ddd"} + domain = Domain(att, metas=[StringVariable("Feature name")]) + result = Table(domain, np.arange(8).reshape((4, 2)).T, + metas=np.array(["c1", "c2"])[:, None]) + + # transpose and compare + self._compare_tables(result, Table.transpose(data)) + + # transpose of transpose + t = Table.transpose(Table.transpose(data), "Feature name") + self._compare_tables(data, t) + + # original should not change + self.assertDictEqual(data.domain.attributes[0].attributes, {}) + + def test_transpose_attributes_of_attributes(self): + attrs = [ContinuousVariable("c1"), ContinuousVariable("c2")] + attrs[0].attributes = {"attr1": "a", "attr2": "aa"} + attrs[1].attributes = {"attr1": "b", "attr2": "bb"} + domain = Domain(attrs) + data = Table(domain, np.arange(8).reshape((4, 2))) + + att = [ContinuousVariable("Feature 1"), ContinuousVariable("Feature 2"), + ContinuousVariable("Feature 3"), ContinuousVariable("Feature 4")] + metas = [StringVariable("Feature name"), StringVariable("attr1"), + StringVariable("attr2")] + domain = Domain(att, metas=metas) + result = Table(domain, np.arange(8).reshape((4, 2)).T, + metas=np.array([["c1", "a", "aa"], ["c2", "b", "bb"]])) + + # transpose and compare + self._compare_tables(result, Table.transpose(data)) + + # transpose of transpose + t = Table.transpose(Table.transpose(data), "Feature name") + self._compare_tables(data, t) + + # original should not change + self.assertDictEqual(data.domain.attributes[0].attributes, + {"attr1": "a", "attr2": "aa"}) + + def test_transpose_attributes_of_attributes_missings(self): + attrs = [ContinuousVariable("c1"), ContinuousVariable("c2")] + attrs[0].attributes = {"attr1": "a", "attr2": "aa"} + attrs[1].attributes = {"attr1": "b"} + domain = Domain(attrs) + data = Table(domain, np.arange(8).reshape((4, 2))) + + att = [ContinuousVariable("Feature 1"), ContinuousVariable("Feature 2"), + ContinuousVariable("Feature 3"), ContinuousVariable("Feature 4")] + metas = [StringVariable("Feature name"), StringVariable("attr1"), + StringVariable("attr2")] + domain = Domain(att, metas=metas) + result = Table(domain, np.arange(8).reshape((4, 2)).T, + metas=np.array([["c1", "a", "aa"], ["c2", "b", ""]])) + + # transpose and compare + self._compare_tables(result, Table.transpose(data)) + + # transpose of transpose + t = Table.transpose(Table.transpose(data), "Feature name") + self._compare_tables(data, t) + + # original should not change + self.assertDictEqual(data.domain.attributes[0].attributes, + {"attr1": "a", "attr2": "aa"}) + + def test_transpose_class_metas_attributes(self): + attrs = [ContinuousVariable("c1"), ContinuousVariable("c2")] + attrs[0].attributes = {"attr1": "a1", "attr2": "aa1"} + attrs[1].attributes = {"attr1": "b1", "attr2": "bb1"} + metas = [StringVariable("m1"), StringVariable("m2")] + domain = Domain(attrs, [ContinuousVariable("cls")], metas) + M = np.array([["aa", "aaa"], ["bb", "bbb"], + ["cc", "ccc"], ["dd", "ddd"]]) + data = Table(domain, np.arange(8).reshape((4, 2)), np.arange(1, 5), M) + + att = [ContinuousVariable("Feature 1"), ContinuousVariable("Feature 2"), + ContinuousVariable("Feature 3"), ContinuousVariable("Feature 4")] + att[0].attributes = {"cls": "1.000", "m1": "aa", "m2": "aaa"} + att[1].attributes = {"cls": "2.000", "m1": "bb", "m2": "bbb"} + att[2].attributes = {"cls": "3.000", "m1": "cc", "m2": "ccc"} + att[3].attributes = {"cls": "4.000", "m1": "dd", "m2": "ddd"} + metas = [StringVariable("Feature name"), StringVariable("attr1"), + StringVariable("attr2")] + domain = Domain(att, metas=metas) + M = np.array([["c1", "a1", "aa1"], ["c2", "b1", "bb1"]]) + result = Table(domain, np.arange(8).reshape((4, 2)).T, metas=M) + + # transpose and compare + self._compare_tables(result, Table.transpose(data)) + + # transpose of transpose + t = Table.transpose(Table.transpose(data), "Feature name") + self._compare_tables(data, t) + + # original should not change + self.assertDictEqual(data.domain.attributes[0].attributes, + {"attr1": "a1", "attr2": "aa1"}) + + def test_transpose(self): + zoo = Table("zoo") + t1 = Table.transpose(zoo) + t2 = Table.transpose(t1, "Feature name") + t3 = Table.transpose(t2) + self._compare_tables(zoo, t2) + self._compare_tables(t1, t3) + + def _compare_tables(self, table1, table2): + self.assertEqual(table1.n_rows, table2.n_rows) + np.testing.assert_array_equal(table1.X, table2.X) + np.testing.assert_array_equal(table1.Y, table2.Y) + np.testing.assert_array_equal(table1.metas, table2.metas) + np.testing.assert_array_equal(table1.W, table2.W) + + self.assertEqual([(type(x), x.name, x.attributes) + for x in table1.domain.attributes], + [(type(x), x.name, x.attributes) + for x in table2.domain.attributes]) + self.assertEqual([(type(x), x.name, x.attributes) + for x in table1.domain.class_vars], + [(type(x), x.name, x.attributes) + for x in table2.domain.class_vars]) + self.assertEqual([(type(x), x.name, x.attributes) + for x in table1.domain.metas], + [(type(x), x.name, x.attributes) + for x in table2.domain.metas]) + + if __name__ == "__main__": unittest.main() From 8576ec1dd4675a04bcff0e1d4e1dda48116aff1b Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Thu, 10 Nov 2016 12:06:45 +0100 Subject: [PATCH 2/5] OWTranspose: Add a new widget --- Orange/widgets/data/icons/Transpose.svg | 257 ++++++++++++++++++ Orange/widgets/data/owtranspose.py | 108 ++++++++ Orange/widgets/data/tests/test_owtranspose.py | 39 +++ 3 files changed, 404 insertions(+) create mode 100644 Orange/widgets/data/icons/Transpose.svg create mode 100644 Orange/widgets/data/owtranspose.py create mode 100644 Orange/widgets/data/tests/test_owtranspose.py diff --git a/Orange/widgets/data/icons/Transpose.svg b/Orange/widgets/data/icons/Transpose.svg new file mode 100644 index 00000000000..095a6f22318 --- /dev/null +++ b/Orange/widgets/data/icons/Transpose.svg @@ -0,0 +1,257 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/Orange/widgets/data/owtranspose.py b/Orange/widgets/data/owtranspose.py new file mode 100644 index 00000000000..c2e0b2c247a --- /dev/null +++ b/Orange/widgets/data/owtranspose.py @@ -0,0 +1,108 @@ +from AnyQt.QtCore import Qt + +from Orange.data import Table, StringVariable +from Orange.widgets.settings import (Setting, ContextSetting, + DomainContextHandler) +from Orange.widgets.utils.itemmodels import DomainModel +from Orange.widgets.widget import OWWidget, Msg +from Orange.widgets import gui + + +class OWTranspose(OWWidget): + name = "Transpose" + description = "Transpose data table." + icon = "icons/Transpose.svg" + priority = 2000 + + inputs = [("Data", Table, "set_data")] + outputs = [("Data", Table)] + + resizing_enabled = False + want_main_area = False + + settingsHandler = DomainContextHandler(metas_in_res=True) + feature_type = ContextSetting(0) + feature_names_column = ContextSetting(None) + auto_apply = Setting(True) + + class Error(OWWidget.Error): + value_error = Msg("{}") + + def __init__(self): + super().__init__() + self.data = None + + # GUI + box = gui.vBox(self.controlArea, "Feature names") + self.feature_radio = gui.radioButtonsInBox( + box, self, "feature_type", callback=self.apply, + btnLabels=["Generic", "From meta attribute:"]) + + self.feature_model = DomainModel( + order=DomainModel.METAS, valid_types=StringVariable, + alphabetical=True) + self.feature_combo = gui.comboBox( + gui.indentedBox( + box, gui.checkButtonOffsetHint(self.feature_radio.buttons[0])), + self, "feature_names_column", callback=self._feature_combo_changed, + model=self.feature_model, sendSelectedValue=True, + labelWidth=100, contentsLength=12, orientation=Qt.Horizontal) + + self.apply_button = gui.auto_commit( + self.controlArea, self, "auto_apply", "&Apply", + box=False, commit=self.apply) + + def _feature_combo_changed(self): + self.feature_type = 1 + self.apply() + + def set_data(self, data): + self.closeContext() + self.data = data + self.update_controls() + self.openContext(data) + self.apply() + + def update_controls(self): + self.feature_model.set_domain(None) + if self.data: + self.feature_model.set_domain(self.data.domain) + if len(self.feature_model): + _names = [m.name for m in self.data.domain.metas if m.is_string] + self.feature_names_column = _names[0] + enabled = bool(len(self.feature_model)) + self.feature_radio.buttons[1].setEnabled(enabled) + self.feature_combo.setEnabled(enabled) + self.feature_type = int(enabled) + + def apply(self): + self.clear_messages() + transposed = None + if self.data: + options = dict() + if self.feature_type: + options["feature_names_column"] = self.feature_names_column + try: + transposed = Table.transpose(self.data, **options) + except ValueError as e: + self.Error.value_error(e) + self.send("Data", transposed) + + def send_report(self): + text = "from meta attribute: {}".format(self.feature_names_column) \ + if self.feature_type else "generic" + self.report_items("", [("Feature names", text)]) + if self.data: + self.report_data("Data", self.data) + + +if __name__ == "__main__": + from AnyQt.QtWidgets import QApplication + + app = QApplication([]) + ow = OWTranspose() + d = Table("zoo") + ow.set_data(d) + ow.show() + app.exec_() + ow.saveSettings() diff --git a/Orange/widgets/data/tests/test_owtranspose.py b/Orange/widgets/data/tests/test_owtranspose.py new file mode 100644 index 00000000000..045ff06afca --- /dev/null +++ b/Orange/widgets/data/tests/test_owtranspose.py @@ -0,0 +1,39 @@ +# Test methods with long descriptive names can omit docstrings +# pylint: disable=missing-docstring + +import numpy as np + +from Orange.data import Table +from Orange.widgets.data.owtranspose import OWTranspose +from Orange.widgets.tests.base import WidgetTest + + +class TestOWTranspose(WidgetTest): + def setUp(self): + self.widget = self.create_widget(OWTranspose) + self.zoo = Table("zoo") + + def test_data(self): + """Check widget's data and the output with data on the input""" + self.assertEqual(self.widget.data, None) + self.send_signal("Data", self.zoo) + self.assertEqual(self.widget.data, self.zoo) + output = self.get_output("Data") + transpose = Table.transpose(self.zoo) + np.testing.assert_array_equal(output.X, transpose.X) + np.testing.assert_array_equal(output.Y, transpose.Y) + np.testing.assert_array_equal(output.metas, transpose.metas) + self.send_signal("Data", None) + self.assertEqual(self.widget.data, None) + self.assertIsNone(self.get_output("Data")) + + def test_parameters(self): + """Check widget's output for all possible values of parameters""" + self.send_signal("Data", self.zoo) + self.assertListEqual( + [a.name for a in self.get_output("Data").domain.attributes], + [self.zoo.domain.metas[0].to_val(m) for m in self.zoo.metas[:, 0]]) + self.widget.feature_radio.buttons[0].click() + self.widget.apply() + self.assertTrue(all(["Feature" in x.name for x + in self.get_output("Data").domain.attributes])) From bad6ed16ee47610f51a593582af497facceb9284 Mon Sep 17 00:00:00 2001 From: janezd Date: Fri, 25 Nov 2016 21:06:15 +0100 Subject: [PATCH 3/5] OWTranspose: Fix contexts, other minor fixes --- Orange/widgets/data/owtranspose.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Orange/widgets/data/owtranspose.py b/Orange/widgets/data/owtranspose.py index c2e0b2c247a..e38eef1b269 100644 --- a/Orange/widgets/data/owtranspose.py +++ b/Orange/widgets/data/owtranspose.py @@ -1,5 +1,3 @@ -from AnyQt.QtCore import Qt - from Orange.data import Table, StringVariable from Orange.widgets.settings import (Setting, ContextSetting, DomainContextHandler) @@ -45,8 +43,7 @@ def __init__(self): gui.indentedBox( box, gui.checkButtonOffsetHint(self.feature_radio.buttons[0])), self, "feature_names_column", callback=self._feature_combo_changed, - model=self.feature_model, sendSelectedValue=True, - labelWidth=100, contentsLength=12, orientation=Qt.Horizontal) + model=self.feature_model) self.apply_button = gui.auto_commit( self.controlArea, self, "auto_apply", "&Apply", @@ -57,20 +54,23 @@ def _feature_combo_changed(self): self.apply() def set_data(self, data): - self.closeContext() + # Skip the context if the combo is empty: a context with + # feature_model == None would then match all domains + if self.feature_model: + self.closeContext() self.data = data self.update_controls() - self.openContext(data) + if self.data is not None and self.feature_model: + self.openContext(data) self.apply() def update_controls(self): self.feature_model.set_domain(None) if self.data: self.feature_model.set_domain(self.data.domain) - if len(self.feature_model): - _names = [m.name for m in self.data.domain.metas if m.is_string] - self.feature_names_column = _names[0] - enabled = bool(len(self.feature_model)) + if self.feature_model: + self.feature_names_column = self.feature_model[0] + enabled = bool(self.feature_model) self.feature_radio.buttons[1].setEnabled(enabled) self.feature_combo.setEnabled(enabled) self.feature_type = int(enabled) @@ -79,11 +79,9 @@ def apply(self): self.clear_messages() transposed = None if self.data: - options = dict() - if self.feature_type: - options["feature_names_column"] = self.feature_names_column try: - transposed = Table.transpose(self.data, **options) + transposed = Table.transpose( + self.data, self.feature_type and self.feature_names_column) except ValueError as e: self.Error.value_error(e) self.send("Data", transposed) @@ -101,6 +99,8 @@ def send_report(self): app = QApplication([]) ow = OWTranspose() + d = Table("iris") + ow.set_data(d) d = Table("zoo") ow.set_data(d) ow.show() From 75eee2778e8a3d26c50bcda394673cbcd03c3713 Mon Sep 17 00:00:00 2001 From: janezd Date: Mon, 28 Nov 2016 22:30:31 +0100 Subject: [PATCH 4/5] Transpose: Fix auto apply --- Orange/widgets/data/owtranspose.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Orange/widgets/data/owtranspose.py b/Orange/widgets/data/owtranspose.py index e38eef1b269..d24c9d7df78 100644 --- a/Orange/widgets/data/owtranspose.py +++ b/Orange/widgets/data/owtranspose.py @@ -33,7 +33,7 @@ def __init__(self): # GUI box = gui.vBox(self.controlArea, "Feature names") self.feature_radio = gui.radioButtonsInBox( - box, self, "feature_type", callback=self.apply, + box, self, "feature_type", callback=lambda: self.apply(), btnLabels=["Generic", "From meta attribute:"]) self.feature_model = DomainModel( From bc7b98887581bbae6d666d50d9dea3dc88f4beb2 Mon Sep 17 00:00:00 2001 From: janezd Date: Mon, 28 Nov 2016 22:31:12 +0100 Subject: [PATCH 5/5] Transpose: Change icon --- Orange/widgets/data/icons/Transpose.svg | 307 ++++-------------------- 1 file changed, 50 insertions(+), 257 deletions(-) diff --git a/Orange/widgets/data/icons/Transpose.svg b/Orange/widgets/data/icons/Transpose.svg index 095a6f22318..349428a5b24 100644 --- a/Orange/widgets/data/icons/Transpose.svg +++ b/Orange/widgets/data/icons/Transpose.svg @@ -1,257 +1,50 @@ - - - -image/svg+xml \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +