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"]
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"]
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"]
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"]