Exemplo n.º 1
0
    def test_regression(self):
        table = Table("housing")[::10]
        freeviz = FreeViz()
        freeviz(table)

        freeviz = FreeViz(p=2)
        freeviz(table)
Exemplo n.º 2
0
    def test_prepare_freeviz_data(self):
        table = Table("iris")
        FreeViz.prepare_freeviz_data(table)

        table.X = table.X * np.nan
        self.assertEqual(FreeViz.prepare_freeviz_data(table), (None, None, None))

        table.X = None
        FreeViz.prepare_freeviz_data(table)
Exemplo n.º 3
0
    def test_weights(self):
        table = Table("iris")
        weights = np.random.rand(150, 1).flatten()
        freeviz = FreeViz(weights=weights, p=3, scale=False, center=False)
        freeviz(table)

        scale = np.array([0.5, 0.4, 0.6, 0.8])
        freeviz = FreeViz(scale=scale, center=[0.2, 0.6, 0.4, 0.2])
        freeviz(table)
    def test_prepare_freeviz_data(self):
        table = Table("iris")
        FreeViz.prepare_freeviz_data(table)

        table.X = table.X * np.nan
        self.assertEqual(FreeViz.prepare_freeviz_data(table),
                         (None, None, None))

        table.X = None
        FreeViz.prepare_freeviz_data(table)
Exemplo n.º 5
0
 def setUp(self):
     anchors = FreeViz.init_radial(len(self.data.domain.attributes))
     self.projector = projector = FreeViz(scale=False,
                                          center=False,
                                          initial=anchors,
                                          maxiter=10)
     self.projector.domain = self.data.domain
     self.projector.components_ = anchors.T
     self.projection = FreeVizModel(projector, projector.domain, 2)
     self.projection.pre_domain = self.data.domain
Exemplo n.º 6
0
 def init_projection(self):
     anchors = FreeViz.init_radial(len(self.effective_variables)) \
         if self.initialization == InitType.Circular \
         else FreeViz.init_random(len(self.effective_variables), 2)
     self.projector = FreeViz(scale=False, center=False,
                              initial=anchors, maxiter=10)
     data = self.projector.preprocess(self.effective_data)
     self.projector.domain = data.domain
     self.projector.components_ = anchors.T
     self.projection = FreeVizModel(self.projector, self.projector.domain, 2)
     self.projection.pre_domain = data.domain
     self.projection.name = self.projector.name
Exemplo n.º 7
0
 def init_projection(self):
     anchors = FreeViz.init_radial(len(self.effective_variables)) \
         if self.initialization == InitType.Circular \
         else FreeViz.init_random(len(self.effective_variables), 2)
     self.projector = FreeViz(scale=False, center=False,
                              initial=anchors, maxiter=10)
     data = self.projector.preprocess(self.effective_data)
     self.projector.domain = data.domain
     self.projector.components_ = anchors.T
     self.projection = FreeVizModel(self.projector, self.projector.domain, 2)
     self.projection.pre_domain = data.domain
     self.projection.name = self.projector.name
    def test_raising_errors(self):
        table = Table("iris")
        freeviz = FreeViz(initial=(2, 4))
        self.assertRaises(ValueError, freeviz, table)

        scale = np.array([0.5, 0.4, 0.6])
        freeviz = FreeViz(scale=scale)
        self.assertRaises(ValueError, freeviz, table)

        freeviz = FreeViz(center=[0.6, 0.4, 0.2])
        self.assertRaises(ValueError, freeviz, table)

        weights = np.random.rand(100, 1).flatten()
        freeviz = FreeViz(weights=weights)
        self.assertRaises(ValueError, freeviz, table)
Exemplo n.º 9
0
    def test_transform_changed_domain(self):
        """
        1. Open data, apply some preprocessor, splits the data into two parts,
        use FreeViz on the first part, and then transform the second part.

        2. Open data, split into two parts, apply the same preprocessor and
        FreeViz only on the first part, and then transform the second part.

        The transformed second part in (1) and (2) has to be the same.
        """
        data = Table("titanic")[::10]
        normalize = Continuize()
        freeviz = FreeViz(maxiter=40)

        # normalize all
        ndata = normalize(data)
        model = freeviz(ndata[:100])
        result_1 = model(ndata[100:])

        # normalize only the "training" part
        ndata = normalize(data[:100])
        model = freeviz(ndata)
        result_2 = model(data[100:])

        np.testing.assert_almost_equal(result_1.X, result_2.X)
Exemplo n.º 10
0
 def setUp(self):
     anchors = FreeViz.init_radial(len(self.data.domain.attributes))
     self.projector = projector = FreeViz(scale=False, center=False,
                                          initial=anchors, maxiter=10)
     self.projector.domain = self.data.domain
     self.projector.components_ = anchors.T
     self.projection = FreeVizModel(projector, projector.domain, 2)
     self.projection.pre_domain = self.data.domain
Exemplo n.º 11
0
 def test_basic(self):
     table = self.iris.copy()
     table[3, 3] = np.nan
     freeviz = FreeViz()
     model = freeviz(table)
     proj = model(table)
     self.assertEqual(len(proj), len(table))
     self.assertTrue(np.isnan(proj.X).any())
     np.testing.assert_array_equal(proj[:100], model(table[:100]))
Exemplo n.º 12
0
class OWFreeViz(OWAnchorProjectionWidget, ConcurrentWidgetMixin):
    MAX_INSTANCES = 10000

    name = "FreeViz"
    description = "Displays FreeViz projection"
    icon = "icons/Freeviz.svg"
    priority = 240
    keywords = ["viz"]

    settings_version = 3
    initialization = settings.Setting(InitType.Circular)
    GRAPH_CLASS = OWFreeVizGraph
    graph = settings.SettingProvider(OWFreeVizGraph)

    class Error(OWAnchorProjectionWidget.Error):
        no_class_var = widget.Msg("Data must have a target variable.")
        multiple_class_vars = widget.Msg(
            "Data must have a single target variable.")
        not_enough_class_vars = widget.Msg(
            "Target variable must have at least two unique values.")
        features_exceeds_instances = widget.Msg(
            "Number of features exceeds the number of instances.")
        too_many_data_instances = widget.Msg("Data is too large.")
        constant_data = widget.Msg("All data columns are constant.")
        not_enough_features = widget.Msg("At least two features are required.")

    class Warning(OWAnchorProjectionWidget.Warning):
        removed_features = widget.Msg("Categorical features with more than"
                                      " two values are not shown.")

    def __init__(self):
        OWAnchorProjectionWidget.__init__(self)
        ConcurrentWidgetMixin.__init__(self)

    def _add_controls(self):
        self.__add_controls_start_box()
        super()._add_controls()
        self.gui.add_control(self._effects_box,
                             gui.hSlider,
                             "Hide radius:",
                             master=self.graph,
                             value="hide_radius",
                             minValue=0,
                             maxValue=100,
                             step=10,
                             createLabel=False,
                             callback=self.__radius_slider_changed)

    def __add_controls_start_box(self):
        box = gui.vBox(self.controlArea, box="Optimize", spacing=0)
        gui.comboBox(box,
                     self,
                     "initialization",
                     label="Initialization:",
                     items=InitType.items(),
                     orientation=Qt.Horizontal,
                     labelWidth=90,
                     callback=self.__init_combo_changed)
        self.run_button = gui.button(box, self, "Start", self._toggle_run)

    @property
    def effective_variables(self):
        return [
            a for a in self.data.domain.attributes
            if a.is_continuous or a.is_discrete and len(a.values) == 2
        ]

    def __radius_slider_changed(self):
        self.graph.update_radius()

    def __init_combo_changed(self):
        self.Error.proj_error.clear()
        self.init_projection()
        self.setup_plot()
        self.commit.deferred()
        if self.task is not None:
            self._run()

    def _toggle_run(self):
        if self.task is not None:
            self.cancel()
            self.graph.set_sample_size(None)
            self.run_button.setText("Resume")
            self.commit.deferred()
        else:
            self._run()

    def _run(self):
        if self.data is None:
            return
        self.graph.set_sample_size(self.SAMPLE_SIZE)
        self.run_button.setText("Stop")
        self.start(run_freeviz, self.effective_data, self.projector)

    # ConcurrentWidgetMixin
    def on_partial_result(self, result: Result):
        assert isinstance(result.projector, FreeViz)
        assert isinstance(result.projection, FreeVizModel)
        self.projector = result.projector
        self.projection = result.projection
        self.graph.update_coordinates()
        self.graph.update_density()

    def on_done(self, result: Result):
        assert isinstance(result.projector, FreeViz)
        assert isinstance(result.projection, FreeVizModel)
        self.projector = result.projector
        self.projection = result.projection
        self.graph.set_sample_size(None)
        self.run_button.setText("Start")
        self.commit.deferred()

    def on_exception(self, ex: Exception):
        self.Error.proj_error(ex)
        self.graph.set_sample_size(None)
        self.run_button.setText("Start")

    # OWAnchorProjectionWidget
    def set_data(self, data):
        super().set_data(data)
        self.graph.set_sample_size(None)
        if self._invalidated:
            self.init_projection()

    def init_projection(self):
        if self.data is None:
            return
        anchors = FreeViz.init_radial(len(self.effective_variables)) \
            if self.initialization == InitType.Circular \
            else FreeViz.init_random(len(self.effective_variables), 2)
        self.projector = FreeViz(scale=False,
                                 center=False,
                                 initial=anchors,
                                 maxiter=10)
        data = self.projector.preprocess(self.effective_data)
        self.projector.domain = data.domain
        self.projector.components_ = anchors.T
        self.projection = FreeVizModel(self.projector, self.projector.domain,
                                       2)
        self.projection.pre_domain = data.domain
        self.projection.name = self.projector.name

    def check_data(self):
        def error(err):
            err()
            self.data = None

        super().check_data()
        if self.data is not None:
            class_vars, domain = self.data.domain.class_vars, self.data.domain
            if not class_vars:
                error(self.Error.no_class_var)
            elif len(class_vars) > 1:
                error(self.Error.multiple_class_vars)
            elif class_vars[0].is_discrete and len(np.unique(self.data.Y)) < 2:
                error(self.Error.not_enough_class_vars)
            elif len(self.data.domain.attributes) < 2:
                error(self.Error.not_enough_features)
            elif len(self.data.domain.attributes) > self.data.X.shape[0]:
                error(self.Error.features_exceeds_instances)
            elif not np.sum(np.std(self.data.X, axis=0)):
                error(self.Error.constant_data)
            elif np.sum(np.all(np.isfinite(self.data.X),
                               axis=1)) > self.MAX_INSTANCES:
                error(self.Error.too_many_data_instances)
            else:
                if len(self.effective_variables) < len(domain.attributes):
                    self.Warning.removed_features()

    def enable_controls(self):
        super().enable_controls()
        self.run_button.setEnabled(self.data is not None)
        self.run_button.setText("Start")

    def get_coordinates_data(self):
        embedding = self.get_embedding()
        if embedding is None:
            return None, None
        valid_emb = embedding[self.valid_data]
        return valid_emb.T / (np.max(np.linalg.norm(valid_emb, axis=1)) or 1)

    def _manual_move(self, anchor_idx, x, y):
        self.projector.initial[anchor_idx] = [x, y]
        super()._manual_move(anchor_idx, x, y)

    def clear(self):
        super().clear()
        self.cancel()

    def onDeleteWidget(self):
        self.shutdown()
        super().onDeleteWidget()

    @classmethod
    def migrate_settings(cls, _settings, version):
        if version < 3:
            if "radius" in _settings:
                _settings["graph"]["hide_radius"] = _settings["radius"]

    @classmethod
    def migrate_context(cls, context, version):
        if version < 3:
            values = context.values
            values["attr_color"] = values["graph"]["attr_color"]
            values["attr_size"] = values["graph"]["attr_size"]
            values["attr_shape"] = values["graph"]["attr_shape"]
            values["attr_label"] = values["graph"]["attr_label"]
Exemplo n.º 13
0
class OWFreeViz(OWAnchorProjectionWidget):
    MAX_ITERATIONS = 1000
    MAX_INSTANCES = 10000

    name = "FreeViz"
    description = "Displays FreeViz projection"
    icon = "icons/Freeviz.svg"
    priority = 240
    keywords = ["viz"]

    settings_version = 3
    initialization = settings.Setting(InitType.Circular)
    GRAPH_CLASS = OWFreeVizGraph
    graph = settings.SettingProvider(OWFreeVizGraph)

    class Error(OWAnchorProjectionWidget.Error):
        no_class_var = widget.Msg("Data has no target variable")
        not_enough_class_vars = widget.Msg(
            "Target variable is not at least binary")
        features_exceeds_instances = widget.Msg(
            "Number of features exceeds the number of instances.")
        too_many_data_instances = widget.Msg("Data is too large.")
        constant_data = widget.Msg("All data columns are constant.")
        not_enough_features = widget.Msg("At least two features are required")

    class Warning(OWAnchorProjectionWidget.Warning):
        removed_features = widget.Msg("Categorical features with more than"
                                      " two values are not shown.")

    def __init__(self):
        super().__init__()
        self._loop = AsyncUpdateLoop(parent=self)
        self._loop.yielded.connect(self.__set_projection)
        self._loop.finished.connect(self.__freeviz_finished)
        self._loop.raised.connect(self.__on_error)

    def _add_controls(self):
        self.__add_controls_start_box()
        super()._add_controls()
        self.graph.gui.add_control(self._effects_box,
                                   gui.hSlider,
                                   "Hide radius:",
                                   master=self.graph,
                                   value="hide_radius",
                                   minValue=0,
                                   maxValue=100,
                                   step=10,
                                   createLabel=False,
                                   callback=self.__radius_slider_changed)

    def __add_controls_start_box(self):
        box = gui.vBox(self.controlArea, box=True)
        gui.comboBox(box,
                     self,
                     "initialization",
                     label="Initialization:",
                     items=InitType.items(),
                     orientation=Qt.Horizontal,
                     labelWidth=90,
                     callback=self.__init_combo_changed)
        self.btn_start = gui.button(box,
                                    self,
                                    "Optimize",
                                    self.__toggle_start,
                                    enabled=False)

    @property
    def effective_variables(self):
        return [
            a for a in self.data.domain.attributes
            if a.is_continuous or a.is_discrete and len(a.values) == 2
        ]

    def __radius_slider_changed(self):
        self.graph.update_radius()

    def __toggle_start(self):
        if self._loop.isRunning():
            self._loop.cancel()
            self.btn_start.setText("Optimize")
            self.progressBarFinished(processEvents=False)
        else:
            self._start()

    def __init_combo_changed(self):
        if self.data is None:
            return
        running = self._loop.isRunning()
        if running:
            self._loop.cancel()
        self.init_projection()
        self.graph.update_coordinates()
        self.commit()
        if running:
            self._start()

    def _start(self):
        def update_freeviz(anchors):
            while True:
                self.projection = self.projector(self.effective_data)
                _anchors = self.projector.components_.T
                self.projector.initial = _anchors
                yield _anchors
                if np.allclose(anchors, _anchors, rtol=1e-5, atol=1e-4):
                    return
                anchors = _anchors

        self.graph.set_sample_size(self.SAMPLE_SIZE)
        self._loop.setCoroutine(update_freeviz(self.projector.components_.T))
        self.btn_start.setText("Stop")
        self.progressBarInit()
        self.setBlocking(True)
        self.setStatusMessage("Optimizing")

    def __set_projection(self, _):
        # Set/update the projection matrix and coordinate embeddings
        self.progressBarAdvance(100. / self.MAX_ITERATIONS)
        self.graph.update_coordinates()

    def __freeviz_finished(self):
        self.graph.set_sample_size(None)
        self.btn_start.setText("Optimize")
        self.setStatusMessage("")
        self.setBlocking(False)
        self.progressBarFinished()
        self.commit()

    def __on_error(self, err):
        sys.excepthook(type(err), err, getattr(err, "__traceback__"))

    def check_data(self):
        def error(err):
            err()
            self.data = None

        super().check_data()
        if self.data is not None:
            class_var, domain = self.data.domain.class_var, self.data.domain
            if class_var is None:
                error(self.Error.no_class_var)
            elif class_var.is_discrete and len(np.unique(self.data.Y)) < 2:
                error(self.Error.not_enough_class_vars)
            elif len(self.data.domain.attributes) < 2:
                error(self.Error.not_enough_features)
            elif len(self.data.domain.attributes) > self.data.X.shape[0]:
                error(self.Error.features_exceeds_instances)
            elif not np.sum(np.std(self.data.X, axis=0)):
                error(self.Error.constant_data)
            elif np.sum(self.valid_data) > self.MAX_INSTANCES:
                error(self.Error.too_many_data_instances)
            else:
                if len(self.effective_variables) < len(domain.attributes):
                    self.Warning.removed_features()
        self.btn_start.setEnabled(self.data is not None)

    def set_data(self, data):
        super().set_data(data)
        if self.data is not None:
            self.init_projection()

    def init_projection(self):
        anchors = FreeViz.init_radial(len(self.effective_variables)) \
            if self.initialization == InitType.Circular \
            else FreeViz.init_random(len(self.effective_variables), 2)
        self.projector = FreeViz(scale=False,
                                 center=False,
                                 initial=anchors,
                                 maxiter=10)
        data = self.projector.preprocess(self.effective_data)
        self.projector.domain = data.domain
        self.projector.components_ = anchors.T
        self.projection = FreeVizModel(self.projector, self.projector.domain)
        self.projection.pre_domain = data.domain
        self.projection.name = self.projector.name

    def get_coordinates_data(self):
        embedding = self.get_embedding()
        if embedding is None:
            return None, None
        valid_emb = embedding[self.valid_data]
        return valid_emb.T / (np.max(np.linalg.norm(valid_emb, axis=1)) or 1)

    def _manual_move(self, anchor_idx, x, y):
        self.projector.initial[anchor_idx] = [x, y]
        super()._manual_move(anchor_idx, x, y)

    def clear(self):
        super().clear()
        self._loop.cancel()

    @classmethod
    def migrate_settings(cls, _settings, version):
        if version < 3:
            if "radius" in _settings:
                _settings["graph"]["hide_radius"] = _settings["radius"]

    @classmethod
    def migrate_context(cls, context, version):
        if version < 3:
            values = context.values
            values["attr_color"] = values["graph"]["attr_color"]
            values["attr_size"] = values["graph"]["attr_size"]
            values["attr_shape"] = values["graph"]["attr_shape"]
            values["attr_label"] = values["graph"]["attr_label"]
Exemplo n.º 14
0
 def test_initial(self):
     FreeViz.init_radial(1)
     FreeViz.init_radial(2)
     FreeViz.init_radial(3)
     FreeViz.init_random(2, 4, 5)
 def test_basic(self):
     table = Table("iris")
     table[3, 3] = np.nan
     freeviz = FreeViz()
     model = freeviz(table)
     model(table)
Exemplo n.º 16
0
class OWFreeViz(OWAnchorProjectionWidget):
    MAX_ITERATIONS = 1000
    MAX_INSTANCES = 10000

    name = "FreeViz"
    description = "Displays FreeViz projection"
    icon = "icons/Freeviz.svg"
    priority = 240
    keywords = ["viz"]

    settings_version = 3
    initialization = settings.Setting(InitType.Circular)
    GRAPH_CLASS = OWFreeVizGraph
    graph = settings.SettingProvider(OWFreeVizGraph)

    class Error(OWAnchorProjectionWidget.Error):
        no_class_var = widget.Msg("Data has no target variable")
        not_enough_class_vars = widget.Msg(
            "Target variable is not at least binary")
        features_exceeds_instances = widget.Msg(
            "Number of features exceeds the number of instances.")
        too_many_data_instances = widget.Msg("Data is too large.")
        constant_data = widget.Msg("All data columns are constant.")
        not_enough_features = widget.Msg("At least two features are required")

    class Warning(OWAnchorProjectionWidget.Warning):
        removed_features = widget.Msg("Categorical features with more than"
                                      " two values are not shown.")

    def __init__(self):
        super().__init__()
        self._loop = AsyncUpdateLoop(parent=self)
        self._loop.yielded.connect(self.__set_projection)
        self._loop.finished.connect(self.__freeviz_finished)
        self._loop.raised.connect(self.__on_error)

    def _add_controls(self):
        self.__add_controls_start_box()
        super()._add_controls()
        self.gui.add_control(
            self._effects_box, gui.hSlider, "Hide radius:", master=self.graph,
            value="hide_radius", minValue=0, maxValue=100, step=10,
            createLabel=False, callback=self.__radius_slider_changed
        )

    def __add_controls_start_box(self):
        box = gui.vBox(self.controlArea, box=True)
        gui.comboBox(
            box, self, "initialization", label="Initialization:",
            items=InitType.items(), orientation=Qt.Horizontal,
            labelWidth=90, callback=self.__init_combo_changed)
        self.btn_start = gui.button(
            box, self, "Optimize", self.__toggle_start, enabled=False)

    @property
    def effective_variables(self):
        return [a for a in self.data.domain.attributes
                if a.is_continuous or a.is_discrete and len(a.values) == 2]

    def __radius_slider_changed(self):
        self.graph.update_radius()

    def __toggle_start(self):
        if self._loop.isRunning():
            self._loop.cancel()
            self.btn_start.setText("Optimize")
            self.progressBarFinished(processEvents=False)
        else:
            self._start()

    def __init_combo_changed(self):
        if self.data is None:
            return
        running = self._loop.isRunning()
        if running:
            self._loop.cancel()
        self.init_projection()
        self.graph.update_coordinates()
        self.commit()
        if running:
            self._start()

    def _start(self):
        def update_freeviz(anchors):
            while True:
                self.projection = self.projector(self.effective_data)
                _anchors = self.projector.components_.T
                self.projector.initial = _anchors
                yield _anchors
                if np.allclose(anchors, _anchors, rtol=1e-5, atol=1e-4):
                    return
                anchors = _anchors

        self.graph.set_sample_size(self.SAMPLE_SIZE)
        self._loop.setCoroutine(update_freeviz(self.projector.components_.T))
        self.btn_start.setText("Stop")
        self.progressBarInit()
        self.setBlocking(True)
        self.setStatusMessage("Optimizing")

    def __set_projection(self, _):
        # Set/update the projection matrix and coordinate embeddings
        self.progressBarAdvance(100. / self.MAX_ITERATIONS)
        self.graph.update_coordinates()

    def __freeviz_finished(self):
        self.graph.set_sample_size(None)
        self.btn_start.setText("Optimize")
        self.setStatusMessage("")
        self.setBlocking(False)
        self.progressBarFinished()
        self.commit()

    def __on_error(self, err):
        sys.excepthook(type(err), err, getattr(err, "__traceback__"))

    def check_data(self):
        def error(err):
            err()
            self.data = None

        super().check_data()
        if self.data is not None:
            class_var, domain = self.data.domain.class_var, self.data.domain
            if class_var is None:
                error(self.Error.no_class_var)
            elif class_var.is_discrete and len(np.unique(self.data.Y)) < 2:
                error(self.Error.not_enough_class_vars)
            elif len(self.data.domain.attributes) < 2:
                error(self.Error.not_enough_features)
            elif len(self.data.domain.attributes) > self.data.X.shape[0]:
                error(self.Error.features_exceeds_instances)
            elif not np.sum(np.std(self.data.X, axis=0)):
                error(self.Error.constant_data)
            elif np.sum(self.valid_data) > self.MAX_INSTANCES:
                error(self.Error.too_many_data_instances)
            else:
                if len(self.effective_variables) < len(domain.attributes):
                    self.Warning.removed_features()
        self.btn_start.setEnabled(self.data is not None)

    def set_data(self, data):
        super().set_data(data)
        if self.data is not None:
            self.init_projection()

    def init_projection(self):
        anchors = FreeViz.init_radial(len(self.effective_variables)) \
            if self.initialization == InitType.Circular \
            else FreeViz.init_random(len(self.effective_variables), 2)
        self.projector = FreeViz(scale=False, center=False,
                                 initial=anchors, maxiter=10)
        data = self.projector.preprocess(self.effective_data)
        self.projector.domain = data.domain
        self.projector.components_ = anchors.T
        self.projection = FreeVizModel(self.projector, self.projector.domain, 2)
        self.projection.pre_domain = data.domain
        self.projection.name = self.projector.name

    def get_coordinates_data(self):
        embedding = self.get_embedding()
        if embedding is None:
            return None, None
        valid_emb = embedding[self.valid_data]
        return valid_emb.T / (np.max(np.linalg.norm(valid_emb, axis=1)) or 1)

    def _manual_move(self, anchor_idx, x, y):
        self.projector.initial[anchor_idx] = [x, y]
        super()._manual_move(anchor_idx, x, y)

    def clear(self):
        super().clear()
        self._loop.cancel()

    @classmethod
    def migrate_settings(cls, _settings, version):
        if version < 3:
            if "radius" in _settings:
                _settings["graph"]["hide_radius"] = _settings["radius"]

    @classmethod
    def migrate_context(cls, context, version):
        if version < 3:
            values = context.values
            values["attr_color"] = values["graph"]["attr_color"]
            values["attr_size"] = values["graph"]["attr_size"]
            values["attr_shape"] = values["graph"]["attr_shape"]
            values["attr_label"] = values["graph"]["attr_label"]
Exemplo n.º 17
0
class OWFreeViz(OWAnchorProjectionWidget, ConcurrentWidgetMixin):
    MAX_INSTANCES = 10000

    name = "FreeViz"
    description = "Displays FreeViz projection"
    icon = "icons/Freeviz.svg"
    priority = 240
    keywords = ["viz"]

    settings_version = 3
    initialization = settings.Setting(InitType.Circular)
    GRAPH_CLASS = OWFreeVizGraph
    graph = settings.SettingProvider(OWFreeVizGraph)

    left_side_scrolling = True

    class Error(OWAnchorProjectionWidget.Error):
        no_class_var = widget.Msg("Data has no target variable")
        not_enough_class_vars = widget.Msg(
            "Target variable is not at least binary")
        features_exceeds_instances = widget.Msg(
            "Number of features exceeds the number of instances.")
        too_many_data_instances = widget.Msg("Data is too large.")
        constant_data = widget.Msg("All data columns are constant.")
        not_enough_features = widget.Msg("At least two features are required")

    class Warning(OWAnchorProjectionWidget.Warning):
        removed_features = widget.Msg("Categorical features with more than"
                                      " two values are not shown.")

    def __init__(self):
        OWAnchorProjectionWidget.__init__(self)
        ConcurrentWidgetMixin.__init__(self)

    def _add_controls(self):
        self.__add_controls_start_box()
        super()._add_controls()
        self.gui.add_control(
            self._effects_box, gui.hSlider, "Hide radius:", master=self.graph,
            value="hide_radius", minValue=0, maxValue=100, step=10,
            createLabel=False, callback=self.__radius_slider_changed
        )

    def __add_controls_start_box(self):
        box = gui.vBox(self.controlArea, box=True)
        gui.comboBox(
            box, self, "initialization", label="Initialization:",
            items=InitType.items(), orientation=Qt.Horizontal,
            labelWidth=90, callback=self.__init_combo_changed)
        self.run_button = gui.button(box, self, "Start", self._toggle_run)

    @property
    def effective_variables(self):
        return [a for a in self.data.domain.attributes
                if a.is_continuous or a.is_discrete and len(a.values) == 2]

    def __radius_slider_changed(self):
        self.graph.update_radius()

    def __init_combo_changed(self):
        self.Error.proj_error.clear()
        self.init_projection()
        self.setup_plot()
        self.commit()
        if self.task is not None:
            self._run()

    def _toggle_run(self):
        if self.task is not None:
            self.cancel()
            self.graph.set_sample_size(None)
            self.run_button.setText("Resume")
            self.commit()
        else:
            self._run()

    def _run(self):
        if self.data is None:
            return
        self.graph.set_sample_size(self.SAMPLE_SIZE)
        self.run_button.setText("Stop")
        self.start(run_freeviz, self.effective_data, self.projector)

    # ConcurrentWidgetMixin
    def on_partial_result(self, result: Result):
        assert isinstance(result.projector, FreeViz)
        assert isinstance(result.projection, FreeVizModel)
        self.projector = result.projector
        self.projection = result.projection
        self.graph.update_coordinates()
        self.graph.update_density()

    def on_done(self, result: Result):
        assert isinstance(result.projector, FreeViz)
        assert isinstance(result.projection, FreeVizModel)
        self.projector = result.projector
        self.projection = result.projection
        self.graph.set_sample_size(None)
        self.run_button.setText("Start")
        self.commit()

    def on_exception(self, ex: Exception):
        self.Error.proj_error(ex)
        self.graph.set_sample_size(None)
        self.run_button.setText("Start")

    # OWAnchorProjectionWidget
    def set_data(self, data):
        super().set_data(data)
        if self._invalidated:
            self.init_projection()

    def init_projection(self):
        if self.data is None:
            return
        anchors = FreeViz.init_radial(len(self.effective_variables)) \
            if self.initialization == InitType.Circular \
            else FreeViz.init_random(len(self.effective_variables), 2)
        self.projector = FreeViz(scale=False, center=False,
                                 initial=anchors, maxiter=10)
        data = self.projector.preprocess(self.effective_data)
        self.projector.domain = data.domain
        self.projector.components_ = anchors.T
        self.projection = FreeVizModel(self.projector, self.projector.domain, 2)
        self.projection.pre_domain = data.domain
        self.projection.name = self.projector.name

    def check_data(self):
        def error(err):
            err()
            self.data = None

        super().check_data()
        if self.data is not None:
            class_var, domain = self.data.domain.class_var, self.data.domain
            if class_var is None:
                error(self.Error.no_class_var)
            elif class_var.is_discrete and len(np.unique(self.data.Y)) < 2:
                error(self.Error.not_enough_class_vars)
            elif len(self.data.domain.attributes) < 2:
                error(self.Error.not_enough_features)
            elif len(self.data.domain.attributes) > self.data.X.shape[0]:
                error(self.Error.features_exceeds_instances)
            elif not np.sum(np.std(self.data.X, axis=0)):
                error(self.Error.constant_data)
            elif np.sum(np.all(np.isfinite(self.data.X), axis=1)) > self.MAX_INSTANCES:
                error(self.Error.too_many_data_instances)
            else:
                if len(self.effective_variables) < len(domain.attributes):
                    self.Warning.removed_features()

    def enable_controls(self):
        super().enable_controls()
        self.run_button.setEnabled(self.data is not None)
        self.run_button.setText("Start")

    def get_coordinates_data(self):
        embedding = self.get_embedding()
        if embedding is None:
            return None, None
        valid_emb = embedding[self.valid_data]
        return valid_emb.T / (np.max(np.linalg.norm(valid_emb, axis=1)) or 1)

    def _manual_move(self, anchor_idx, x, y):
        self.projector.initial[anchor_idx] = [x, y]
        super()._manual_move(anchor_idx, x, y)

    def clear(self):
        super().clear()
        self.cancel()

    def onDeleteWidget(self):
        self.shutdown()
        super().onDeleteWidget()

    @classmethod
    def migrate_settings(cls, _settings, version):
        if version < 3:
            if "radius" in _settings:
                _settings["graph"]["hide_radius"] = _settings["radius"]

    @classmethod
    def migrate_context(cls, context, version):
        if version < 3:
            values = context.values
            values["attr_color"] = values["graph"]["attr_color"]
            values["attr_size"] = values["graph"]["attr_size"]
            values["attr_shape"] = values["graph"]["attr_shape"]
            values["attr_label"] = values["graph"]["attr_label"]