Exemplo n.º 1
0
class CheckBoxPopup(QWidget):
    def __init__(self, var, lc, widget_parent=None, widget=None):
        QWidget.__init__(self)

        self.list_view = QListView()
        text = []
        model = QStandardItemModel(self.list_view)
        for (i, val) in enumerate(var.values):
            item = QStandardItem(val)
            item.setCheckable(True)
            if i + 1 in lc:
                item.setCheckState(Qt.Checked)
                text.append(val)
            model.appendRow(item)
        model.itemChanged.connect(widget_parent.conditions_changed)
        self.list_view.setModel(model)

        layout = QGridLayout(self)
        layout.addWidget(self.list_view)
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        self.adjustSize()
        self.setWindowFlags(Qt.Popup)

        self.widget = widget
        self.widget.desc_text = ', '.join(text)
        self.widget.set_text()

    def moved(self):
        point = self.widget.rect().bottomRight()
        global_point = self.widget.mapToGlobal(point)
        self.move(global_point - QPoint(self.width(), 0))
Exemplo n.º 2
0
    def test_dock_standalone(self):
        widget = QWidget()
        layout = QHBoxLayout()
        widget.setLayout(layout)
        layout.addStretch(1)
        widget.show()

        dock = CollapsibleDockWidget()
        layout.addWidget(dock)
        list_view = QListView()
        list_view.setModel(QStringListModel(["a", "b"], list_view))

        label = QLabel("A label. ")
        label.setWordWrap(True)

        dock.setExpandedWidget(label)
        dock.setCollapsedWidget(list_view)
        dock.setExpanded(True)
        dock.setExpanded(False)

        timer = QTimer(dock, interval=50)
        timer.timeout.connect(lambda: dock.setExpanded(not dock.expanded()))
        timer.start()
        self.qWait()
        timer.stop()
Exemplo n.º 3
0
    def make_list_view(self, callback, append_to):
        data = unc.years()

        list = QListView()
        list.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        model = QStandardItemModel(0, 1)

        for i in range(1, len(data)):
            item = QStandardItem(str(data[i]))
            model.appendRow(item)

        model.itemChanged.connect(callback)
        list.setModel(model)
        list.setEditTriggers(QAbstractItemView.NoEditTriggers)
        list.setSelectionMode(QAbstractItemView.ExtendedSelection)

        proxy_model = FindFilterProxyModel()
        proxy_model.setSourceModel(model)
        proxy_model.setFilterKeyColumn(-1)
        list.setModel(proxy_model)
        list.selectionModel().selectionChanged.connect(callback)

        append_to.layout().addWidget(list)

        return [list, proxy_model, model]
Exemplo n.º 4
0
class CheckBoxPopup(QWidget):
    def __init__(self, var, lc, widget_parent=None, widget=None):
        QWidget.__init__(self)

        self.list_view = QListView()
        text = []
        model = QStandardItemModel(self.list_view)
        for (i, val) in enumerate(var.values):
            item = QStandardItem(val)
            item.setCheckable(True)
            if i + 1 in lc:
                item.setCheckState(Qt.Checked)
                text.append(val)
            model.appendRow(item)
        model.itemChanged.connect(widget_parent.conditions_changed)
        self.list_view.setModel(model)

        layout = QGridLayout(self)
        layout.addWidget(self.list_view)
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        self.adjustSize()
        self.setWindowFlags(Qt.Popup)

        self.widget = widget
        self.widget.desc_text = ', '.join(text)
        self.widget.set_text()

    def moved(self):
        point = self.widget.rect().bottomRight()
        global_point = self.widget.mapToGlobal(point)
        self.move(global_point - QPoint(self.width(), 0))
Exemplo n.º 5
0
    def test_dock_standalone(self):
        widget = QWidget()
        layout = QHBoxLayout()
        widget.setLayout(layout)
        layout.addStretch(1)
        widget.show()

        dock = CollapsibleDockWidget()
        layout.addWidget(dock)
        list_view = QListView()
        list_view.setModel(QStringListModel(["a", "b"], list_view))

        label = QLabel("A label. ")
        label.setWordWrap(True)

        dock.setExpandedWidget(label)
        dock.setCollapsedWidget(list_view)
        dock.setExpanded(True)

        self.app.processEvents()

        def toogle():
            dock.setExpanded(not dock.expanded())
            self.singleShot(2000, toogle)

        toogle()

        self.app.exec_()
Exemplo n.º 6
0
    def test_dock_standalone(self):
        widget = QWidget()
        layout = QHBoxLayout()
        widget.setLayout(layout)
        layout.addStretch(1)
        widget.show()

        dock = CollapsibleDockWidget()
        layout.addWidget(dock)
        list_view = QListView()
        list_view.setModel(QStringListModel(["a", "b"], list_view))

        label = QLabel("A label. ")
        label.setWordWrap(True)

        dock.setExpandedWidget(label)
        dock.setCollapsedWidget(list_view)
        dock.setExpanded(True)

        self.app.processEvents()

        def toogle():
            dock.setExpanded(not dock.expanded())
            self.singleShot(2000, toogle)

        toogle()

        self.app.exec_()
Exemplo n.º 7
0
    def __init__(self):
        # Commit is thunked because autocommit redefines it
        # pylint: disable=unnecessary-lambda
        super().__init__()
        self.data = None

        list_args = dict(alternatingRowColors=True,
                         dragEnabled=True,
                         dragDropMode=QListView.DragDrop,
                         acceptDrops=True,
                         defaultDropAction=Qt.MoveAction,
                         showDropIndicator=True,
                         selectionMode=QListView.ExtendedSelection,
                         selectionBehavior=QListView.SelectRows)

        hbox = gui.hBox(self.controlArea)

        listview_avail = QListView(self, **list_args)
        self.model_avail = VariableListModel(parent=self, enable_dnd=True)
        listview_avail.setModel(self.model_avail)
        gui.vBox(hbox,
                 'Available Variables').layout().addWidget(listview_avail)

        listview_key = QListView(self, **list_args)
        self.model_key = VariableListModel(parent=self, enable_dnd=True)
        listview_key.setModel(self.model_key)
        self.model_key.rowsInserted.connect(lambda: self.commit())
        self.model_key.rowsRemoved.connect(lambda: self.commit())
        gui.vBox(hbox, 'Group by Variables').layout().addWidget(listview_key)

        gui.comboBox(self.controlArea,
                     self,
                     'tiebreaker',
                     box="Item Selection",
                     label='Instance to select in each group:',
                     orientation=Qt.Horizontal,
                     items=tuple(self.TIEBREAKERS),
                     callback=lambda: self.commit(),
                     sendSelectedValue=True,
                     sizeAdjustPolicy=QComboBox.AdjustToContents,
                     minimumContentsLength=20,
                     sizePolicy=(QSizePolicy.Minimum, QSizePolicy.Preferred))
        gui.auto_commit(self.controlArea,
                        self,
                        'autocommit',
                        'Commit',
                        orientation=Qt.Horizontal)
Exemplo n.º 8
0
    def test_dock_standalone(self):
        widget = QWidget()
        layout = QHBoxLayout()
        widget.setLayout(layout)
        layout.addStretch(1)
        widget.show()

        dock = CollapsibleDockWidget()
        layout.addWidget(dock)
        list_view = QListView()
        list_view.setModel(QStringListModel(["a", "b"], list_view))

        label = QLabel("A label. ")
        label.setWordWrap(True)

        dock.setExpandedWidget(label)
        dock.setCollapsedWidget(list_view)
        dock.setExpanded(True)
        dock.setExpanded(False)

        timer = QTimer(dock, interval=200)
        timer.timeout.connect(lambda: dock.setExpanded(not dock.expanded()))
        timer.start()
Exemplo n.º 9
0
    def __setupUi(self):
        """Set up the UI.
        """
        if self.__macUnified:
            self.tab = QToolBar(
                floatable=False, movable=False, allowedAreas=Qt.TopToolBarArea,
            )
            self.addToolBar(Qt.TopToolBarArea, self.tab)
            self.setUnifiedTitleAndToolBarOnMac(True)

            # This does not seem to work
            self.setWindowFlags(self.windowFlags() & \
                                ~Qt.MacWindowToolBarButtonHint)

            self.tab.actionTriggered[QAction].connect(
                self.__macOnToolBarAction
            )

            central = QStackedWidget()

            central.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        else:
            self.tab = central = QTabWidget(self)

        # Add a close button to the bottom of the dialog
        # (to satisfy GNOME 3 which shows the dialog  without a title bar).
        container = container_widget_helper()
        container.layout().addWidget(central)
        buttonbox = QDialogButtonBox(QDialogButtonBox.Close)
        buttonbox.rejected.connect(self.close)
        container.layout().addWidget(buttonbox)

        self.setCentralWidget(container)

        self.stack = central

        # General Tab
        tab = QWidget()
        self.addTab(tab, self.tr("常规"),
                    toolTip=self.tr("常规选项"))

        form = FormLayout()
        tab.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)

        nodes = QWidget(self, objectName="nodes")
        nodes.setLayout(QVBoxLayout())
        nodes.layout().setContentsMargins(0, 0, 0, 0)

        cb_anim = QCheckBox(
            self.tr("启用节点动画"),
            objectName="enable-node-animations",
            toolTip=self.tr("为工作流中的节点启用阴影和ping动画。")
        )
        self.bind(cb_anim, "checked", "schemeedit/enable-node-animations")
        nodes.layout().addWidget(cb_anim)

        form.addRow(self.tr("结点"), nodes)

        links = QWidget(self, objectName="links")
        links.setLayout(QVBoxLayout())
        links.layout().setContentsMargins(0, 0, 0, 0)

        cb_show = QCheckBox(
            self.tr("在窗口小部件之间显示通道名称"),
            objectName="show-channel-names",
            toolTip=self.tr("在链接上显示源和接收器通道名称。")
        )

        self.bind(cb_show, "checked", "schemeedit/show-channel-names")

        links.layout().addWidget(cb_show)

        form.addRow(self.tr("链接"), links)

        quickmenu = QWidget(self, objectName="quickmenu-options")
        quickmenu.setLayout(QVBoxLayout())
        quickmenu.layout().setContentsMargins(0, 0, 0, 0)

        cb1 = QCheckBox(self.tr("双击时打开"),
                        toolTip=self.tr("双击画布中的空白位置打开快捷菜单"))

        cb2 = QCheckBox(self.tr("单击鼠标右键时打开"),
                        toolTip=self.tr("右键单击画布中的空白处打开快捷菜单"))

        cb3 = QCheckBox(self.tr("按空格键时打开"),
                        toolTip=self.tr("当鼠标悬停在画布上时,按空格键。"))

        cb4 = QCheckBox(self.tr("按任意按键时打开"),
                        toolTip=self.tr("当鼠标悬停在画布上时,按任意键。"))

        cb5 = QCheckBox(self.tr("显示分类"),
                        toolTip=self.tr("In addition to searching, allow filtering "
                                        "by categories."))

        self.bind(cb1, "checked", "quickmenu/trigger-on-double-click")
        self.bind(cb2, "checked", "quickmenu/trigger-on-right-click")
        self.bind(cb3, "checked", "quickmenu/trigger-on-space-key")
        self.bind(cb4, "checked", "quickmenu/trigger-on-any-key")
        self.bind(cb5, "checked", "quickmenu/show-categories")

        quickmenu.layout().addWidget(cb1)
        quickmenu.layout().addWidget(cb2)
        quickmenu.layout().addWidget(cb3)
        quickmenu.layout().addWidget(cb4)
        quickmenu.layout().addWidget(cb5)

        form.addRow(self.tr("快捷菜单"), quickmenu)

        startup = QWidget(self, objectName="startup-group")
        startup.setLayout(QVBoxLayout())
        startup.layout().setContentsMargins(0, 0, 0, 0)

        cb_splash = QCheckBox(self.tr("显示启动画面"), self,
                              objectName="show-splash-screen")

        cb_welcome = QCheckBox(self.tr("显示欢迎界面"), self,
                               objectName="show-welcome-screen")

        cb_crash = QCheckBox(self.tr("加载崩溃的工作流"), self,
                             objectName="load-crashed-workflows")

        self.bind(cb_splash, "checked", "startup/show-splash-screen")
        self.bind(cb_welcome, "checked", "startup/show-welcome-screen")
        self.bind(cb_crash, "checked", "startup/load-crashed-workflows")

        startup.layout().addWidget(cb_splash)
        startup.layout().addWidget(cb_welcome)
        startup.layout().addWidget(cb_crash)

        form.addRow(self.tr("启动时"), startup)

        toolbox = QWidget(self, objectName="toolbox-group")
        toolbox.setLayout(QVBoxLayout())
        toolbox.layout().setContentsMargins(0, 0, 0, 0)

        exclusive = QCheckBox(self.tr("一次只能打开一个选项卡"))

        self.bind(exclusive, "checked", "mainwindow/toolbox-dock-exclusive")

        toolbox.layout().addWidget(exclusive)

        form.addRow(self.tr("工具箱"), toolbox)
        tab.setLayout(form)

        # Style tab
        tab = StyleConfigWidget()
        self.addTab(tab, self.tr("&Style"), toolTip="Application style")
        self.bind(tab, "selectedStyle_", "application-style/style-name")
        self.bind(tab, "selectedPalette_", "application-style/palette")

        # Output Tab
        tab = QWidget()
        self.addTab(tab, self.tr("输出"),
                    toolTip="输出重定向")

        form = FormLayout()

        combo = QComboBox()
        combo.addItems([self.tr("关键"),
                        self.tr("错误"),
                        self.tr("警告"),
                        self.tr("信息"),
                        self.tr("调试")])
        self.bind(combo, "currentIndex", "logging/level")
        form.addRow(self.tr("记录"), combo)
        box = QWidget()
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        cb1 = QCheckBox(self.tr("在外部浏览器中打开"),
                        objectName="open-in-external-browser")
        self.bind(cb1, "checked", "help/open-in-external-browser")
        layout.addWidget(cb1)
        box.setLayout(layout)
        form.addRow(self.tr("帮助窗口"), box)

        tab.setLayout(form)

        # Categories Tab
        tab = QWidget()
        layout = QVBoxLayout()
        view = QListView(
            editTriggers=QListView.NoEditTriggers
        )
        from .. import registry
        reg = registry.global_registry()
        model = QStandardItemModel()
        settings = QSettings()
        for cat in reg.categories():
            item = QStandardItem()
            item.setText(cat.name)
            item.setCheckable(True)
            visible, _ = category_state(cat, settings)
            item.setCheckState(Qt.Checked if visible else Qt.Unchecked)
            model.appendRow([item])

        view.setModel(model)
        layout.addWidget(view)
        tab.setLayout(layout)
        model.itemChanged.connect(
            lambda item:
                save_category_state(
                    reg.category(str(item.text())),
                    _State(item.checkState() == Qt.Checked, -1),
                    settings
                )
        )

        self.addTab(tab, "类别")

        # Add-ons Tab
        tab = QWidget()
        self.addTab(tab, self.tr("插件"),
                    toolTip="与插件安装相关的设置")

        form = FormLayout()
        conda = QWidget(self, objectName="conda-group")
        conda.setLayout(QVBoxLayout())
        conda.layout().setContentsMargins(0, 0, 0, 0)

        mirror_install = QCheckBox(self.tr("使用国内镜像安装"), self,
                                     objectName="allow-conda")
        self.bind(mirror_install, "checked", "add-ons/allow-conda")
        conda.layout().addWidget(mirror_install)

        form.addRow(self.tr("镜像"), conda)

        form.addRow(self.tr("Pip"), QLabel("Pip 安装参数:"))
        line_edit_pip = QLineEdit()
        # line_edit_pip.setText('-i https://mirrors.aliyun.com/pypi/simple')
        self.bind(line_edit_pip, "text", "add-ons/pip-install-arguments")
        form.addRow("", line_edit_pip)

        tab.setLayout(form)

        # Network Tab
        tab = QWidget()
        self.addTab(tab, self.tr("网络"),
                    toolTip="与网络相关的设置")

        form = FormLayout()
        line_edit_http_proxy = QLineEdit()
        self.bind(line_edit_http_proxy, "text", "network/http-proxy")
        form.addRow("HTTP 代理:", line_edit_http_proxy)
        line_edit_https_proxy = QLineEdit()
        self.bind(line_edit_https_proxy, "text", "network/https-proxy")
        form.addRow("HTTPS 代理:", line_edit_https_proxy)
        tab.setLayout(form)

        if self.__macUnified:
            # Need some sensible size otherwise mac unified toolbar 'takes'
            # the space that should be used for layout of the contents
            self.adjustSize()
class DiscreteVariableEditor(VariableEditor):
    """An editor widget for editing a discrete variable.

    Extends the :class:`VariableEditor` to enable editing of
    variables values.

    """
    def setup_gui(self):
        layout = QVBoxLayout()
        self.setLayout(layout)

        self.main_form = QFormLayout()
        self.main_form.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
        layout.addLayout(self.main_form)

        self._setup_gui_name()
        self._setup_gui_values()
        self._setup_gui_labels()

    def _setup_gui_values(self):
        self.values_edit = QListView()
        self.values_edit.setEditTriggers(QTreeView.CurrentChanged)
        self.values_model = itemmodels.PyListModel(
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable)
        self.values_edit.setModel(self.values_model)

        self.values_model.dataChanged.connect(self.on_values_changed)
        self.main_form.addRow("Values:", self.values_edit)

    def set_data(self, var):
        """Set the variable to edit
        """
        VariableEditor.set_data(self, var)
        self.values_model[:] = list(var.values) if var is not None else []

    def get_data(self):
        """Retrieve the modified variable
        """
        name = str(self.name_edit.text()).strip()
        labels = self.labels_model.get_dict()
        values = map(str, self.values_model)

        if self.var is not None and not self.is_same():
            var = type(self.var)(name, values=values)
            var.attributes.update(labels)
            self.var = var
        else:
            var = self.var

        return var

    def is_same(self):
        """Is the current model state the same as the input.
        """
        values = list(map(str, self.values_model))
        return (VariableEditor.is_same(self) and self.var is not None
                and self.var.values == values)

    def clear(self):
        """Clear the model state.
        """
        VariableEditor.clear(self)
        self.values_model.clear()

    @Slot()
    def on_values_changed(self):
        self.maybe_commit()
Exemplo n.º 11
0
class OWFeatureConstructor(OWWidget):
    name = "Feature Constructor"
    description = "Construct new features (data columns) from a set of " \
                  "existing features in the input data set."
    icon = "icons/FeatureConstructor.svg"

    class Inputs:
        data = Input("Data", Orange.data.Table)

    class Outputs:
        data = Output("Data", Orange.data.Table)

    want_main_area = False

    settingsHandler = FeatureConstructorHandler()
    descriptors = ContextSetting([])
    currentIndex = ContextSetting(-1)

    EDITORS = [
        (ContinuousDescriptor, ContinuousFeatureEditor),
        (DiscreteDescriptor, DiscreteFeatureEditor),
        (StringDescriptor, StringFeatureEditor)
    ]

    class Error(OWWidget.Error):
        more_values_needed = Msg("Categorical feature {} needs more values.")
        invalid_expressions = Msg("Invalid expressions: {}.")

    def __init__(self):
        super().__init__()
        self.data = None
        self.editors = {}

        box = gui.vBox(self.controlArea, "Variable Definitions")

        toplayout = QHBoxLayout()
        toplayout.setContentsMargins(0, 0, 0, 0)
        box.layout().addLayout(toplayout)

        self.editorstack = QStackedWidget(
            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,
                                   QSizePolicy.MinimumExpanding)
        )

        for descclass, editorclass in self.EDITORS:
            editor = editorclass()
            editor.featureChanged.connect(self._on_modified)
            self.editors[descclass] = editor
            self.editorstack.addWidget(editor)

        self.editorstack.setEnabled(False)

        buttonlayout = QVBoxLayout(spacing=10)
        buttonlayout.setContentsMargins(0, 0, 0, 0)

        self.addbutton = QPushButton(
            "New", toolTip="Create a new variable",
            minimumWidth=120,
            shortcut=QKeySequence.New
        )

        def unique_name(fmt, reserved):
            candidates = (fmt.format(i) for i in count(1))
            return next(c for c in candidates if c not in reserved)

        def reserved_names():
            varnames = []
            if self.data is not None:
                varnames = [var.name for var in
                            self.data.domain.variables + self.data.domain.metas]
            varnames += [desc.name for desc in self.featuremodel]
            return set(varnames)

        def generate_newname(fmt):
            return unique_name(fmt, reserved_names())

        menu = QMenu(self.addbutton)
        cont = menu.addAction("Numeric")
        cont.triggered.connect(
            lambda: self.addFeature(
                ContinuousDescriptor(generate_newname("X{}"), "", 3))
        )
        disc = menu.addAction("Categorical")
        disc.triggered.connect(
            lambda: self.addFeature(
                DiscreteDescriptor(generate_newname("D{}"), "",
                                   ("A", "B"), -1, False))
        )
        string = menu.addAction("Text")
        string.triggered.connect(
            lambda: self.addFeature(
                StringDescriptor(generate_newname("S{}"), ""))
        )
        menu.addSeparator()
        self.duplicateaction = menu.addAction("Duplicate Selected Variable")
        self.duplicateaction.triggered.connect(self.duplicateFeature)
        self.duplicateaction.setEnabled(False)
        self.addbutton.setMenu(menu)

        self.removebutton = QPushButton(
            "Remove", toolTip="Remove selected variable",
            minimumWidth=120,
            shortcut=QKeySequence.Delete
        )
        self.removebutton.clicked.connect(self.removeSelectedFeature)

        buttonlayout.addWidget(self.addbutton)
        buttonlayout.addWidget(self.removebutton)
        buttonlayout.addStretch(10)

        toplayout.addLayout(buttonlayout, 0)
        toplayout.addWidget(self.editorstack, 10)

        # Layout for the list view
        layout = QVBoxLayout(spacing=1, margin=0)
        self.featuremodel = DescriptorModel(parent=self)

        self.featureview = QListView(
            minimumWidth=200,
            sizePolicy=QSizePolicy(QSizePolicy.Minimum,
                                   QSizePolicy.MinimumExpanding)
        )

        self.featureview.setItemDelegate(FeatureItemDelegate(self))
        self.featureview.setModel(self.featuremodel)
        self.featureview.selectionModel().selectionChanged.connect(
            self._on_selectedVariableChanged
        )

        layout.addWidget(self.featureview)

        box.layout().addLayout(layout, 1)

        box = gui.hBox(self.controlArea)
        gui.rubber(box)
        commit = gui.button(box, self, "Send", callback=self.apply,
                            default=True)
        commit.setMinimumWidth(180)

    def setCurrentIndex(self, index):
        index = min(index, len(self.featuremodel) - 1)
        self.currentIndex = index
        if index >= 0:
            itemmodels.select_row(self.featureview, index)
            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]
            editor = self.editors[type(desc)]
            self.editorstack.setCurrentWidget(editor)
            editor.setEditorData(desc, self.data.domain if self.data else None)
        self.editorstack.setEnabled(index >= 0)
        self.duplicateaction.setEnabled(index >= 0)
        self.removebutton.setEnabled(index >= 0)

    def _on_selectedVariableChanged(self, selected, *_):
        index = selected_row(self.featureview)
        if index is not None:
            self.setCurrentIndex(index)
        else:
            self.setCurrentIndex(-1)

    def _on_modified(self):
        if self.currentIndex >= 0:
            editor = self.editorstack.currentWidget()
            self.featuremodel[self.currentIndex] = editor.editorData()
            self.descriptors = list(self.featuremodel)

    def setDescriptors(self, descriptors):
        """
        Set a list of variable descriptors to edit.
        """
        self.descriptors = descriptors
        self.featuremodel[:] = list(self.descriptors)

    @Inputs.data
    @check_sql_input
    def setData(self, data=None):
        """Set the input dataset."""
        self.closeContext()

        self.data = data

        if self.data is not None:
            descriptors = list(self.descriptors)
            currindex = self.currentIndex
            self.descriptors = []
            self.currentIndex = -1
            self.openContext(data)

            if descriptors != self.descriptors or \
                    self.currentIndex != currindex:
                # disconnect from the selection model while reseting the model
                selmodel = self.featureview.selectionModel()
                selmodel.selectionChanged.disconnect(
                    self._on_selectedVariableChanged)

                self.featuremodel[:] = list(self.descriptors)
                self.setCurrentIndex(self.currentIndex)

                selmodel.selectionChanged.connect(
                    self._on_selectedVariableChanged)

        self.editorstack.setEnabled(self.currentIndex >= 0)

    def handleNewSignals(self):
        if self.data is not None:
            self.apply()
        else:
            self.Outputs.data.send(None)

    def addFeature(self, descriptor):
        self.featuremodel.append(descriptor)
        self.setCurrentIndex(len(self.featuremodel) - 1)
        editor = self.editorstack.currentWidget()
        editor.nameedit.setFocus()
        editor.nameedit.selectAll()

    def removeFeature(self, index):
        del self.featuremodel[index]
        index = selected_row(self.featureview)
        if index is not None:
            self.setCurrentIndex(index)
        elif index is None and len(self.featuremodel) > 0:
            # Deleting the last item clears selection
            self.setCurrentIndex(len(self.featuremodel) - 1)

    def removeSelectedFeature(self):
        if self.currentIndex >= 0:
            self.removeFeature(self.currentIndex)

    def duplicateFeature(self):
        desc = self.featuremodel[self.currentIndex]
        self.addFeature(copy.deepcopy(desc))

    def check_attrs_values(self, attr, data):
        for i in range(len(data)):
            for var in attr:
                if not math.isnan(data[i, var]) \
                        and int(data[i, var]) >= len(var.values):
                    return var.name
        return None

    def _validate_descriptors(self, desc):

        def validate(source):
            try:
                return validate_exp(ast.parse(source, mode="eval"))
            except Exception:
                return False

        final = []
        invalid = []
        for d in desc:
            if validate(d.expression):
                final.append(d)
            else:
                final.append(d._replace(expression=""))
                invalid.append(d)

        if invalid:
            self.Error.invalid_expressions(", ".join(s.name for s in invalid))

        return final

    def apply(self):
        self.Error.clear()

        if self.data is None:
            return

        desc = list(self.featuremodel)
        desc = self._validate_descriptors(desc)
        source_vars = self.data.domain.variables + self.data.domain.metas
        new_variables = construct_variables(desc, source_vars)

        attrs = [var for var in new_variables if var.is_primitive()]
        metas = [var for var in new_variables if not var.is_primitive()]
        new_domain = Orange.data.Domain(
            self.data.domain.attributes + tuple(attrs),
            self.data.domain.class_vars,
            metas=self.data.domain.metas + tuple(metas)
        )

        try:
            data = self.data.transform(new_domain)
        except Exception as err:
            log = logging.getLogger(__name__)
            log.error("", exc_info=True)
            self.error("".join(format_exception_only(type(err), err)).rstrip())
            return
        disc_attrs_not_ok = self.check_attrs_values(
            [var for var in attrs if var.is_discrete], data)
        if disc_attrs_not_ok:
            self.Error.more_values_needed(disc_attrs_not_ok)
            return

        self.Outputs.data.send(data)

    def send_report(self):
        items = OrderedDict()
        for feature in self.featuremodel:
            if isinstance(feature, DiscreteDescriptor):
                items[feature.name] = "{} (categorical with values {}{})".format(
                    feature.expression, feature.values,
                    "; ordered" * feature.ordered)
            elif isinstance(feature, ContinuousDescriptor):
                items[feature.name] = "{} (numeric)".format(feature.expression)
            else:
                items[feature.name] = "{} (text)".format(feature.expression)
        self.report_items(
            report.plural("Constructed feature{s}", len(items)), items)
Exemplo n.º 12
0
class SelectionSetsWidget(QFrame):
    """
    Widget for managing multiple stored item selections
    """
    selectionModified = Signal(bool)

    def __init__(self, parent):
        QFrame.__init__(self, parent)
        self.setContentsMargins(0, 0, 0, 0)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(1)
        self._setNameLineEdit = QLineEdit(self)
        layout.addWidget(self._setNameLineEdit)

        self._setListView = QListView(self)
        self._listModel = QStandardItemModel(self)
        self._proxyModel = QSortFilterProxyModel(self)
        self._proxyModel.setSourceModel(self._listModel)

        self._setListView.setModel(self._proxyModel)
        self._setListView.setItemDelegate(ListItemDelegate(self))

        self._setNameLineEdit.textChanged.connect(
            self._proxyModel.setFilterFixedString)

        self._completer = QCompleter(self._listModel, self)

        self._setNameLineEdit.setCompleter(self._completer)

        self._listModel.itemChanged.connect(self._onSetNameChange)
        layout.addWidget(self._setListView)
        buttonLayout = QHBoxLayout()

        self._addAction = QAction(
            "+", self, toolTip="Add a new sort key")
        self._updateAction = QAction(
            "Update", self, toolTip="Update/save current selection")
        self._removeAction = QAction(
            "\u2212", self, toolTip="Remove selected sort key.")

        self._addToolButton = QToolButton(self)
        self._updateToolButton = QToolButton(self)
        self._removeToolButton = QToolButton(self)
        self._updateToolButton.setSizePolicy(
                QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)

        self._addToolButton.setDefaultAction(self._addAction)
        self._updateToolButton.setDefaultAction(self._updateAction)
        self._removeToolButton.setDefaultAction(self._removeAction)

        buttonLayout.addWidget(self._addToolButton)
        buttonLayout.addWidget(self._updateToolButton)
        buttonLayout.addWidget(self._removeToolButton)

        layout.addLayout(buttonLayout)
        self.setLayout(layout)

        self._addAction.triggered.connect(self.addCurrentSelection)
        self._updateAction.triggered.connect(self.updateSelectedSelection)
        self._removeAction.triggered.connect(self.removeSelectedSelection)

        self._setListView.selectionModel().selectionChanged.connect(
            self._onListViewSelectionChanged)
        self.selectionModel = None
        self._selections = []

    def sizeHint(self):
        size = QFrame.sizeHint(self)
        return QSize(size.width(), 150)

    def _onSelectionChanged(self, selected, deselected):
        self.setSelectionModified(True)

    def _onListViewSelectionChanged(self, selected, deselected):
        try:
            index = self._setListView.selectedIndexes()[0]
        except IndexError:
            return
        self.commitSelection(self._proxyModel.mapToSource(index).row())

    def _onSetNameChange(self, item):
        self.selections[item.row()].name = str(item.text())

    def _setButtonStates(self, val):
        self._updateToolButton.setEnabled(val)

    def setSelectionModel(self, selectionModel):
        if self.selectionModel:
            self.selectionModel.selectionChanged.disconnect(
                self._onSelectionChanged)
        self.selectionModel = selectionModel
        self.selectionModel.selectionChanged.connect(self._onSelectionChanged)

    def addCurrentSelection(self):
        item = self.addSelection(
            SelectionByKey(self.selectionModel.selection(),
                           name="New selection",
                           key=(1, 2, 3, 10)))
        index = self._proxyModel.mapFromSource(item.index())
        self._setListView.setCurrentIndex(index)
        self._setListView.edit(index)
        self.setSelectionModified(False)

    def removeSelectedSelection(self):
        i = self._proxyModel.mapToSource(self._setListView.currentIndex()).row()
        self._listModel.takeRow(i)
        del self.selections[i]

    def updateCurentSelection(self):
        i = self._proxyModel.mapToSource(self._setListView.selectedIndex()).row()
        self.selections[i].setSelection(self.selectionModel.selection())
        self.setSelectionModified(False)

    def addSelection(self, selection, name=""):
        self._selections.append(selection)
        item = QStandardItem(selection.name)
        item.setFlags(item.flags() ^ Qt.ItemIsDropEnabled)
        self._listModel.appendRow(item)
        self.setSelectionModified(False)
        return item

    def updateSelectedSelection(self):
        i = self._proxyModel.mapToSource(self._setListView.currentIndex()).row()
        self.selections[i].setSelection(self.selectionModel.selection())
        self.setSelectionModified(False)

    def setSelectionModified(self, val):
        self._selectionModified = val
        self._setButtonStates(val)
        self.selectionModified.emit(bool(val))

    def commitSelection(self, index):
        selection = self.selections[index]
        selection.select(self.selectionModel)

    def setSelections(self, selections):
        self._listModel.clear()
        for selection in selections:
            self.addSelection(selection)

    def selections(self):
        return self._selections

    selections = property(selections, setSelections)
Exemplo n.º 13
0
class OWImpute(OWWidget):
    name = "Impute"
    description = "Impute missing values in the data table."
    icon = "icons/Impute.svg"
    priority = 2130

    class Inputs:
        data = Input("Data", Orange.data.Table)
        learner = Input("Learner", Learner)

    class Outputs:
        data = Output("Data", Orange.data.Table)

    class Error(OWWidget.Error):
        imputation_failed = Msg("Imputation failed for '{}'")
        model_based_imputer_sparse = Msg("Model based imputer does not work for sparse data")

    DEFAULT_LEARNER = SimpleTreeLearner()
    METHODS = [AsDefault(), impute.DoNotImpute(), impute.Average(),
               impute.AsValue(), impute.Model(DEFAULT_LEARNER), impute.Random(),
               impute.DropInstances(), impute.Default()]
    DEFAULT, DO_NOT_IMPUTE, MODEL_BASED_IMPUTER, AS_INPUT = 0, 1, 4, 7

    settingsHandler = settings.DomainContextHandler()

    _default_method_index = settings.Setting(DO_NOT_IMPUTE)
    variable_methods = settings.ContextSetting({})
    autocommit = settings.Setting(True)

    want_main_area = False
    resizing_enabled = False

    def __init__(self):
        super().__init__()
        # copy METHODS (some are modified by the widget)
        self.methods = copy.deepcopy(OWImpute.METHODS)

        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(10, 10, 10, 10)
        self.controlArea.layout().addLayout(main_layout)

        box = QGroupBox(title=self.tr("Default Method"), flat=False)
        box_layout = QVBoxLayout(box)
        main_layout.addWidget(box)

        button_group = QButtonGroup()
        button_group.buttonClicked[int].connect(self.set_default_method)
        for i, method in enumerate(self.methods):
            if not method.columns_only:
                button = QRadioButton(method.name)
                button.setChecked(i == self.default_method_index)
                button_group.addButton(button, i)
                box_layout.addWidget(button)

        self.default_button_group = button_group

        box = QGroupBox(title=self.tr("Individual Attribute Settings"),
                        flat=False)
        main_layout.addWidget(box)

        horizontal_layout = QHBoxLayout(box)
        main_layout.addWidget(box)

        self.varview = QListView(
            selectionMode=QListView.ExtendedSelection
        )
        self.varview.setItemDelegate(DisplayFormatDelegate())
        self.varmodel = itemmodels.VariableListModel()
        self.varview.setModel(self.varmodel)
        self.varview.selectionModel().selectionChanged.connect(
            self._on_var_selection_changed
        )
        self.selection = self.varview.selectionModel()

        horizontal_layout.addWidget(self.varview)

        method_layout = QVBoxLayout()
        horizontal_layout.addLayout(method_layout)

        button_group = QButtonGroup()
        for i, method in enumerate(self.methods):
            button = QRadioButton(text=method.name)
            button_group.addButton(button, i)
            method_layout.addWidget(button)

        self.value_combo = QComboBox(
            minimumContentsLength=8,
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLength,
            activated=self._on_value_selected
            )
        self.value_double = QDoubleSpinBox(
            editingFinished=self._on_value_selected,
            minimum=-1000., maximum=1000., singleStep=.1, decimals=3,
            )
        self.value_stack = value_stack = QStackedWidget()
        value_stack.addWidget(self.value_combo)
        value_stack.addWidget(self.value_double)
        method_layout.addWidget(value_stack)

        button_group.buttonClicked[int].connect(
            self.set_method_for_current_selection
        )

        method_layout.addStretch(2)

        reset_button = QPushButton(
                "Restore All to Default", checked=False, checkable=False,
                clicked=self.reset_variable_methods, default=False,
                autoDefault=False)
        method_layout.addWidget(reset_button)

        self.variable_button_group = button_group

        box = gui.auto_commit(
            self.controlArea, self, "autocommit", "Apply",
            orientation=Qt.Horizontal, checkbox_label="Apply automatically")
        box.layout().insertSpacing(0, 80)
        box.layout().insertWidget(0, self.report_button)

        self.data = None
        self.learner = None
        self.modified = False
        self.default_method = self.methods[self.default_method_index]
        self.executor = qconcurrent.ThreadExecutor(self)
        self.__task = None

    @property
    def default_method_index(self):
        return self._default_method_index

    @default_method_index.setter
    def default_method_index(self, index):
        if self._default_method_index != index:
            self._default_method_index = index
            self.default_button_group.button(index).setChecked(True)
            self.default_method = self.methods[self.default_method_index]
            self.methods[self.DEFAULT].method = self.default_method

            # update variable view
            for index in map(self.varmodel.index, range(len(self.varmodel))):
                method = self.variable_methods.get(
                    index.row(), self.methods[self.DEFAULT])
                self.varmodel.setData(index, method, Qt.UserRole)
            self._invalidate()

    def set_default_method(self, index):
        """Set the current selected default imputation method.
        """
        self.default_method_index = index

    @Inputs.data
    @check_sql_input
    def set_data(self, data):
        self.closeContext()
        self.varmodel[:] = []
        self.variable_methods = {}
        self.modified = False
        self.data = data

        if data is not None:
            self.varmodel[:] = data.domain.variables
            self.openContext(data.domain)

        self.update_varview()
        self.unconditional_commit()

    @Inputs.learner
    def set_learner(self, learner):
        self.learner = learner or self.DEFAULT_LEARNER
        imputer = self.methods[self.MODEL_BASED_IMPUTER]
        imputer.learner = self.learner

        button = self.default_button_group.button(self.MODEL_BASED_IMPUTER)
        button.setText(imputer.name)

        variable_button = self.variable_button_group.button(self.MODEL_BASED_IMPUTER)
        variable_button.setText(imputer.name)

        if learner is not None:
            self.default_method_index = self.MODEL_BASED_IMPUTER

        self.update_varview()
        self.commit()

    def get_method_for_column(self, column_index):
        """Returns the imputation method for column by its index.
        """
        if not isinstance(column_index, int):
            column_index = column_index.row()

        return self.variable_methods.get(column_index,
                                         self.methods[self.DEFAULT])

    def _invalidate(self):
        self.modified = True
        if self.__task is not None:
            self.cancel()
        self.commit()

    def commit(self):
        self.cancel()
        self.warning()
        self.Error.imputation_failed.clear()
        self.Error.model_based_imputer_sparse.clear()

        if self.data is None or len(self.data) == 0 or len(self.varmodel) == 0:
            self.Outputs.data.send(self.data)
            self.modified = False
            return

        data = self.data
        impute_state = [
            (i, var, self.variable_methods.get(i, self.default_method))
            for i, var in enumerate(self.varmodel)
        ]

        def impute_one(method, var, data):
            # type: (impute.BaseImputeMethod, Variable, Table) -> Any
            if isinstance(method, impute.Model) and data.is_sparse():
                raise SparseNotSupported()
            elif isinstance(method, impute.DropInstances):
                return RowMask(method(data, var))
            elif not method.supports_variable(var):
                raise VariableNotSupported(var)
            else:
                return method(data, var)

        futures = []
        for _, var, method in impute_state:
            f = self.executor.submit(
                impute_one, copy.deepcopy(method), var, data)
            futures.append(f)

        w = qconcurrent.FutureSetWatcher(futures)
        w.doneAll.connect(self.__commit_finish)
        w.progressChanged.connect(self.__progress_changed)
        self.__task = Task(futures, w)
        self.progressBarInit(processEvents=False)
        self.setBlocking(True)

    @Slot()
    def __commit_finish(self):
        assert QThread.currentThread() is self.thread()
        assert self.__task is not None
        futures = self.__task.futures
        assert len(futures) == len(self.varmodel)
        assert self.data is not None

        self.__task = None
        self.setBlocking(False)
        self.progressBarFinished()

        data = self.data
        attributes = []
        class_vars = []
        drop_mask = np.zeros(len(self.data), bool)

        for i, (var, fut) in enumerate(zip(self.varmodel, futures)):
            assert fut.done()
            newvar = []
            try:
                res = fut.result()
            except SparseNotSupported:
                self.Error.model_based_imputer_sparse()
                # ?? break
            except VariableNotSupported:
                self.warning("Default method can not handle '{}'".
                             format(var.name))
            except Exception:  # pylint: disable=broad-except
                log = logging.getLogger(__name__)
                log.info("Error for %s", var, exc_info=True)
                self.Error.imputation_failed(var.name)
                attributes = class_vars = None
                break
            else:
                if isinstance(res, RowMask):
                    drop_mask |= res.mask
                    newvar = var
                else:
                    newvar = res

            if isinstance(newvar, Orange.data.Variable):
                newvar = [newvar]

            if i < len(data.domain.attributes):
                attributes.extend(newvar)
            else:
                class_vars.extend(newvar)

        if attributes is None:
            data = None
        else:
            domain = Orange.data.Domain(attributes, class_vars,
                                        data.domain.metas)
            try:
                data = self.data.from_table(domain, data[~drop_mask])
            except Exception:  # pylint: disable=broad-except
                log = logging.getLogger(__name__)
                log.info("Error", exc_info=True)
                self.Error.imputation_failed("Unknown")
                data = None

        self.Outputs.data.send(data)
        self.modified = False

    @Slot(int, int)
    def __progress_changed(self, n, d):
        assert QThread.currentThread() is self.thread()
        assert self.__task is not None
        self.progressBarSet(100. * n / d)

    def cancel(self):
        if self.__task is not None:
            task, self.__task = self.__task, None
            task.cancel()
            task.watcher.doneAll.disconnect(self.__commit_finish)
            task.watcher.progressChanged.disconnect(self.__progress_changed)
            concurrent.futures.wait(task.futures)
            task.watcher.flush()
            self.progressBarFinished()
            self.setBlocking(False)

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

    def send_report(self):
        specific = []
        for i, var in enumerate(self.varmodel):
            method = self.variable_methods.get(i, None)
            if method is not None:
                specific.append("{} ({})".format(var.name, str(method)))

        default = self.default_method.name
        if specific:
            self.report_items((
                ("Default method", default),
                ("Specific imputers", ", ".join(specific))
            ))
        else:
            self.report_items((("Method", default),))

    def _on_var_selection_changed(self):
        indexes = self.selection.selectedIndexes()
        methods = [self.get_method_for_column(i.row()) for i in indexes]

        def method_key(method):
            """
            Decompose method into its type and parameters.
            """
            # The return value should be hashable and  __eq__ comparable
            if isinstance(method, AsDefault):
                return AsDefault, (method.method,)
            elif isinstance(method, impute.Model):
                return impute.Model, (method.learner,)
            elif isinstance(method, impute.Default):
                return impute.Default, (method.default,)
            else:
                return type(method), None

        methods = set(method_key(m) for m in methods)
        selected_vars = [self.varmodel[index.row()] for index in indexes]
        has_discrete = any(var.is_discrete for var in selected_vars)
        fixed_value = None
        value_stack_enabled = False
        current_value_widget = None

        if len(methods) == 1:
            method_type, parameters = methods.pop()
            for i, m in enumerate(self.methods):
                if method_type == type(m):
                    self.variable_button_group.button(i).setChecked(True)

            if method_type is impute.Default:
                (fixed_value,) = parameters

        elif self.variable_button_group.checkedButton() is not None:
            # Uncheck the current button
            self.variable_button_group.setExclusive(False)
            self.variable_button_group.checkedButton().setChecked(False)
            self.variable_button_group.setExclusive(True)
            assert self.variable_button_group.checkedButton() is None

        for method, button in zip(self.methods,
                                  self.variable_button_group.buttons()):
            enabled = all(method.supports_variable(var) for var in
                          selected_vars)
            button.setEnabled(enabled)

        if not has_discrete:
            value_stack_enabled = True
            current_value_widget = self.value_double
        elif len(selected_vars) == 1:
            value_stack_enabled = True
            current_value_widget = self.value_combo
            self.value_combo.clear()
            self.value_combo.addItems(selected_vars[0].values)
        else:
            value_stack_enabled = False
            current_value_widget = None
            self.variable_button_group.button(self.AS_INPUT).setEnabled(False)

        self.value_stack.setEnabled(value_stack_enabled)
        if current_value_widget is not None:
            self.value_stack.setCurrentWidget(current_value_widget)
            if fixed_value is not None:
                if current_value_widget is self.value_combo:
                    self.value_combo.setCurrentIndex(fixed_value)
                elif current_value_widget is self.value_double:
                    self.value_double.setValue(fixed_value)
                else:
                    assert False

    def set_method_for_current_selection(self, method_index):
        indexes = self.selection.selectedIndexes()
        self.set_method_for_indexes(indexes, method_index)

    def set_method_for_indexes(self, indexes, method_index):
        if method_index == self.DEFAULT:
            for index in indexes:
                self.variable_methods.pop(index.row(), None)
        elif method_index == OWImpute.AS_INPUT:
            current = self.value_stack.currentWidget()
            if current is self.value_combo:
                value = self.value_combo.currentIndex()
            else:
                value = self.value_double.value()
            for index in indexes:
                method = impute.Default(default=value)
                self.variable_methods[index.row()] = method
        else:
            method = self.methods[method_index]
            for index in indexes:
                self.variable_methods[index.row()] = method

        self.update_varview(indexes)
        self._invalidate()

    def update_varview(self, indexes=None):
        if indexes is None:
            indexes = map(self.varmodel.index, range(len(self.varmodel)))

        for index in indexes:
            self.varmodel.setData(index, self.get_method_for_column(index.row()), Qt.UserRole)

    def _on_value_selected(self):
        # The fixed 'Value' in the widget has been changed by the user.
        self.variable_button_group.button(self.AS_INPUT).setChecked(True)
        self.set_method_for_current_selection(self.AS_INPUT)

    def reset_variable_methods(self):
        indexes = list(map(self.varmodel.index, range(len(self.varmodel))))
        self.set_method_for_indexes(indexes, self.DEFAULT)
        self.variable_button_group.button(self.DEFAULT).setChecked(True)
Exemplo n.º 14
0
class OWEditDomain(widget.OWWidget):
    name = "Edit Domain"
    description = "Rename features and their values."
    icon = "icons/EditDomain.svg"
    priority = 3125

    inputs = [("Data", Orange.data.Table, "set_data")]
    outputs = [("Data", Orange.data.Table)]

    settingsHandler = settings.DomainContextHandler()

    domain_change_hints = settings.ContextSetting({})
    selected_index = settings.ContextSetting({})

    autocommit = settings.Setting(False)

    def __init__(self):
        super().__init__()

        self.data = None
        self.input_vars = ()
        self._invalidated = False

        box = gui.vBox(self.controlArea, "Domain Features")

        self.domain_model = itemmodels.VariableListModel()
        self.domain_view = QListView(
            selectionMode=QListView.SingleSelection
        )
        self.domain_view.setModel(self.domain_model)
        self.domain_view.selectionModel().selectionChanged.connect(
            self._on_selection_changed)
        box.layout().addWidget(self.domain_view)

        box = gui.hBox(self.controlArea)
        gui.button(box, self, "Reset Selected", callback=self.reset_selected)
        gui.button(box, self, "Reset All", callback=self.reset_all)

        gui.auto_commit(self.controlArea, self, "autocommit", "Apply")

        box = gui.vBox(self.mainArea, "Edit")
        self.editor_stack = QStackedWidget()

        self.editor_stack.addWidget(DiscreteVariableEditor())
        self.editor_stack.addWidget(ContinuousVariableEditor())
        self.editor_stack.addWidget(VariableEditor())

        box.layout().addWidget(self.editor_stack)

    @check_sql_input
    def set_data(self, data):
        """Set input data set."""
        self.closeContext()
        self.clear()
        self.data = data

        if self.data is not None:
            self._initialize()
            self.openContext(self.data)
            self._restore()

        self.unconditional_commit()

    def clear(self):
        """Clear the widget state."""
        self.data = None
        self.domain_model[:] = []
        self.input_vars = []
        self.domain_change_hints = {}
        self.selected_index = -1

    def reset_selected(self):
        """Reset the currently selected variable to its original state."""
        ind = self.selected_var_index()
        if ind >= 0:
            var = self.input_vars[ind]
            desc = variable_description(var, skip_attributes=True)
            if desc in self.domain_change_hints:
                del self.domain_change_hints[desc]

            self.domain_model[ind] = var
            self.editor_stack.currentWidget().set_data(var)
            self._invalidate()

    def reset_all(self):
        """Reset all variables to their original state."""
        self.domain_change_hints = {}
        if self.data is not None:
            # To invalidate stored hints
            self.domain_model[:] = self.input_vars
            itemmodels.select_row(self.domain_view, self.selected_index)
            self._invalidate()

    def selected_var_index(self):
        """Return the selected row in 'Domain Features' view."""
        rows = self.domain_view.selectedIndexes()
        assert len(rows) <= 1
        return rows[0].row() if rows else -1

    def _initialize(self):
        domain = self.data.domain
        self.input_vars = tuple(domain) + domain.metas
        self.domain_model[:] = list(self.input_vars)

    def _restore(self):
        # Restore the variable states from saved settings.
        def transform(var):
            vdesc = variable_description(var, skip_attributes=True)
            if vdesc in self.domain_change_hints:
                return variable_from_description(
                    self.domain_change_hints[vdesc],
                    compute_value=Orange.preprocess.transformation.Identity(var))
            else:
                return var

        self.domain_model[:] = map(transform, self.input_vars)

        # Restore the variable selection if possible
        index = self.selected_index
        if index >= len(self.input_vars):
            index = 0 if len(self.input_vars) else -1
        if index >= 0:
            itemmodels.select_row(self.domain_view, index)

    def _on_selection_changed(self):
        self.selected_index = self.selected_var_index()
        self.open_editor(self.selected_index)

    def open_editor(self, index):
        self.clear_editor()
        if index < 0:
            return

        var = self.domain_model[index]

        editor_index = 2
        if var.is_discrete:
            editor_index = 0
        elif var.is_continuous:
            editor_index = 1
        editor = self.editor_stack.widget(editor_index)
        self.editor_stack.setCurrentWidget(editor)

        editor.set_data(var)
        editor.variable_changed.connect(self._on_variable_changed)

    def clear_editor(self):
        current = self.editor_stack.currentWidget()
        try:
            current.variable_changed.disconnect(self._on_variable_changed)
        except Exception:
            pass
        current.set_data(None)

    def _on_variable_changed(self):
        """User edited the current variable in editor."""
        assert 0 <= self.selected_index <= len(self.domain_model)
        editor = self.editor_stack.currentWidget()

        # Replace the variable in the 'Domain Features' view/model
        old_var = self.input_vars[self.selected_index]
        new_var = editor.get_data().copy(compute_value=Orange.preprocess.transformation.Identity(old_var))
        self.domain_model[self.selected_index] = new_var


        # Store the transformation hint.
        old_var_desc = variable_description(old_var, skip_attributes=True)
        self.domain_change_hints[old_var_desc] = variable_description(new_var)

        self._invalidate()

    def _invalidate(self):
        self.commit()

    def commit(self):
        """Send the changed data to output."""
        new_data = None
        if self.data is not None:
            input_domain = self.data.domain
            n_attrs = len(input_domain.attributes)
            n_vars = len(input_domain.variables)
            n_class_vars = len(input_domain.class_vars)
            all_new_vars = list(self.domain_model)
            attrs = all_new_vars[: n_attrs]
            class_vars = all_new_vars[n_attrs: n_attrs + n_class_vars]
            new_metas = all_new_vars[n_attrs + n_class_vars:]
            new_domain = Orange.data.Domain(attrs, class_vars, new_metas)
            new_data = self.data.from_table(new_domain, self.data)

        self.send("Data", new_data)

    def sizeHint(self):
        sh = super().sizeHint()
        return sh.expandedTo(QSize(660, 550))

    def send_report(self):
        if self.data is not None:
            self.report_raw("", EditDomainReport(
                old_domain=chain(self.data.domain.variables, self.data.domain.metas),
                new_domain=self.domain_model).to_html())
        else:
            self.report_data(None)
Exemplo n.º 15
0
class OWSpiralogram(widget.OWWidget):
    name = 'Spiralogram'
    description = "Visualize time series' periodicity in a spiral heatmap."
    icon = 'icons/Spiralogram.svg'
    priority = 120

    class Inputs:
        time_series = Input("Time series", Table)

    class Outputs:
        time_series = Output("Time series", Timeseries)

    settingsHandler = settings.DomainContextHandler()

    ax1 = settings.ContextSetting('months of year')
    ax2 = settings.ContextSetting('years')

    agg_attr = settings.ContextSetting([])
    agg_func = settings.ContextSetting(0)

    invert_date_order = settings.Setting(False)

    graph_name = 'chart'

    class Error(widget.OWWidget.Error):
        no_time_variable = widget.Msg(
            'Spiralogram requires time series with a time variable.')

    def __init__(self):
        self.data = None
        self.indices = []
        box = gui.vBox(self.controlArea, 'Axes')
        self.combo_ax2_model = VariableListModel(parent=self)
        self.combo_ax1_model = VariableListModel(parent=self)
        for model in (self.combo_ax1_model, self.combo_ax2_model):
            model[:] = [_enum_str(i) for i in Spiralogram.AxesCategories]
        self.combo_ax2 = gui.comboBox(box,
                                      self,
                                      'ax2',
                                      label='Y axis:',
                                      callback=self.replot,
                                      sendSelectedValue=True,
                                      orientation='horizontal',
                                      model=self.combo_ax2_model)
        self.combo_ax1 = gui.comboBox(box,
                                      self,
                                      'ax1',
                                      label='Radial:',
                                      callback=self.replot,
                                      sendSelectedValue=True,
                                      orientation='horizontal',
                                      model=self.combo_ax1_model)
        gui.checkBox(box,
                     self,
                     'invert_date_order',
                     'Invert Y axis order',
                     callback=self.replot)

        box = gui.vBox(self.controlArea, 'Aggregation')
        self.combo_func = gui.comboBox(box,
                                       self,
                                       'agg_func',
                                       label='Function:',
                                       orientation='horizontal',
                                       callback=self.replot)
        func_model = ListModel(AGG_FUNCTIONS, parent=self)
        self.combo_func.setModel(func_model)

        self.attrlist_model = VariableListModel(parent=self)
        self.attrlist = QListView(selectionMode=QListView.SingleSelection)
        self.attrlist.setModel(self.attrlist_model)
        self.attrlist.selectionModel().selectionChanged.connect(
            self.attrlist_selectionChanged)
        box.layout().addWidget(self.attrlist)

        gui.rubber(self.controlArea)

        self.chart = chart = Spiralogram(self,
                                         selection_callback=self.on_selection)
        self.mainArea.layout().addWidget(chart)

    def attrlist_selectionChanged(self):
        self.agg_attr = [
            self.attrlist_model[i.row()]
            for i in self.attrlist.selectionModel().selectedIndexes()
        ]
        self.replot()

    @Inputs.time_series
    def set_data(self, data):
        self.Error.clear()
        self.data = data = None if data is None else Timeseries.from_data_table(
            data)

        if data is None:
            self.commit()
            return

        if self.data.time_variable is None or not isinstance(
                self.data.time_variable, TimeVariable):
            self.Error.no_time_variable()
            self.commit()
            return

        def init_combos():
            for model in (self.combo_ax1_model, self.combo_ax2_model):
                model.clear()
            newmodel = []
            if data is not None and data.time_variable is not None:
                for model in (self.combo_ax1_model, self.combo_ax2_model):
                    model[:] = [
                        _enum_str(i) for i in Spiralogram.AxesCategories
                    ]
            for var in data.domain.variables if data is not None else []:
                if (var.is_primitive()
                        and (var is not data.time_variable or isinstance(
                            var, TimeVariable) and data.time_delta is None)):
                    newmodel.append(var)
                if var.is_discrete:
                    for model in (self.combo_ax1_model, self.combo_ax2_model):
                        model.append(var)
            self.attrlist_model.wrap(newmodel)

        init_combos()
        self.chart.clear()

        self.closeContext()
        self.ax2 = next((self.combo_ax2.itemText(i)
                         for i in range(self.combo_ax2.count())), '')
        self.ax1 = next((self.combo_ax1.itemText(i)
                         for i in range(1, self.combo_ax1.count())), self.ax2)
        self.agg_attr = [data.domain.variables[0]] if len(
            data.domain.variables) else []
        self.agg_func = 0
        if getattr(data, 'time_variable', None) is not None:
            self.openContext(data.domain)

        if self.agg_attr:
            self.attrlist.blockSignals(True)
            self.attrlist.selectionModel().clear()
            for attr in self.agg_attr:
                try:
                    row = self.attrlist_model.indexOf(attr)
                except ValueError:
                    continue
                self.attrlist.selectionModel().select(
                    self.attrlist_model.index(row),
                    QItemSelectionModel.SelectCurrent)
            self.attrlist.blockSignals(False)

        self.replot()

    def replot(self):
        if not self.combo_ax1.count() or not self.agg_attr:
            return self.chart.clear()

        vars = self.agg_attr
        func = AGG_FUNCTIONS[self.agg_func]
        if any(var.is_discrete for var in vars) and func != Mode:
            self.combo_func.setCurrentIndex(AGG_FUNCTIONS.index(Mode))
            func = Mode
        try:
            ax1 = Spiralogram.AxesCategories[_enum_str(self.ax1, True)]
        except KeyError:
            ax1 = self.data.domain[self.ax1]
        # TODO: Allow having only a single (i.e. radial) axis
        try:
            ax2 = Spiralogram.AxesCategories[_enum_str(self.ax2, True)]
        except KeyError:
            ax2 = self.data.domain[self.ax2]
        self.chart.setSeries(self.data, vars, ax1, ax2, func)

    def on_selection(self, indices):
        self.indices = self.chart.selection_indices(indices)
        self.commit()

    def commit(self):
        self.Outputs.time_series.send(
            self.data[self.indices] if self.data else None)
Exemplo n.º 16
0
class StatisticalTestWidget(OWWidget):
    """
    Perform statistical tests
    """
    name = 'Statistical test'
    description = 'Do statistical tests and returns p-value.\n' \
                  'Many samples should be in same table as columns.'
    icon = 'icons/test_widget.svg'
    want_main_area = False
    inputs = [('Data', Orange.data.Table, 'set_data')]
    outputs = [('p-value', float)]

    def __init__(self):
        super().__init__()
        self.data = None
        self.available_tests = [TTest(), ZTest(), FisherTest(), Anova()]
        self.active_tests = []

        self.available_columns = itemmodels.VariableListModel(parent=self)

        self.available_corrections = [
            None, BonferroniCorrection(), SidakCorrection()]

        vlayout = QHBoxLayout()
        # Data selection
        gui.widgetBox(
            self.controlArea, self.tr('Chose data to test'),
            orientation=vlayout, spacing=16
        )
        self.varview = QListView(selectionMode=QListView.MultiSelection)
        self.varview.setModel(self.available_columns)
        self.varview.selectionModel().selectionChanged.connect(
            self.update_column_selection
        )
        vlayout.addWidget(self.varview)

        # Test selection
        self.tests = itemmodels.VariableListModel(parent=self)
        self.testview = QListView(selectionMode=QListView.SingleSelection)
        self.testview.setModel(self.tests)
        self.testview.selectionModel().selectionChanged.connect(
            self.test_selected
        )
        vlayout.addWidget(self.testview)

        # Correction selection
        self.corrections = itemmodels.VariableListModel(parent=self)
        self.corrections[:] = \
            [str(None), BonferroniCorrection().name, SidakCorrection().name]
        self.cor_varview = QListView(selectionMode=QListView.SingleSelection)
        self.cor_varview.setModel(self.corrections)
        self.cor_varview.selectionModel().selectionChanged.connect(
            self.set_correction
        )
        vlayout.addWidget(self.cor_varview)

        self.n_of_tests = 1

        self.n_of_tests_input = gui.lineEdit(
            self.controlArea, self,
            'n_of_tests',
            label='<p align="right">Number of tests</p>',
            callbackOnType=True,
            controlWidth=150,
            orientation=Qt.Horizontal,
            callback=self.number_of_tests_changed,
        )

        self.chosen_correction = None

        pval_box = gui.vBox(self.controlArea)
        self.pval_infolabel = gui.widgetLabel(
            pval_box,
            '<p align="left"><b>p-value: </b></p>',
        )

    def show_p_value(self, p_value: float):
        """
        Draw p-value in widget if only is numeric.
        """
        if isinstance(p_value, int) or isinstance(p_value, float):
            p_val_str = str(p_value)
        else:
            p_val_str = '~ ? ~'
        self.pval_infolabel.setText(
            '<p align="left"><b>p-value: {}</b></p>'.format(p_val_str))

    def update_column_selection(self, *args):
        """
        Method called when selection of samples changed.
        Enables matching tests for selected samples.
        """
        columns_index = self.selected_columns
        self.active_tests = []
        self.tests[:] = []
        if (not self.selected_data_is_continuous and
                not self.selected_data_is_discrete):
            return
        elif len(columns_index) == 1:
            self.enable_test_with_one_sample()
        elif len(columns_index) == 2:
            self.enable_tests_with_two_samples()
        elif len(columns_index) >= 2:
            self.enable_tests_with_many_samples()

    def enable_test_with_one_sample(self):
        """
        Enable tests which can be apply to one sample.
        """
        self.active_tests = [test for test in self.available_tests if
                             test.can_be_used_with_one_sample(self)]
        self.tests[:] = [test.name for test in self.active_tests]

    def enable_tests_with_two_samples(self):
        """
        Enable tests which can be apply to two samples.
        """
        self.active_tests = [test for test in self.available_tests if
                             test.can_be_used_with_two_sample(self)]
        self.tests[:] = [test.name for test in self.active_tests]

    def enable_tests_with_many_samples(self):
        """
        Enable tests which can be apply to many samples.
        It should not be called for one or two data samples!
        """
        self.active_tests = [test for test in self.available_tests if
                             test.can_be_used_with_many_sample(self)]
        self.tests[:] = [test.name for test in self.active_tests]

    @property
    def selected_columns(self):
        """
        :return: Indexes of actually selected columns.
        """
        rows = self.varview.selectionModel().selectedRows()
        return [index.row() for index in rows]

    @property
    def selected_data_is_continuous(self):
        """
        :return: Tell whether all selected samples are continuous.
        """
        return all([self.available_columns[i].is_continuous for i in
                    self.selected_columns])

    @property
    def selected_data_is_discrete(self):
        """
        :return: Tell whether all selected samples are discrete.
        """
        return all([not self.available_columns[i].is_continuous for i in
                    self.selected_columns])

    @property
    def selected_test(self):
        """
        :return: index of actually selected test.
        """
        # FIXME: I have no idea why test list selection sometimes disappearing
        try:
            _ = self.testview.selectionModel().selectedRows()[0].row()
            self.last_selected_test = _
        except IndexError:
            pass
        return self.last_selected_test

    def set_data(self, data):
        return self.update_data(data)

    def update_data(self, data):
        """
        Method called when input data updated.
        :param data: input data
        """
        if data:
            self.data = data
            self.available_columns[:] = data.domain

    def test_selected(self):
        """
        Method called when test selected.
        """
        return self.do_test()

    def do_test(self):
        """
        Main method of widget. Do all test logic.
        Call it if you want to do test or recalculate it.
        """
        if not self.data:
            return
        try:
            test = self.active_tests[self.selected_test]
        except IndexError:
            return
        except AttributeError:
            return
        columns_indexes = self.selected_columns
        number_of_columns = len(columns_indexes)
        if number_of_columns == 0:
            return
        elif number_of_columns == 1:
            p_value = test.one_sample_test(
                self.data[:, columns_indexes].X)
        elif number_of_columns == 2:
            p_value = test.two_sample_test(
                *[self.data[:, column_index].X for column_index in
                  columns_indexes])
        else:
            p_value = test.many_sample_test(
                *[self.data[:, column_index].X for column_index in
                  columns_indexes])
        if self.chosen_correction:
            chosen_ = self.available_corrections[self.chosen_correction]
            p_value = chosen_.calculate_correction(p_value, self.n_of_tests)
        self.show_p_value(p_value)
        return self.send('p-value', p_value)

    def number_of_tests_changed(self):
        """
        Method called when number of test in correction changed.
        """
        try:
            self.n_of_tests = int(self.n_of_tests)
        except TypeError:
            self.n_of_tests = 1
            self.n_of_tests_input = "1"
        self.test_selected()

    def set_correction(self):
        """
        Method called when correction method changed.
        """
        new_cor = self.cor_varview.selectionModel().selectedRows()[0].row()
        recalculate = False
        if self.chosen_correction != new_cor:
            self.chosen_correction = new_cor
            recalculate = True
        if recalculate:
            self.test_selected()
Exemplo n.º 17
0
class OWFeatureConstructor(OWWidget):
    name = "特征构造器(Feature Constructor)"
    description = "用输入数据集中的现有特征构造新特征。"
    icon = "icons/FeatureConstructor.svg"
    keywords = ['tezheng', 'gouzao', 'tezhenggouzao']
    category = "数据(Data)"
    icon = "icons/FeatureConstructor.svg"

    class Inputs:
        data = Input("数据(Data)", Orange.data.Table, replaces=['Data'])

    class Outputs:
        data = Output("数据(Data)", Orange.data.Table, replaces=['Data'])

    want_main_area = False

    settingsHandler = FeatureConstructorHandler()
    descriptors = ContextSetting([])
    currentIndex = ContextSetting(-1)
    expressions_with_values = ContextSetting(False)
    settings_version = 2

    EDITORS = [(ContinuousDescriptor, ContinuousFeatureEditor),
               (DateTimeDescriptor, DateTimeFeatureEditor),
               (DiscreteDescriptor, DiscreteFeatureEditor),
               (StringDescriptor, StringFeatureEditor)]

    class Error(OWWidget.Error):
        more_values_needed = Msg("Categorical feature {} needs more values.")
        invalid_expressions = Msg("Invalid expressions: {}.")

    class Warning(OWWidget.Warning):
        renamed_var = Msg("Recently added variable has been renamed, "
                          "to avoid duplicates.\n")

    def __init__(self):
        super().__init__()
        self.data = None
        self.editors = {}

        box = gui.vBox(self.controlArea, "变量定义")

        toplayout = QHBoxLayout()
        toplayout.setContentsMargins(0, 0, 0, 0)
        box.layout().addLayout(toplayout)

        self.editorstack = QStackedWidget(sizePolicy=QSizePolicy(
            QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding))

        for descclass, editorclass in self.EDITORS:
            editor = editorclass()
            editor.featureChanged.connect(self._on_modified)
            self.editors[descclass] = editor
            self.editorstack.addWidget(editor)

        self.editorstack.setEnabled(False)

        buttonlayout = QVBoxLayout(spacing=10)
        buttonlayout.setContentsMargins(0, 0, 0, 0)

        self.addbutton = QPushButton("新建",
                                     toolTip="Create a new variable",
                                     minimumWidth=120,
                                     shortcut=QKeySequence.New)

        def unique_name(fmt, reserved):
            candidates = (fmt.format(i) for i in count(1))
            return next(c for c in candidates if c not in reserved)

        def generate_newname(fmt):
            return unique_name(fmt, self.reserved_names())

        menu = QMenu(self.addbutton)
        cont = menu.addAction("数值数据")
        cont.triggered.connect(lambda: self.addFeature(
            ContinuousDescriptor(generate_newname("X{}"), "", 3)))
        disc = menu.addAction("分类数据")
        disc.triggered.connect(lambda: self.addFeature(
            DiscreteDescriptor(generate_newname("D{}"), "", (), False)))
        string = menu.addAction("文本")
        string.triggered.connect(lambda: self.addFeature(
            StringDescriptor(generate_newname("S{}"), "")))
        datetime = menu.addAction("日期/时间")
        datetime.triggered.connect(lambda: self.addFeature(
            DateTimeDescriptor(generate_newname("T{}"), "")))

        menu.addSeparator()
        self.duplicateaction = menu.addAction("复制选中变量")
        self.duplicateaction.triggered.connect(self.duplicateFeature)
        self.duplicateaction.setEnabled(False)
        self.addbutton.setMenu(menu)

        self.removebutton = QPushButton("删除",
                                        toolTip="删除选中变量",
                                        minimumWidth=120,
                                        shortcut=QKeySequence.Delete)
        self.removebutton.clicked.connect(self.removeSelectedFeature)

        buttonlayout.addWidget(self.addbutton)
        buttonlayout.addWidget(self.removebutton)
        buttonlayout.addStretch(10)

        toplayout.addLayout(buttonlayout, 0)
        toplayout.addWidget(self.editorstack, 10)

        # Layout for the list view
        layout = QVBoxLayout(spacing=1, margin=0)
        self.featuremodel = DescriptorModel(parent=self)

        self.featureview = QListView(minimumWidth=200,
                                     minimumHeight=50,
                                     sizePolicy=QSizePolicy(
                                         QSizePolicy.Minimum,
                                         QSizePolicy.MinimumExpanding))

        self.featureview.setItemDelegate(FeatureItemDelegate(self))
        self.featureview.setModel(self.featuremodel)
        self.featureview.selectionModel().selectionChanged.connect(
            self._on_selectedVariableChanged)

        layout.addWidget(self.featureview)

        box.layout().addLayout(layout, 1)

        self.fix_button = gui.button(self.buttonsArea,
                                     self,
                                     "Upgrade Expressions",
                                     callback=self.fix_expressions)
        self.fix_button.setHidden(True)
        gui.button(self.buttonsArea,
                   self,
                   "Send",
                   callback=self.apply,
                   default=True)

    def setCurrentIndex(self, index):
        index = min(index, len(self.featuremodel) - 1)
        self.currentIndex = index
        if index >= 0:
            itemmodels.select_row(self.featureview, index)
            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]
            editor = self.editors[type(desc)]
            self.editorstack.setCurrentWidget(editor)
            editor.setEditorData(desc, self.data.domain if self.data else None)
        self.editorstack.setEnabled(index >= 0)
        self.duplicateaction.setEnabled(index >= 0)
        self.removebutton.setEnabled(index >= 0)

    def _on_selectedVariableChanged(self, selected, *_):
        index = selected_row(self.featureview)
        if index is not None:
            self.setCurrentIndex(index)
        else:
            self.setCurrentIndex(-1)

    def _on_modified(self):
        if self.currentIndex >= 0:
            self.Warning.clear()
            editor = self.editorstack.currentWidget()
            proposed = editor.editorData().name
            uniq = get_unique_names(self.reserved_names(self.currentIndex),
                                    proposed)

            feature = editor.editorData()
            if editor.editorData().name != uniq:
                self.Warning.renamed_var()
                feature = feature.__class__(uniq, *feature[1:])

            self.featuremodel[self.currentIndex] = feature
            self.descriptors = list(self.featuremodel)

    def setDescriptors(self, descriptors):
        """
        Set a list of variable descriptors to edit.
        """
        self.descriptors = descriptors
        self.featuremodel[:] = list(self.descriptors)

    def reserved_names(self, idx_=None):
        varnames = []
        if self.data is not None:
            varnames = [
                var.name
                for var in self.data.domain.variables + self.data.domain.metas
            ]
        varnames += [
            desc.name for idx, desc in enumerate(self.featuremodel)
            if idx != idx_
        ]
        return set(varnames)

    @Inputs.data
    @check_sql_input
    def setData(self, data=None):
        """Set the input dataset."""
        self.closeContext()

        self.data = data
        self.expressions_with_values = False

        self.descriptors = []
        self.currentIndex = -1
        if self.data is not None:
            self.openContext(data)

        # disconnect from the selection model while reseting the model
        selmodel = self.featureview.selectionModel()
        selmodel.selectionChanged.disconnect(self._on_selectedVariableChanged)

        self.featuremodel[:] = list(self.descriptors)
        self.setCurrentIndex(self.currentIndex)

        selmodel.selectionChanged.connect(self._on_selectedVariableChanged)
        self.fix_button.setHidden(not self.expressions_with_values)
        self.editorstack.setEnabled(self.currentIndex >= 0)

    def handleNewSignals(self):
        if self.data is not None:
            self.apply()
        else:
            self.Outputs.data.send(None)
            self.fix_button.setHidden(True)

    def addFeature(self, descriptor):
        self.featuremodel.append(descriptor)
        self.setCurrentIndex(len(self.featuremodel) - 1)
        editor = self.editorstack.currentWidget()
        editor.nameedit.setFocus()
        editor.nameedit.selectAll()

    def removeFeature(self, index):
        del self.featuremodel[index]
        index = selected_row(self.featureview)
        if index is not None:
            self.setCurrentIndex(index)
        elif index is None and self.featuremodel.rowCount():
            # Deleting the last item clears selection
            self.setCurrentIndex(self.featuremodel.rowCount() - 1)

    def removeSelectedFeature(self):
        if self.currentIndex >= 0:
            self.removeFeature(self.currentIndex)

    def duplicateFeature(self):
        desc = self.featuremodel[self.currentIndex]
        self.addFeature(copy.deepcopy(desc))

    @staticmethod
    def check_attrs_values(attr, data):
        for i in range(len(data)):
            for var in attr:
                if not math.isnan(data[i, var]) \
                        and int(data[i, var]) >= len(var.values):
                    return var.name
        return None

    def _validate_descriptors(self, desc):
        def validate(source):
            try:
                return validate_exp(ast.parse(source, mode="eval"))
            # ast.parse can return arbitrary errors, not only SyntaxError
            # pylint: disable=broad-except
            except Exception:
                return False

        final = []
        invalid = []
        for d in desc:
            if validate(d.expression):
                final.append(d)
            else:
                final.append(d._replace(expression=""))
                invalid.append(d)

        if invalid:
            self.Error.invalid_expressions(", ".join(s.name for s in invalid))

        return final

    def apply(self):
        def report_error(err):
            log = logging.getLogger(__name__)
            log.error("", exc_info=True)
            self.error("".join(format_exception_only(type(err), err)).rstrip())

        self.Error.clear()

        if self.data is None:
            return

        desc = list(self.featuremodel)
        desc = self._validate_descriptors(desc)
        try:
            new_variables = construct_variables(desc, self.data,
                                                self.expressions_with_values)
        # user's expression can contain arbitrary errors
        except Exception as err:  # pylint: disable=broad-except
            report_error(err)
            return

        attrs = [var for var in new_variables if var.is_primitive()]
        metas = [var for var in new_variables if not var.is_primitive()]
        new_domain = Orange.data.Domain(
            self.data.domain.attributes + tuple(attrs),
            self.data.domain.class_vars,
            metas=self.data.domain.metas + tuple(metas))

        try:
            for variable in new_variables:
                variable.compute_value.mask_exceptions = False
            data = self.data.transform(new_domain)
        # user's expression can contain arbitrary errors
        # pylint: disable=broad-except
        except Exception as err:
            report_error(err)
            return
        finally:
            for variable in new_variables:
                variable.compute_value.mask_exceptions = True

        disc_attrs_not_ok = self.check_attrs_values(
            [var for var in attrs if var.is_discrete], data)
        if disc_attrs_not_ok:
            self.Error.more_values_needed(disc_attrs_not_ok)
            return

        self.Outputs.data.send(data)

    def send_report(self):
        items = OrderedDict()
        for feature in self.featuremodel:
            if isinstance(feature, DiscreteDescriptor):
                desc = "categorical"
                if feature.values:
                    desc += " with values " \
                            + ", ".join(f"'{val}'" for val in feature.values)
                if feature.ordered:
                    desc += "; ordered"
            elif isinstance(feature, ContinuousDescriptor):
                desc = "numeric"
            elif isinstance(feature, DateTimeDescriptor):
                desc = "date/time"
            else:
                desc = "text"
            items[feature.name] = f"{feature.expression} ({desc})"
        self.report_items(report.plural("Constructed feature{s}", len(items)),
                          items)

    def fix_expressions(self):
        dlg = QMessageBox(
            QMessageBox.Question, "Fix Expressions",
            "This widget's behaviour has changed. Values of categorical "
            "variables are now inserted as their textual representations "
            "(strings); previously they appeared as integer numbers, with an "
            "attribute '.value' that contained the text.\n\n"
            "The widget currently runs in compatibility mode. After "
            "expressions are updated, manually check for their correctness.")
        dlg.addButton("Update", QMessageBox.ApplyRole)
        dlg.addButton("Cancel", QMessageBox.RejectRole)
        if dlg.exec() == QMessageBox.RejectRole:
            return

        def fixer(mo):
            var = domain[mo.group(2)]
            if mo.group(3) == ".value":  # uses string; remove `.value`
                return "".join(mo.group(1, 2, 4))
            # Uses ints: get them by indexing
            return mo.group(1) + "{" + \
                ", ".join(f"'{val}': {i}"
                          for i, val in enumerate(var.values)) + \
                f"}}[{var.name}]" + mo.group(4)

        domain = self.data.domain
        disc_vars = "|".join(f"{var.name}"
                             for var in chain(domain.variables, domain.metas)
                             if var.is_discrete)
        expr = re.compile(r"(^|\W)(" + disc_vars + r")(\.value)?(\W|$)")
        self.descriptors[:] = [
            descriptor._replace(
                expression=expr.sub(fixer, descriptor.expression))
            for descriptor in self.descriptors
        ]

        self.expressions_with_values = False
        self.fix_button.hide()
        index = self.currentIndex
        self.featuremodel[:] = list(self.descriptors)
        self.setCurrentIndex(index)
        self.apply()

    @classmethod
    def migrate_context(cls, context, version):
        if version is None or version < 2:
            used_vars = set(
                chain(*(
                    freevars(ast.parse(descriptor.expression, mode="eval"), [])
                    for descriptor in context.values["descriptors"]
                    if descriptor.expression)))
            disc_vars = {
                name
                for (name, vtype) in chain(context.attributes.items(),
                                           context.metas.items()) if vtype == 1
            }
            if used_vars & disc_vars:
                context.values["expressions_with_values"] = True
Exemplo n.º 18
0
class OWDiscretize(widget.OWWidget):
    # pylint: disable=too-many-instance-attributes
    name = "Discretize"
    description = "Discretize the numeric data features."
    icon = "icons/Discretize.svg"
    keywords = []

    class Inputs:
        data = Input("Data", Orange.data.Table, doc="Input data table")

    class Outputs:
        data = Output("Data",
                      Orange.data.Table,
                      doc="Table with discretized features")

    settingsHandler = settings.DomainContextHandler()
    settings_version = 2
    saved_var_states = settings.ContextSetting({})

    #: The default method name
    default_method_name = settings.Setting(Methods.EqualFreq.name)
    #: The k for Equal{Freq,Width}
    default_k = settings.Setting(3)
    #: The default cut points for custom entry
    default_cutpoints: Tuple[float, ...] = settings.Setting(())
    autosend = settings.Setting(True)

    #: Discretization methods
    Default, Leave, MDL, EqualFreq, EqualWidth, Remove, Custom = list(Methods)

    want_main_area = False
    resizing_enabled = False

    def __init__(self):
        super().__init__()

        #: input data
        self.data = None
        self.class_var = None
        #: Current variable discretization state
        self.var_state = {}
        #: Saved variable discretization settings (context setting)
        self.saved_var_states = {}

        self.method = Methods.Default
        self.k = 5
        self.cutpoints = ()
        self.default_cutpoints = ()

        box = gui.vBox(self.controlArea, self.tr("Default Discretization"))
        self._default_method_ = 0
        self.default_bbox = rbox = gui.radioButtons(
            box, self, "_default_method_", callback=self._default_disc_changed)
        self.default_button_group = bg = rbox.findChild(QButtonGroup)
        bg.buttonClicked[int].connect(self.set_default_method)

        rb = gui.hBox(rbox)
        self.left = gui.vBox(rb)
        right = gui.vBox(rb)
        rb.layout().setStretch(0, 1)
        rb.layout().setStretch(1, 1)
        self.options = [
            (Methods.Default, self.tr("Default")),
            (Methods.Leave, self.tr("Leave numeric")),
            (Methods.MDL, self.tr("Entropy-MDL discretization")),
            (Methods.EqualFreq, self.tr("Equal-frequency discretization")),
            (Methods.EqualWidth, self.tr("Equal-width discretization")),
            (Methods.Remove, self.tr("Remove numeric variables")),
            (Methods.Custom, self.tr("Manual")),
        ]

        for id_, opt in self.options[1:]:
            t = gui.appendRadioButton(rbox, opt)
            bg.setId(t, id_)
            t.setChecked(id_ == self.default_method)
            [right, self.left][opt.startswith("Equal")].layout().addWidget(t)

        def _intbox(parent, attr, callback):
            box = gui.indentedBox(parent)
            s = gui.spin(box,
                         self,
                         attr,
                         minv=2,
                         maxv=10,
                         label="Num. of intervals:",
                         callback=callback)
            s.setMaximumWidth(60)
            s.setAlignment(Qt.AlignRight)
            gui.rubber(s.box)
            sp = box.sizePolicy()
            sp.setControlType(sp.SpinBox)
            return box.box

        self.k_general = _intbox(self.left, "default_k",
                                 self._default_disc_changed)
        self.k_general.layout().setContentsMargins(0, 0, 0, 0)

        def manual_cut_editline(text="") -> QLineEdit:
            edit = QLineEdit(
                text=text,
                placeholderText="e.g. 0.0, 0.5, 1.0",
                toolTip="Enter fixed discretization cut points (a comma "
                "separated list of strictly increasing numbers e.g. "
                "0.0, 0.5, 1.0).",
            )

            @edit.textChanged.connect
            def update():
                validator = edit.validator()
                if validator is not None:
                    state, _, _ = validator.validate(edit.text(), 0)
                else:
                    state = QValidator.Acceptable
                palette = edit.palette()
                colors = {
                    QValidator.Intermediate: (Qt.yellow, Qt.black),
                    QValidator.Invalid: (Qt.red, Qt.black),
                }.get(state, None)
                if colors is None:
                    palette = QPalette()
                else:
                    palette.setColor(QPalette.Base, colors[0])
                    palette.setColor(QPalette.Text, colors[1])

                cr = edit.cursorRect()
                p = edit.mapToGlobal(cr.bottomRight())
                edit.setPalette(palette)
                if state != QValidator.Acceptable and edit.isVisible():
                    show_tip(edit, p, edit.toolTip(), textFormat=Qt.RichText)
                else:
                    show_tip(edit, p, "")

            return edit

        self.manual_cuts_edit = manual_cut_editline(
            text=", ".join(map(str, self.default_cutpoints)))

        def set_manual_default_cuts():
            text = self.manual_cuts_edit.text()
            self.default_cutpoints = tuple(
                float(s.strip()) for s in text.split(",") if s.strip())
            self._default_disc_changed()

        self.manual_cuts_edit.editingFinished.connect(set_manual_default_cuts)

        validator = IncreasingNumbersListValidator()
        self.manual_cuts_edit.setValidator(validator)
        ibox = gui.indentedBox(right, orientation=Qt.Horizontal)
        ibox.layout().addWidget(self.manual_cuts_edit)

        right.layout().addStretch(10)
        self.left.layout().addStretch(10)

        self.connect_control(
            "default_cutpoints",
            lambda values: self.manual_cuts_edit.setText(", ".join(
                map(str, values))))
        vlayout = QHBoxLayout()
        box = gui.widgetBox(self.controlArea,
                            "Individual Attribute Settings",
                            orientation=vlayout,
                            spacing=8)

        # List view with all attributes
        self.varview = QListView(
            selectionMode=QListView.ExtendedSelection,
            uniformItemSizes=True,
        )
        self.varview.setItemDelegate(DiscDelegate())
        self.varmodel = itemmodels.VariableListModel()
        self.varview.setModel(self.varmodel)
        self.varview.selectionModel().selectionChanged.connect(
            self._var_selection_changed)

        vlayout.addWidget(self.varview)
        # Controls for individual attr settings
        self.bbox = controlbox = gui.radioButtons(
            box, self, "method", callback=self._disc_method_changed)
        vlayout.addWidget(controlbox)
        self.variable_button_group = bg = controlbox.findChild(QButtonGroup)
        for id_, opt in self.options[:5]:
            b = gui.appendRadioButton(controlbox, opt)
            bg.setId(b, id_)

        self.k_specific = _intbox(controlbox, "k", self._disc_method_changed)

        gui.appendRadioButton(controlbox,
                              "Remove attribute",
                              id=Methods.Remove)
        b = gui.appendRadioButton(controlbox, "Manual", id=Methods.Custom)

        self.manual_cuts_specific = manual_cut_editline()
        self.manual_cuts_specific.setValidator(validator)
        b.toggled[bool].connect(self.manual_cuts_specific.setEnabled)

        def set_manual_cuts():
            text = self.manual_cuts_specific.text()
            points = [t for t in text.split(",") if t.split()]
            self.cutpoints = tuple(float(t) for t in points)
            self._disc_method_changed()

        self.manual_cuts_specific.editingFinished.connect(set_manual_cuts)

        self.connect_control(
            "cutpoints",
            lambda values: self.manual_cuts_specific.setText(", ".join(
                map(str, values))))
        ibox = gui.indentedBox(controlbox, orientation=Qt.Horizontal)
        self.copy_current_to_manual_button = b = FixedSizeButton(
            text="CC",
            toolTip="Copy the current cut points to manual mode",
            enabled=False)
        b.clicked.connect(self._copy_to_manual)
        ibox.layout().addWidget(self.manual_cuts_specific)
        ibox.layout().addWidget(b)

        gui.rubber(controlbox)
        controlbox.setEnabled(False)
        bg.button(self.method)
        self.controlbox = controlbox

        box = gui.auto_apply(self.controlArea, self, "autosend")
        box.button.setFixedWidth(180)
        box.layout().insertStretch(0)

        self._update_spin_positions()

        self.info.set_input_summary(self.info.NoInput)
        self.info.set_output_summary(self.info.NoOutput)

    @property
    def default_method(self) -> Methods:
        return Methods[self.default_method_name]

    @default_method.setter
    def default_method(self, method):
        self.set_default_method(method)

    def set_default_method(self, method: Methods):
        if isinstance(method, int):
            method = Methods(method)
        else:
            method = Methods.from_method(method)

        if method != self.default_method:
            self.default_method_name = method.name
            self.default_button_group.button(method).setChecked(True)
            self._default_disc_changed()

    @Inputs.data
    def set_data(self, data):
        self.closeContext()
        self.data = data
        if self.data is not None:
            self._initialize(data)
            self.openContext(data)
            # Restore the per variable discretization settings
            self._restore(self.saved_var_states)
            # Complete the induction of cut points
            self._update_points()
            self.info.set_input_summary(len(data),
                                        format_summary_details(data))
        else:
            self.info.set_input_summary(self.info.NoInput)
            self._clear()
        self.unconditional_commit()

    def _initialize(self, data):
        # Initialize the default variable states for new data.
        self.class_var = data.domain.class_var
        cvars = [var for var in data.domain.variables if var.is_continuous]
        self.varmodel[:] = cvars

        has_disc_class = data.domain.has_discrete_class

        def set_enabled(box: QWidget, id_: Methods, state: bool):
            bg = box.findChild(QButtonGroup)
            b = bg.button(id_)
            b.setEnabled(state)

        set_enabled(self.default_bbox, self.MDL, has_disc_class)
        bg = self.bbox.findChild(QButtonGroup)
        b = bg.button(Methods.MDL)
        b.setEnabled(has_disc_class)
        set_enabled(self.bbox, self.MDL, has_disc_class)

        # If the newly disabled MDL button is checked then change it
        if not has_disc_class and self.default_method == self.MDL:
            self.default_method = Methods.Leave
        if not has_disc_class and self.method == self.MDL:
            self.method = Methods.Default

        # Reset (initialize) the variable discretization states.
        self._reset()

    def _restore(self, saved_state):
        # Restore variable states from a saved_state dictionary.
        def_method = self._current_default_method()
        for i, var in enumerate(self.varmodel):
            key = variable_key(var)
            if key in saved_state:
                state = saved_state[key]
                if isinstance(state.method, Default):
                    state = DState(Default(def_method), None, None)
                self._set_var_state(i, state)

    def _reset(self):
        # restore the individual variable settings back to defaults.
        def_method = self._current_default_method()
        self.var_state = {}
        for i in range(len(self.varmodel)):
            state = DState(Default(def_method), None, None)
            self._set_var_state(i, state)

    def _set_var_state(self, index, state):
        # set the state of variable at `index` to `state`.
        self.var_state[index] = state
        self.varmodel.setData(self.varmodel.index(index), state, Qt.UserRole)

    def _clear(self):
        self.data = None
        self.varmodel[:] = []
        self.var_state = {}
        self.saved_var_states = {}
        self.default_button_group.button(self.MDL).setEnabled(True)
        self.variable_button_group.button(self.MDL).setEnabled(True)

    def _update_points(self):
        """
        Update the induced cut points.
        """
        if self.data is None:
            return

        def induce_cuts(method, data, var):
            dvar = _dispatch[type(method)](method, data, var)
            if dvar is None:
                # removed
                return [], None
            elif dvar is var:
                # no transformation took place
                return None, var
            elif is_discretized(dvar):
                return dvar.compute_value.points, dvar
            raise ValueError

        for i, var in enumerate(self.varmodel):
            state = self.var_state[i]
            if state.points is None and state.disc_var is None:
                points, dvar = induce_cuts(state.method, self.data, var)
                new_state = state._replace(points=points, disc_var=dvar)
                self._set_var_state(i, new_state)

    def _current_default_method(self):
        method = self.default_method
        k = self.default_k
        if method == Methods.Leave:
            def_method = Leave()
        elif method == Methods.MDL:
            def_method = MDL()
        elif method == Methods.EqualFreq:
            def_method = EqualFreq(k)
        elif method == Methods.EqualWidth:
            def_method = EqualWidth(k)
        elif method == Methods.Remove:
            def_method = Remove()
        elif method == Methods.Custom:
            def_method = Custom(self.default_cutpoints)
        else:
            assert False
        return def_method

    def _current_method(self):
        if self.method == Methods.Default:
            method = Default(self._current_default_method())
        elif self.method == Methods.Leave:
            method = Leave()
        elif self.method == Methods.MDL:
            method = MDL()
        elif self.method == Methods.EqualFreq:
            method = EqualFreq(self.k)
        elif self.method == Methods.EqualWidth:
            method = EqualWidth(self.k)
        elif self.method == Methods.Remove:
            method = Remove()
        elif self.method == Methods.Custom:
            method = Custom(self.cutpoints)
        else:
            assert False
        return method

    def _update_spin_positions(self):
        kmethods = [Methods.EqualFreq, Methods.EqualWidth]
        self.k_general.setDisabled(self.default_method not in kmethods)
        if self.default_method == Methods.EqualFreq:
            self.left.layout().insertWidget(1, self.k_general)
        elif self.default_method == Methods.EqualWidth:
            self.left.layout().insertWidget(2, self.k_general)

        self.k_specific.setDisabled(self.method not in kmethods)
        if self.method == Methods.EqualFreq:
            self.bbox.layout().insertWidget(4, self.k_specific)
        elif self.method == Methods.EqualWidth:
            self.bbox.layout().insertWidget(5, self.k_specific)

    def _default_disc_changed(self):
        self._update_spin_positions()
        method = self._current_default_method()
        state = DState(Default(method), None, None)
        for i, _ in enumerate(self.varmodel):
            if isinstance(self.var_state[i].method, Default):
                self._set_var_state(i, state)
        self._update_points()
        self.commit()

    def _disc_method_changed(self):
        self._update_spin_positions()
        indices = self.selected_indices()
        method = self._current_method()
        state = DState(method, None, None)
        for idx in indices:
            self._set_var_state(idx, state)
        self._update_points()
        self._copy_to_manual_update_enabled()
        self.commit()

    def _copy_to_manual(self):
        indices = self.selected_indices()
        # set of all methods for the current selection
        if len(indices) != 1:
            return
        index = indices[0]
        state = self.var_state[index]
        var = self.varmodel[index]
        fmt = var.repr_val
        points = state.points
        if points is None:
            points = ()
        else:
            points = tuple(state.points)
        state = state._replace(method=Custom(points),
                               points=None,
                               disc_var=None)
        self._set_var_state(index, state)
        self.method = Methods.Custom
        self.cutpoints = points
        self.manual_cuts_specific.setText(", ".join(map(fmt, points)))
        self._update_points()
        self.commit()

    def _copy_to_manual_update_enabled(self):
        indices = self.selected_indices()
        methods = [self.var_state[i].method for i in indices]
        self.copy_current_to_manual_button.setEnabled(
            len(indices) == 1 and not isinstance(methods[0], Custom))

    def _var_selection_changed(self, *_):
        self._copy_to_manual_update_enabled()
        indices = self.selected_indices()
        # set of all methods for the current selection
        methods = [self.var_state[i].method for i in indices]

        def key(method):
            if isinstance(method, Default):
                return Default, (None, )
            return type(method), tuple(method)

        mset = list(unique_everseen(methods, key=key))

        self.controlbox.setEnabled(len(mset) > 0)
        if len(mset) == 1:
            method = mset.pop()
            self.method = Methods.from_method(method)
            if isinstance(method, (EqualFreq, EqualWidth)):
                self.k = method.k
            elif isinstance(method, Custom):
                self.cutpoints = method.points
        else:
            # deselect the current button
            self.method = -1
            bg = self.controlbox.group
            button_group_reset(bg)
        self._update_spin_positions()

    def selected_indices(self):
        rows = self.varview.selectionModel().selectedRows()
        return [index.row() for index in rows]

    def method_for_index(self, index):
        state = self.var_state[index]
        return state.method

    def discretized_var(self, index):
        # type: (int) -> Optional[Orange.data.DiscreteVariable]
        state = self.var_state[index]
        if state.disc_var is not None and state.points == []:
            # Removed by MDL Entropy
            return None
        else:
            return state.disc_var

    def discretized_domain(self):
        """
        Return the current effective discretized domain.
        """
        if self.data is None:
            return None

        # a mapping of all applied changes for variables in `varmodel`
        mapping = {
            var: self.discretized_var(i)
            for i, var in enumerate(self.varmodel)
        }

        def disc_var(source):
            return mapping.get(source, source)

        # map the full input domain to the new variables (where applicable)
        attributes = [disc_var(v) for v in self.data.domain.attributes]
        attributes = [v for v in attributes if v is not None]

        class_vars = [disc_var(v) for v in self.data.domain.class_vars]
        class_vars = [v for v in class_vars if v is not None]

        domain = Orange.data.Domain(attributes,
                                    class_vars,
                                    metas=self.data.domain.metas)
        return domain

    def commit(self):
        output = None
        if self.data is not None:
            domain = self.discretized_domain()
            output = self.data.transform(domain)

        summary = len(output) if output else self.info.NoOutput
        details = format_summary_details(output) if output else ""
        self.info.set_output_summary(summary, details)
        self.Outputs.data.send(output)

    def storeSpecificSettings(self):
        super().storeSpecificSettings()
        self.saved_var_states = {
            variable_key(var): self.var_state[i]._replace(points=None,
                                                          disc_var=None)
            for i, var in enumerate(self.varmodel)
        }

    def send_report(self):
        self.report_items(
            (("Default method", self.options[self.default_method][1]), ))
        if self.varmodel:
            self.report_items(
                "Thresholds",
                [(var.name,
                  DiscDelegate.cutsText(self.var_state[i], var.repr_val)
                  or "leave numeric") for i, var in enumerate(self.varmodel)])

    @classmethod
    def migrate_settings(cls, settings, version):  # pylint: disable=redefined-outer-name
        if version is None or version < 2:
            # was stored as int indexing Methods (but offset by 1)
            default = settings.pop("default_method", 0)
            default = Methods(default + 1)
            settings["default_method_name"] = default.name
Exemplo n.º 19
0
class LabelSelectionWidget(QWidget):
    """
    A widget for selection of label values.

    The widget displays the contents of the model with two widgets:

    * The top level model items are displayed in a combo box.
    * The children of the current selected top level item are
      displayed in a subordinate list view.

    .. note:: This is not a QAbstractItemView subclass.
    """

    # Current group/root index has changed.
    groupChanged = Signal(int)
    # Selection for the current group/root has changed.
    groupSelectionChanged = Signal(int)

    def __init__(self):
        super().__init__()
        self.__model = None
        self.__selectionMode = QListView.ExtendedSelection

        self.__currentIndex = -1
        self.__selections = {}

        self.__parent = None

        self.targets = []

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)

        def group_box(title):
            box = QGroupBox(title)
            box.setFlat(True)
            lay = QVBoxLayout()
            lay.setContentsMargins(0, 0, 0, 0)
            box.setLayout(lay)
            return box

        self.labels_combo = QComboBox()
        self.values_view = QListView(selectionMode=self.__selectionMode)

        self.labels_combo.currentIndexChanged.connect(
            self.__onCurrentIndexChanged)

        l_box = group_box(self.tr("Label"))
        v_box = group_box(self.tr("Values"))

        l_box.layout().addWidget(self.labels_combo)
        v_box.layout().addWidget(self.values_view)

        layout.addWidget(l_box)
        layout.addWidget(v_box)

        self.setLayout(layout)

        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

    def set_selection(self):
        model = self.model()

        # FIX: This assumes fixed target key/values order.
        indices = [
            model.index(i, 0, model.index(keyind, 0))
            for keyind, selected in enumerate(self.__parent.stored_selections)
            for i in selected
        ]

        selection = itemselection(indices)
        self.setCurrentGroupIndex(self.__parent.current_group_index)
        self.setSelection(selection)

    def selected_split(self):
        group_index = self.currentGroupIndex()
        if not (0 <= group_index < len(self.targets)):
            return None, []

        group = self.targets[group_index]
        selection = [
            ind.row() for ind in self.currentGroupSelection().indexes()
        ]
        return group, selection

    def set_data(self, parent, data):
        """ Initialize widget state after receiving new data.
        """
        self.__parent = parent
        # self.__currentIndex = parent.current_group_index
        if data is not None:
            column_groups, row_groups = group_candidates(data)

            self.targets = column_groups + row_groups

            model = QStandardItemModel()
            for item in [standarditem_from(desc) for desc in self.targets]:
                model.appendRow(item)

            self.setModel(model)
        else:
            self.targets = []
            self.setModel(None)

    def clear(self):
        """ Clear the widget/model (same as ``setModel(None)``).
        """
        if self.__model is not None:
            self.values_view.selectionModel().clearSelection()
            self.values_view.selectionModel().selectionChanged.disconnect(
                self.__onSelectionChanged)

            self.values_view.setModel(None)
            self.labels_combo.setModel(QStandardItemModel(self.labels_combo))
            self.__currentIndex = -1
            self.__selections = {}
            self.__model = None

    def setModel(self, model):
        """
        Set the source model for display.

        The model should be a tree model with depth 2.

        Parameters
        ----------
        model : QtCore.QAbstractItemModel
        """
        if model is self.__model:
            return

        self.clear()

        if model is None:
            return

        self.__model = model
        self.values_view.setModel(model)
        self.values_view.setRootIndex(model.index(0, 0))

        self.values_view.selectionModel().selectionChanged.connect(
            self.__onSelectionChanged)
        # will emit the currentIndexChanged (if the model is not empty)
        self.labels_combo.setModel(model)

    def model(self):
        """
        Return the current model.

        Returns
        -------
        model : QtCore.QAbstractItemModel
        """
        return self.__model

    def setCurrentGroupIndex(self, index):
        """
        Set the current selected group/root index row.

        Parameters
        ----------
        index : int
            Group index.
        """
        self.labels_combo.setCurrentIndex(index)

    def currentGroupIndex(self):
        """
        Return the current selected group/root index row.

        Returns
        -------
        row : index
            Current group row index (-1 if there is no current index)
        """
        return self.labels_combo.currentIndex()

    def setSelection(self, selection):
        """
        Set the model item selection.

        Parameters
        ----------
        selection : QtCore.QItemSelection
            Item selection.
        """
        if self.values_view.selectionModel() is not None:
            indices = selection.indexes()
            pind = defaultdict(list)

            for index in indices:
                parent = index.parent()
                if parent.isValid():
                    if parent == self.__model.index(parent.row(),
                                                    parent.column()):
                        pind[parent.row()].append(QPersistentModelIndex(index))
                    else:
                        warnings.warn("Die Die Die")
                else:
                    # top level index
                    pass

            self.__selections = pind
            self.__restoreSelection()

    def selection(self):
        """
        Return the item selection.

        Returns
        -------
        selection : QtCore.QItemSelection
        """
        selection = QItemSelection()
        if self.__model is None:
            return selection

        for pind in chain(*self.__selections.values()):
            ind = self.__model.index(pind.row(), pind.column(), pind.parent())
            if ind.isValid():
                selection.select(ind, ind)
        return selection

    def currentGroupSelection(self):
        """
        Return the item selection for the current group only.
        """
        if self.values_view.selectionModel() is not None:
            return self.values_view.selectionModel().selection()
        else:
            return QItemSelection()

    def __onCurrentIndexChanged(self, index):
        self.__storeSelection(self.__currentIndex,
                              self.values_view.selectedIndexes())

        self.__currentIndex = index
        if self.__model is not None:
            root = self.__model.index(index, 0)
            self.values_view.setRootIndex(root)

            self.__restoreSelection()
        self.groupChanged.emit(index)

    def __onSelectionChanged(self, old, new):
        self.__storeSelection(self.__currentIndex,
                              self.values_view.selectedIndexes())

        self.groupSelectionChanged.emit(self.__currentIndex)

    def __storeSelection(self, groupind, indices):
        # Store current values selection for the current group
        groupind = self.__currentIndex
        indices = [
            QPersistentModelIndex(ind)
            for ind in self.values_view.selectedIndexes()
        ]
        self.__selections[groupind] = indices

    def __restoreSelection(self):
        # Restore previous selection for root (if available)
        assert self.__model is not None
        groupind = self.__currentIndex
        root = self.__model.index(groupind, 0)
        sel = self.__selections.get(groupind, [])
        indices = [
            self.__model.index(pind.row(), pind.column(), root) for pind in sel
            if pind.isValid() and pind.parent() == root
        ]

        selection = QItemSelection()
        for ind in indices:
            selection.select(ind, ind)
        self.values_view.selectionModel().select(
            selection, QItemSelectionModel.ClearAndSelect)

    def sizeHint(self):
        """Reimplemented from QWidget.sizeHint"""
        return QSize(100, 200)
Exemplo n.º 20
0
class OWDiscretize(widget.OWWidget):
    name = "Discretize"
    description = "Discretize the numeric data features."
    icon = "icons/Discretize.svg"
    inputs = [
        InputSignal("Data",
                    Orange.data.Table,
                    "set_data",
                    doc="Input data table")
    ]
    outputs = [
        OutputSignal("Data",
                     Orange.data.Table,
                     doc="Table with discretized features")
    ]

    settingsHandler = settings.DomainContextHandler()
    saved_var_states = settings.ContextSetting({})

    default_method = settings.Setting(2)
    default_k = settings.Setting(3)
    autosend = settings.Setting(True)

    #: Discretization methods
    Default, Leave, MDL, EqualFreq, EqualWidth, Remove, Custom = range(7)

    want_main_area = False
    resizing_enabled = False

    def __init__(self):
        super().__init__()

        #: input data
        self.data = None
        #: Current variable discretization state
        self.var_state = {}
        #: Saved variable discretization settings (context setting)
        self.saved_var_states = {}

        self.method = 0
        self.k = 5

        box = gui.vBox(self.controlArea, self.tr("Default Discretization"))
        self.default_bbox = rbox = gui.radioButtons(
            box, self, "default_method", callback=self._default_disc_changed)
        rb = gui.hBox(rbox)
        self.left = gui.vBox(rb)
        right = gui.vBox(rb)
        rb.layout().setStretch(0, 1)
        rb.layout().setStretch(1, 1)
        options = self.options = [
            self.tr("Default"),
            self.tr("Leave numeric"),
            self.tr("Entropy-MDL discretization"),
            self.tr("Equal-frequency discretization"),
            self.tr("Equal-width discretization"),
            self.tr("Remove numeric variables")
        ]

        for opt in options[1:]:
            t = gui.appendRadioButton(rbox, opt)
            # This condition is ugly, but it keeps the same order of
            # options for backward compatibility of saved schemata
            [right, self.left][opt.startswith("Equal")].layout().addWidget(t)
        gui.separator(right, 18, 18)

        def _intbox(widget, attr, callback):
            box = gui.indentedBox(widget)
            s = gui.spin(box,
                         self,
                         attr,
                         minv=2,
                         maxv=10,
                         label="Num. of intervals:",
                         callback=callback)
            s.setMaximumWidth(60)
            s.setAlignment(Qt.AlignRight)
            gui.rubber(s.box)
            return box.box

        self.k_general = _intbox(self.left, "default_k",
                                 self._default_disc_changed)
        self.k_general.layout().setContentsMargins(0, 0, 0, 0)
        vlayout = QHBoxLayout()
        box = gui.widgetBox(self.controlArea,
                            "Individual Attribute Settings",
                            orientation=vlayout,
                            spacing=8)

        # List view with all attributes
        self.varview = QListView(selectionMode=QListView.ExtendedSelection)
        self.varview.setItemDelegate(DiscDelegate())
        self.varmodel = itemmodels.VariableListModel()
        self.varview.setModel(self.varmodel)
        self.varview.selectionModel().selectionChanged.connect(
            self._var_selection_changed)

        vlayout.addWidget(self.varview)
        # Controls for individual attr settings
        self.bbox = controlbox = gui.radioButtons(
            box, self, "method", callback=self._disc_method_changed)
        vlayout.addWidget(controlbox)

        for opt in options[:5]:
            gui.appendRadioButton(controlbox, opt)

        self.k_specific = _intbox(controlbox, "k", self._disc_method_changed)

        gui.appendRadioButton(controlbox, "Remove attribute")

        gui.rubber(controlbox)
        controlbox.setEnabled(False)

        self.controlbox = controlbox

        box = gui.auto_commit(self.controlArea,
                              self,
                              "autosend",
                              "Apply",
                              orientation=Qt.Horizontal,
                              checkbox_label="Apply automatically")
        box.layout().insertSpacing(0, 20)
        box.layout().insertWidget(0, self.report_button)
        self._update_spin_positions()

    def set_data(self, data):
        self.closeContext()
        self.data = data
        if self.data is not None:
            self._initialize(data)
            self.openContext(data)
            # Restore the per variable discretization settings
            self._restore(self.saved_var_states)
            # Complete the induction of cut points
            self._update_points()
        else:
            self._clear()
        self.unconditional_commit()

    def _initialize(self, data):
        # Initialize the default variable states for new data.
        self.class_var = data.domain.class_var
        cvars = [var for var in data.domain if var.is_continuous]
        self.varmodel[:] = cvars

        class_var = data.domain.class_var
        has_disc_class = data.domain.has_discrete_class

        self.default_bbox.buttons[self.MDL - 1].setEnabled(has_disc_class)
        self.bbox.buttons[self.MDL].setEnabled(has_disc_class)

        # If the newly disabled MDL button is checked then change it
        if not has_disc_class and self.default_method == self.MDL - 1:
            self.default_method = 0
        if not has_disc_class and self.method == self.MDL:
            self.method = 0

        # Reset (initialize) the variable discretization states.
        self._reset()

    def _restore(self, saved_state):
        # Restore variable states from a saved_state dictionary.
        def_method = self._current_default_method()
        for i, var in enumerate(self.varmodel):
            key = variable_key(var)
            if key in saved_state:
                state = saved_state[key]
                if isinstance(state.method, Default):
                    state = DState(Default(def_method), None, None)
                self._set_var_state(i, state)

    def _reset(self):
        # restore the individual variable settings back to defaults.
        def_method = self._current_default_method()
        self.var_state = {}
        for i in range(len(self.varmodel)):
            state = DState(Default(def_method), None, None)
            self._set_var_state(i, state)

    def _set_var_state(self, index, state):
        # set the state of variable at `index` to `state`.
        self.var_state[index] = state
        self.varmodel.setData(self.varmodel.index(index), state, Qt.UserRole)

    def _clear(self):
        self.data = None
        self.varmodel[:] = []
        self.var_state = {}
        self.saved_var_states = {}
        self.default_bbox.buttons[self.MDL - 1].setEnabled(True)
        self.bbox.buttons[self.MDL].setEnabled(True)

    def _update_points(self):
        """
        Update the induced cut points.
        """
        if self.data is None or not len(self.data):
            return

        def induce_cuts(method, data, var):
            dvar = _dispatch[type(method)](method, data, var)
            if dvar is None:
                # removed
                return [], None
            elif dvar is var:
                # no transformation took place
                return None, var
            elif is_discretized(dvar):
                return dvar.compute_value.points, dvar
            else:
                assert False

        for i, var in enumerate(self.varmodel):
            state = self.var_state[i]
            if state.points is None and state.disc_var is None:
                points, dvar = induce_cuts(state.method, self.data, var)
                new_state = state._replace(points=points, disc_var=dvar)
                self._set_var_state(i, new_state)

    def _method_index(self, method):
        return METHODS.index((type(method), ))

    def _current_default_method(self):
        method = self.default_method + 1
        k = self.default_k
        if method == OWDiscretize.Leave:
            def_method = Leave()
        elif method == OWDiscretize.MDL:
            def_method = MDL()
        elif method == OWDiscretize.EqualFreq:
            def_method = EqualFreq(k)
        elif method == OWDiscretize.EqualWidth:
            def_method = EqualWidth(k)
        elif method == OWDiscretize.Remove:
            def_method = Remove()
        else:
            assert False
        return def_method

    def _current_method(self):
        if self.method == OWDiscretize.Default:
            method = Default(self._current_default_method())
        elif self.method == OWDiscretize.Leave:
            method = Leave()
        elif self.method == OWDiscretize.MDL:
            method = MDL()
        elif self.method == OWDiscretize.EqualFreq:
            method = EqualFreq(self.k)
        elif self.method == OWDiscretize.EqualWidth:
            method = EqualWidth(self.k)
        elif self.method == OWDiscretize.Remove:
            method = Remove()
        elif self.method == OWDiscretize.Custom:
            method = Custom(self.cutpoints)
        else:
            assert False
        return method

    def _update_spin_positions(self):
        self.k_general.setDisabled(self.default_method not in [2, 3])
        if self.default_method == 2:
            self.left.layout().insertWidget(1, self.k_general)
        elif self.default_method == 3:
            self.left.layout().insertWidget(2, self.k_general)

        self.k_specific.setDisabled(self.method not in [3, 4])
        if self.method == 3:
            self.bbox.layout().insertWidget(4, self.k_specific)
        elif self.method == 4:
            self.bbox.layout().insertWidget(5, self.k_specific)

    def _default_disc_changed(self):
        self._update_spin_positions()
        method = self._current_default_method()
        state = DState(Default(method), None, None)
        for i, _ in enumerate(self.varmodel):
            if isinstance(self.var_state[i].method, Default):
                self._set_var_state(i, state)
        self._update_points()
        self.commit()

    def _disc_method_changed(self):
        self._update_spin_positions()
        indices = self.selected_indices()
        method = self._current_method()
        state = DState(method, None, None)
        for idx in indices:
            self._set_var_state(idx, state)
        self._update_points()
        self.commit()

    def _var_selection_changed(self, *args):
        indices = self.selected_indices()
        # set of all methods for the current selection
        methods = [self.var_state[i].method for i in indices]
        mset = set(methods)
        self.controlbox.setEnabled(len(mset) > 0)
        if len(mset) == 1:
            method = mset.pop()
            self.method = self._method_index(method)
            if isinstance(method, (EqualFreq, EqualWidth)):
                self.k = method.k
            elif isinstance(method, Custom):
                self.cutpoints = method.points
        else:
            # deselect the current button
            self.method = -1
            bg = self.controlbox.group
            button_group_reset(bg)
        self._update_spin_positions()

    def selected_indices(self):
        rows = self.varview.selectionModel().selectedRows()
        return [index.row() for index in rows]

    def discretized_var(self, source):
        index = list(self.varmodel).index(source)
        state = self.var_state[index]
        if state.disc_var is None:
            return None
        elif state.disc_var is source:
            return source
        elif state.points == []:
            return None
        else:
            return state.disc_var

    def discretized_domain(self):
        """
        Return the current effective discretized domain.
        """
        if self.data is None:
            return None

        def disc_var(source):
            if source and source.is_continuous:
                return self.discretized_var(source)
            else:
                return source

        attributes = [disc_var(v) for v in self.data.domain.attributes]
        attributes = [v for v in attributes if v is not None]

        class_var = disc_var(self.data.domain.class_var)

        domain = Orange.data.Domain(attributes,
                                    class_var,
                                    metas=self.data.domain.metas)
        return domain

    def commit(self):
        output = None
        if self.data is not None and len(self.data):
            domain = self.discretized_domain()
            output = self.data.transform(domain)
        self.send("Data", output)

    def storeSpecificSettings(self):
        super().storeSpecificSettings()
        self.saved_var_states = {
            variable_key(var): self.var_state[i]._replace(points=None,
                                                          disc_var=None)
            for i, var in enumerate(self.varmodel)
        }

    def send_report(self):
        self.report_items(
            (("Default method", self.options[self.default_method + 1]), ))
        if self.varmodel:
            self.report_items(
                "Thresholds",
                [(var.name, DiscDelegate.cutsText(self.var_state[i])
                  or "leave numeric") for i, var in enumerate(self.varmodel)])
Exemplo n.º 21
0
class OWOntology(OWWidget, ConcurrentWidgetMixin):
    name = "Ontology"
    description = ""
    icon = "icons/Ontology.svg"
    priority = 1110
    keywords = []

    CACHED, LIBRARY = range(2)  # library list modification types
    RUN_BUTTON, INC_BUTTON = "Generate", "Include"

    settingsHandler = DomainContextHandler()
    ontology_library: List[Dict] = Setting([
        {"name": Ontology.generate_name([]), "ontology": {}},
    ])
    ontology_index: int = Setting(0)
    ontology: OntoType = Setting((), schema_only=True)
    include_children = Setting(True)
    auto_commit = Setting(True)

    class Inputs:
        words = Input("Words", Table)

    class Outputs:
        words = Output("Words", Table, dynamic=False)

    class Warning(OWWidget.Warning):
        no_words_column = Msg("Input is missing 'Words' column.")

    class Error(OWWidget.Error):
        load_error = Msg("{}")

    def __init__(self):
        OWWidget.__init__(self)
        ConcurrentWidgetMixin.__init__(self)
        self.__onto_handler = OntologyHandler()

        flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
        self.__model = PyListModel([], self, flags=flags)
        self.__input_model = QStandardItemModel()
        self.__library_view: QListView = None
        self.__input_view: ListViewSearch = None
        self.__ontology_view: EditableTreeView = None
        self.ontology_info = ""

        self._setup_gui()
        self._restore_state()
        self.settingsAboutToBePacked.connect(self._save_state)

    def _setup_gui(self):
        # control area
        library_box: QGroupBox = gui.vBox(self.controlArea, "Library")
        library_box.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Maximum)

        edit_triggers = QListView.DoubleClicked | QListView.EditKeyPressed
        self.__library_view = QListView(
            editTriggers=int(edit_triggers),
            minimumWidth=200,
            sizePolicy=QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Expanding),
        )
        self.__library_view.setFixedHeight(100)
        self.__library_view.setItemDelegate(LibraryItemDelegate(self))
        self.__library_view.setModel(self.__model)
        self.__library_view.selectionModel().selectionChanged.connect(
            self.__on_selection_changed
        )

        actions_widget = ModelActionsWidget()
        actions_widget.layout().setSpacing(1)

        tool_tip = "Add a new ontology to the library"
        action = QAction("+", self, toolTip=tool_tip)
        action.triggered.connect(self.__on_add)
        actions_widget.addAction(action)

        tool_tip = "Remove the ontology from the library"
        action = QAction("\N{MINUS SIGN}", self, toolTip=tool_tip)
        action.triggered.connect(self.__on_remove)
        actions_widget.addAction(action)

        tool_tip = "Save changes in the editor to the library"
        action = QAction("Update", self, toolTip=tool_tip)
        action.triggered.connect(self.__on_update)
        actions_widget.addAction(action)

        gui.rubber(actions_widget)

        action = QAction("More", self, toolTip="More actions")

        new_from_file = QAction("Import Ontology from File", self)
        new_from_file.triggered.connect(self.__on_import_file)

        new_from_url = QAction("Import Ontology from URL", self)
        new_from_url.triggered.connect(self.__on_import_url)

        save_to_file = QAction("Save Ontology to File", self)
        save_to_file.triggered.connect(self.__on_save)

        menu = QMenu(actions_widget)
        menu.addAction(new_from_file)
        menu.addAction(new_from_url)
        menu.addAction(save_to_file)
        action.setMenu(menu)
        button = actions_widget.addAction(action)
        button.setPopupMode(QToolButton.InstantPopup)

        vlayout = QVBoxLayout()
        vlayout.setSpacing(1)
        vlayout.setContentsMargins(0, 0, 0, 0)
        vlayout.addWidget(self.__library_view)
        vlayout.addWidget(actions_widget)

        library_box.layout().setSpacing(1)
        library_box.layout().addLayout(vlayout)

        input_box: QGroupBox = gui.vBox(self.controlArea, "Input")
        self.__input_view = ListViewSearch(
            sizePolicy=QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Expanding),
            selectionMode=QListView.ExtendedSelection,
            dragEnabled=True,
        )
        self.__input_view.setModel(self.__input_model)
        self.__input_view.selectionModel().selectionChanged.connect(
            self._enable_include_button
        )

        self.__inc_button = gui.button(
            None, self, self.INC_BUTTON, enabled=False,
            toolTip="Include selected words into the ontology",
            autoDefault=False, callback=self.__on_toggle_include
        )

        input_box.layout().setSpacing(1)
        input_box.layout().addWidget(self.__input_view)
        input_box.layout().addWidget(self.__inc_button)

        self.__run_button = gui.button(
            self.controlArea, self, self.RUN_BUTTON,
            callback=self.__on_toggle_run
        )
        gui.checkBox(
            self.controlArea, self, "include_children", "Include subtree",
            box="Output", callback=self.commit.deferred
        )
        box = gui.vBox(self.controlArea, "Ontology info")
        gui.label(box, self, "%(ontology_info)s")

        gui.auto_send(self.buttonsArea, self, "auto_commit")

        # main area
        ontology_box: QGroupBox = gui.vBox(self.mainArea, box=True)

        self.__ontology_view = EditableTreeView(self)
        self.__ontology_view.dataChanged.connect(
            self.__on_ontology_data_changed
        )
        self.__ontology_view.selectionChanged.connect(self.commit.deferred)

        ontology_box.layout().setSpacing(1)
        ontology_box.layout().addWidget(self.__ontology_view)

        self._enable_include_button()

    def __on_selection_changed(self, selection: QItemSelection, *_):
        self.Error.load_error.clear()
        if selection.indexes():
            self.ontology_index = row = selection.indexes()[0].row()
            data = self.__model[row].cached_word_tree
            self.__ontology_view.set_data(data)
            self.__update_score()
            error_msg = self.__model[row].error_msg
            if error_msg:
                self.Error.load_error(error_msg)

    def __on_add(self):
        name = Ontology.generate_name([l.name for l in self.__model])
        data = self.__ontology_view.get_data()
        self.__model.append(Ontology(name, data))
        self.__set_selected_row(len(self.__model) - 1)

    def __on_remove(self):
        index = self.__get_selected_row()
        if index is not None:
            del self.__model[index]
            self.__set_selected_row(max(index - 1, 0))

    def __on_update(self):
        self.__set_current_modified(self.LIBRARY)

    def __on_import_file(self):
        ontology = read_from_file(self)
        self._import_ontology(ontology)

    def __on_import_url(self):
        ontology = read_from_url(self)
        self._import_ontology(ontology)

    def __on_save(self):
        index = self.__get_selected_row()
        if index is not None:
            filename = self.__model[index].filename
            if filename:
                filename, _ = os.path.splitext(filename)
        else:
            filename = os.path.expanduser("~/")
        save_ontology(self, filename, self.__ontology_view.get_data())
        QApplication.setActiveWindow(self)

    def __on_toggle_include(self):
        if self.task is not None:
            self._cancel_tasks()
        else:
            self._run_insert()

    def __on_toggle_run(self):
        if self.task is not None:
            self._cancel_tasks()
        else:
            self._run()

    def __on_ontology_data_changed(self):
        self.__set_current_modified(self.CACHED)
        self.__update_score()
        self._enable_include_button()
        self.commit.deferred()

    @Inputs.words
    def set_words(self, words: Optional[Table]):
        self.Warning.no_words_column.clear()
        self.__input_model.clear()
        if words:
            if WORDS_COLUMN_NAME in words.domain and words.domain[
                    WORDS_COLUMN_NAME].attributes.get("type") == "words":
                for word in words.get_column_view(WORDS_COLUMN_NAME)[0]:
                    self.__input_model.appendRow(QStandardItem(word))
            else:
                self.Warning.no_words_column()

    @gui.deferred
    def commit(self):
        if self.include_children:
            words = self.__ontology_view.get_selected_words_with_children()
        else:
            words = self.__ontology_view.get_selected_words()
        words_table = self._create_output_table(sorted(words))
        self.Outputs.words.send(words_table)

    @staticmethod
    def _create_output_table(words: List[str]) -> Optional[Table]:
        if not words:
            return None
        return create_words_table(words)

    def _cancel_tasks(self):
        self.cancel()
        self.__inc_button.setText(self.INC_BUTTON)
        self.__run_button.setText(self.RUN_BUTTON)

    def _run(self):
        self.__run_button.setText("Stop")
        words = self.__ontology_view.get_words()
        handler = self.__onto_handler.generate
        self.start(_run, handler, (words,))

    def _run_insert(self):
        self.__inc_button.setText("Stop")
        tree = self.__ontology_view.get_data()
        words = self.__get_selected_input_words()
        handler = self.__onto_handler.insert
        self.start(_run, handler, (tree, words))

    def on_done(self, data: Dict):
        self.__inc_button.setText(self.INC_BUTTON)
        self.__run_button.setText(self.RUN_BUTTON)
        self.__ontology_view.set_data(data, keep_history=True)
        self.__set_current_modified(self.CACHED)
        self.__update_score()

    def __update_score(self):
        tree = self.__ontology_view.get_data()
        score = round(self.__onto_handler.score(tree), 2) \
            if len(tree) == 1 and list(tree.values())[0] else "/"
        self.ontology_info = f"Score: {score}"

    def on_exception(self, ex: Exception):
        raise ex

    def on_partial_result(self, _: Any):
        pass

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

    def __set_selected_row(self, row: int):
        self.__library_view.selectionModel().select(
            self.__model.index(row, 0), QItemSelectionModel.ClearAndSelect
        )

    def __get_selected_row(self) -> Optional[int]:
        rows = self.__library_view.selectionModel().selectedRows()
        return rows[0].row() if rows else None

    def __set_current_modified(self, mod_type: int):
        index = self.__get_selected_row()
        if index is not None:
            if mod_type == self.LIBRARY:
                ontology = self.__ontology_view.get_data()
                self.__model[index].word_tree = ontology
                self.__model[index].cached_word_tree = ontology
                self.__model[index].update_rule_flag = Ontology.NotModified
            elif mod_type == self.CACHED:
                ontology = self.__ontology_view.get_data()
                self.__model[index].cached_word_tree = ontology
            else:
                raise NotImplementedError
            self.__model.emitDataChanged(index)
            self.__library_view.repaint()

    def __get_selected_input_words(self) -> List[str]:
        return [self.__input_view.model().data(index) for index in
                self.__input_view.selectedIndexes()]

    def _import_ontology(self, ontology: Ontology):
        if ontology is not None:
            self.__model.append(ontology)
            self.__set_selected_row(len(self.__model) - 1)
        QApplication.setActiveWindow(self)

    def _restore_state(self):
        source = [Ontology.from_dict(s) for s in self.ontology_library]
        self.__model.wrap(source)
        self.__set_selected_row(self.ontology_index)
        if self.ontology:
            self.__ontology_view.set_data(self.ontology)
            self.__set_current_modified(self.CACHED)
            self.__update_score()
            self.commit.now()

    def _save_state(self):
        self.ontology_library = [s.as_dict() for s in self.__model]
        self.ontology = self.__ontology_view.get_data(with_selection=True)

    def _enable_include_button(self):
        tree = self.__ontology_view.get_data()
        words = self.__get_selected_input_words()
        enabled = len(tree) == 1 and len(words) > 0
        self.__inc_button.setEnabled(enabled)

    def send_report(self):
        model = self.__model
        library = model[self.ontology_index].name if model else "/"
        self.report_items("Settings", [("Library", library)])

        ontology = self.__ontology_view.get_data()
        style = """
        <style>
            ul {
                padding-top: 0px;
                padding-right: 0px;
                padding-bottom: 0px;
                padding-left: 20px;
            }
        </style>
        """
        self.report_raw("Ontology", style + _tree_to_html(ontology))
Exemplo n.º 22
0
class OWQualityControl(widget.OWWidget):
    name = "Quality Control"
    description = "Experiment quality control"
    icon = "../widgets/icons/QualityControl.svg"
    priority = 5000

    inputs = [("Experiment Data", Orange.data.Table, "set_data")]
    outputs = []

    DISTANCE_FUNCTIONS = [("Distance from Pearson correlation",
                           dist_pcorr),
                          ("Euclidean distance",
                           dist_eucl),
                          ("Distance from Spearman correlation",
                           dist_spearman)]

    settingsHandler = SetContextHandler()

    split_by_labels = settings.ContextSetting({})
    sort_by_labels = settings.ContextSetting({})

    selected_distance_index = settings.Setting(0)

    def __init__(self, parent=None):
        super().__init__(parent)

        ## Attributes
        self.data = None
        self.distances = None
        self.groups = None
        self.unique_pos = None
        self.base_group_index = 0

        ## GUI
        box = gui.widgetBox(self.controlArea, "Info")
        self.info_box = gui.widgetLabel(box, "\n")

        ## Separate By box
        box = gui.widgetBox(self.controlArea, "Separate By")
        self.split_by_model = itemmodels.PyListModel(parent=self)
        self.split_by_view = QListView()
        self.split_by_view.setSelectionMode(QListView.ExtendedSelection)
        self.split_by_view.setModel(self.split_by_model)
        box.layout().addWidget(self.split_by_view)

        self.split_by_view.selectionModel().selectionChanged.connect(
            self.on_split_key_changed)

        ## Sort By box
        box = gui.widgetBox(self.controlArea, "Sort By")
        self.sort_by_model = itemmodels.PyListModel(parent=self)
        self.sort_by_view = QListView()
        self.sort_by_view.setSelectionMode(QListView.ExtendedSelection)
        self.sort_by_view.setModel(self.sort_by_model)
        box.layout().addWidget(self.sort_by_view)

        self.sort_by_view.selectionModel().selectionChanged.connect(
            self.on_sort_key_changed)

        ## Distance box
        box = gui.widgetBox(self.controlArea, "Distance Measure")
        gui.comboBox(box, self, "selected_distance_index",
                     items=[name for name, _ in self.DISTANCE_FUNCTIONS],
                     callback=self.on_distance_measure_changed)

        self.scene = QGraphicsScene()
        self.scene_view = QGraphicsView(self.scene)
        self.scene_view.setRenderHints(QPainter.Antialiasing)
        self.scene_view.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
        self.mainArea.layout().addWidget(self.scene_view)

        self.scene_view.installEventFilter(self)

        self._disable_updates = False
        self._cached_distances = {}
        self._base_index_hints = {}
        self.main_widget = None

        self.resize(800, 600)

    def clear(self):
        """Clear the widget state."""
        self.data = None
        self.distances = None
        self.groups = None
        self.unique_pos = None

        with disable_updates(self):
            self.split_by_model[:] = []
            self.sort_by_model[:] = []

        self.main_widget = None
        self.scene.clear()
        self.info_box.setText("\n")
        self._cached_distances = {}

    def set_data(self, data=None):
        """Set input experiment data."""
        self.closeContext()
        self.clear()

        self.error(0)
        self.warning(0)

        if data is not None:
            keys = self.get_suitable_keys(data)
            if not keys:
                self.error(0, "Data has no suitable feature labels.")
                data = None

        self.data = data
        if data is not None:
            self.on_new_data()

    def update_label_candidates(self):
        """Update the label candidates selection GUI 
        (Group/Sort By views).

        """
        keys = self.get_suitable_keys(self.data)
        with disable_updates(self):
            self.split_by_model[:] = keys
            self.sort_by_model[:] = keys

    def get_suitable_keys(self, data):
        """ Return suitable attr label keys from the data where
        the key has at least two unique values in the data.

        """
        attrs = [attr.attributes.items() for attr in data.domain.attributes]
        attrs = reduce(operator.iadd, attrs, [])
        # in case someone put non string values in attributes dict
        attrs = [(str(key), str(value)) for key, value in attrs]
        attrs = set(attrs)
        values = defaultdict(set)
        for key, value in attrs:
            values[key].add(value)
        keys = [key for key in values if len(values[key]) > 1]
        return keys

    def selected_split_by_labels(self):
        """Return the current selected split labels.
        """
        sel_m = self.split_by_view.selectionModel()
        indices = [r.row() for r in sel_m.selectedRows()]
        return [self.sort_by_model[i] for i in indices]

    def selected_sort_by_labels(self):
        """Return the current selected sort labels
        """
        sel_m = self.sort_by_view.selectionModel()
        indices = [r.row() for r in sel_m.selectedRows()]
        return [self.sort_by_model[i] for i in indices]

    def selected_distance(self):
        """Return the selected distance function.
        """
        return self.DISTANCE_FUNCTIONS[self.selected_distance_index][1]

    def selected_base_group_index(self):
        """Return the selected base group index
        """
        return self.base_group_index

    def selected_base_indices(self, base_group_index=None):
        indices = []
        for g, ind in self.groups:
            if base_group_index is None:
                label = group_label(self.selected_split_by_labels(), g)
                ind = [i for i in ind if i is not None]
                i = self._base_index_hints.get(label, ind[0] if ind else None)
            else:
                i = ind[base_group_index]
            indices.append(i)
        return indices

    def on_new_data(self):
        """We have new data and need to recompute all.
        """
        self.closeContext()

        self.update_label_candidates()
        self.info_box.setText(
            "%s genes \n%s experiments" %
            (len(self.data),  len(self.data.domain.attributes))
        )

        self.base_group_index = 0

        keys = self.get_suitable_keys(self.data)
        self.openContext(keys)

        ## Restore saved context settings (split/sort selection)
        split_by_labels = self.split_by_labels
        sort_by_labels = self.sort_by_labels

        def select(model, selection_model, selected_items):
            """Select items in a Qt item model view
            """
            all_items = list(model)
            try:
                indices = [all_items.index(item) for item in selected_items]
            except:
                indices = []
            for ind in indices:
                selection_model.select(model.index(ind),
                                       QItemSelectionModel.Select)

        with disable_updates(self):
            select(self.split_by_view.model(),
                   self.split_by_view.selectionModel(),
                   split_by_labels)

            select(self.sort_by_view.model(),
                   self.sort_by_view.selectionModel(),
                   sort_by_labels)

        with widget_disable(self):
            self.split_and_update()

    def on_split_key_changed(self, *args):
        """Split key has changed
        """
        with widget_disable(self):
            if not self._disable_updates:
                self.base_group_index = 0
                self.split_by_labels = self.selected_split_by_labels()
                self.split_and_update()

    def on_sort_key_changed(self, *args):
        """Sort key has changed
        """
        with widget_disable(self):
            if not self._disable_updates:
                self.base_group_index = 0
                self.sort_by_labels = self.selected_sort_by_labels()
                self.split_and_update()

    def on_distance_measure_changed(self):
        """Distance measure has changed
        """
        if self.data is not None:
            with widget_disable(self):
                self.update_distances()
                self.replot_experiments()

    def on_view_resize(self, size):
        """The view with the quality plot has changed
        """
        if self.main_widget:
            current = self.main_widget.size()
            self.main_widget.resize(size.width() - 6,
                                    current.height())

            self.scene.setSceneRect(self.scene.itemsBoundingRect())

    def on_rug_item_clicked(self, item):
        """An ``item`` in the quality plot has been clicked.
        """
        update = False
        sort_by_labels = self.selected_sort_by_labels()
        if sort_by_labels and item.in_group:
            ## The item is part of the group
            if item.group_index != self.base_group_index:
                self.base_group_index = item.group_index
                update = True

        else:
            if sort_by_labels:
                # If the user clicked on an background item it
                # invalidates the sorted labels selection
                with disable_updates(self):
                    self.sort_by_view.selectionModel().clear()
                    update = True

            index = item.index
            group = item.group
            label = group_label(self.selected_split_by_labels(), group)

            if self._base_index_hints.get(label, 0) != index:
                self._base_index_hints[label] = index
                update = True

        if update:
            with widget_disable(self):
                self.split_and_update()

    def eventFilter(self, obj, event):
        if obj is self.scene_view and event.type() == QEvent.Resize:
            self.on_view_resize(event.size())
        return super().eventFilter(obj, event)

    def split_and_update(self):
        """
        Split the data based on the selected sort/split labels
        and update the quality plot.

        """
        split_labels = self.selected_split_by_labels()
        sort_labels = self.selected_sort_by_labels()

        self.warning(0)
        if not split_labels:
            self.warning(0, "No separate by label selected.")

        self.groups, self.unique_pos = \
                exp.separate_by(self.data, split_labels,
                                consider=sort_labels,
                                add_empty=True)

        self.groups = sorted(self.groups.items(),
                             key=lambda t: list(map(float_if_posible, t[0])))
        self.unique_pos = sorted(self.unique_pos.items(),
                                 key=lambda t: list(map(float_if_posible, t[0])))

        if self.groups:
            if sort_labels:
                group_base = self.selected_base_group_index()
                base_indices = self.selected_base_indices(group_base)
            else:
                base_indices = self.selected_base_indices()
            self.update_distances(base_indices)
            self.replot_experiments()

    def get_cached_distances(self, measure):
        if measure not in self._cached_distances:
            attrs = self.data.domain.attributes
            mat = numpy.zeros((len(attrs), len(attrs)))

            self._cached_distances[measure] = \
                (mat, set(zip(range(len(attrs)), range(len(attrs)))))

        return self._cached_distances[measure]

    def get_cached_distance(self, measure, i, j):
        matrix, computed = self.get_cached_distances(measure)
        key = (i, j) if i < j else (j, i)
        if key in computed:
            return matrix[i, j]
        else:
            return None

    def get_distance(self, measure, i, j):
        d = self.get_cached_distance(measure, i, j)
        if d is None:
            vec_i = take_columns(self.data, [i])
            vec_j = take_columns(self.data, [j])
            d = measure(vec_i, vec_j)

            mat, computed = self.get_cached_distances(measure)
            mat[i, j] = d
            key = key = (i, j) if i < j else (j, i)
            computed.add(key)
        return d

    def store_distance(self, measure, i, j, dist):
        matrix, computed = self.get_cached_distances(measure)
        key = (i, j) if i < j else (j, i)
        matrix[j, i] = matrix[i, j] = dist
        computed.add(key)

    def update_distances(self, base_indices=()):
        """Recompute the experiment distances.
        """
        distance = self.selected_distance()
        if base_indices == ():
            base_group_index = self.selected_base_group_index()
            base_indices = [ind[base_group_index] \
                            for _, ind in self.groups]

        assert(len(base_indices) == len(self.groups))

        base_distances = []
        attributes = self.data.domain.attributes
        pb = gui.ProgressBar(self, len(self.groups) * len(attributes))

        for (group, indices), base_index in zip(self.groups, base_indices):
            # Base column of the group
            if base_index is not None:
                base_vec = take_columns(self.data, [base_index])
                distances = []
                # Compute the distances between base column
                # and all the rest data columns.
                for i in range(len(attributes)):
                    if i == base_index:
                        distances.append(0.0)
                    elif self.get_cached_distance(distance, i, base_index) is not None:
                        distances.append(self.get_cached_distance(distance, i, base_index))
                    else:
                        vec_i = take_columns(self.data, [i])
                        dist = distance(base_vec, vec_i)
                        self.store_distance(distance, i, base_index, dist)
                        distances.append(dist)
                    pb.advance()

                base_distances.append(distances)
            else:
                base_distances.append(None)

        pb.finish()
        self.distances = base_distances

    def replot_experiments(self):
        """Replot the whole quality plot.
        """
        self.scene.clear()
        labels = []

        max_dist = numpy.nanmax(list(filter(None, self.distances)))
        rug_widgets = []

        group_pen = QPen(Qt.black)
        group_pen.setWidth(2)
        group_pen.setCapStyle(Qt.RoundCap)
        background_pen = QPen(QColor(0, 0, 250, 150))
        background_pen.setWidth(1)
        background_pen.setCapStyle(Qt.RoundCap)

        main_widget = QGraphicsWidget()
        layout = QGraphicsGridLayout()
        attributes = self.data.domain.attributes
        if self.data is not None:
            for (group, indices), dist_vec in zip(self.groups, self.distances):
                indices_set = set(indices)
                rug_items = []
                if dist_vec is not None:
                    for i, attr in enumerate(attributes):
                        # Is this a within group distance or background
                        in_group = i in indices_set
                        if in_group:
                            rug_item = ClickableRugItem(dist_vec[i] / max_dist,
                                           1.0, self.on_rug_item_clicked)
                            rug_item.setPen(group_pen)
                            tooltip = experiment_description(attr)
                            rug_item.setToolTip(tooltip)
                            rug_item.group_index = indices.index(i)
                            rug_item.setZValue(rug_item.zValue() + 1)
                        else:
                            rug_item = ClickableRugItem(dist_vec[i] / max_dist,
                                           0.85, self.on_rug_item_clicked)
                            rug_item.setPen(background_pen)
                            tooltip = experiment_description(attr)
                            rug_item.setToolTip(tooltip)

                        rug_item.group = group
                        rug_item.index = i
                        rug_item.in_group = in_group

                        rug_items.append(rug_item)

                rug_widget = RugGraphicsWidget(parent=main_widget)
                rug_widget.set_rug(rug_items)

                rug_widgets.append(rug_widget)

                label = group_label(self.selected_split_by_labels(), group)
                label_item = QGraphicsSimpleTextItem(label, main_widget)
                label_item = GraphicsSimpleTextLayoutItem(label_item, parent=layout)
                label_item.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
                labels.append(label_item)

        for i, (label, rug_w) in enumerate(zip(labels, rug_widgets)):
            layout.addItem(label, i, 0, Qt.AlignVCenter)
            layout.addItem(rug_w, i, 1)
            layout.setRowMaximumHeight(i, 30)

        main_widget.setLayout(layout)
        self.scene.addItem(main_widget)
        self.main_widget = main_widget
        self.rug_widgets = rug_widgets
        self.labels = labels
        self.on_view_resize(self.scene_view.size())
Exemplo n.º 23
0
class OWPythonScript(widget.OWWidget):
    name = "Python Script"
    description = "Write a Python script and run it on input data or models."
    icon = "icons/PythonScript.svg"
    priority = 3150

    inputs = [
        ("in_data", Orange.data.Table, "setExampleTable", widget.Default),
        # ("in_distance", Orange.misc.SymMatrix, "setDistanceMatrix",
        # widget.Default),
        ("in_learner", Learner, "setLearner", widget.Default),
        ("in_classifier", Model, "setClassifier", widget.Default),
        ("in_object", object, "setObject")
    ]

    outputs = [
        (
            "out_data",
            Orange.data.Table,
        ),
        # ("out_distance", Orange.misc.SymMatrix, ),
        (
            "out_learner",
            Learner,
        ),
        ("out_classifier", Model, widget.Dynamic),
        ("out_object", object, widget.Dynamic)
    ]

    libraryListSource = \
        Setting([Script("Hello world", "print('Hello world')\n")])
    currentScriptIndex = Setting(0)
    splitterState = Setting(None)
    auto_execute = Setting(True)

    class Error(OWWidget.Error):
        pass

    def __init__(self):
        super().__init__()

        self.in_data = None
        self.in_distance = None
        self.in_learner = None
        self.in_classifier = None
        self.in_object = None

        for s in self.libraryListSource:
            s.flags = 0

        self._cachedDocuments = {}

        self.infoBox = gui.vBox(self.controlArea, 'Info')
        gui.label(
            self.infoBox, self,
            "<p>Execute python script.</p><p>Input variables:<ul><li> " + \
            "<li>".join(t.name for t in self.inputs) + \
            "</ul></p><p>Output variables:<ul><li>" + \
            "<li>".join(t.name for t in self.outputs) + \
            "</ul></p>"
        )

        self.libraryList = itemmodels.PyListModel(
            [],
            self,
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable)

        self.libraryList.wrap(self.libraryListSource)

        self.controlBox = gui.vBox(self.controlArea, 'Library')
        self.controlBox.layout().setSpacing(1)

        self.libraryView = QListView(
            editTriggers=QListView.DoubleClicked | QListView.EditKeyPressed,
            sizePolicy=QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred))
        self.libraryView.setItemDelegate(ScriptItemDelegate(self))
        self.libraryView.setModel(self.libraryList)

        self.libraryView.selectionModel().selectionChanged.connect(
            self.onSelectedScriptChanged)
        self.controlBox.layout().addWidget(self.libraryView)

        w = itemmodels.ModelActionsWidget()

        self.addNewScriptAction = action = QAction("+", self)
        action.setToolTip("Add a new script to the library")
        action.triggered.connect(self.onAddScript)
        w.addAction(action)

        action = QAction(unicodedata.lookup("MINUS SIGN"), self)
        action.setToolTip("Remove script from library")
        action.triggered.connect(self.onRemoveScript)
        w.addAction(action)

        action = QAction("Update", self)
        action.setToolTip("Save changes in the editor to library")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.triggered.connect(self.commitChangesToLibrary)
        w.addAction(action)

        action = QAction("More", self, toolTip="More actions")

        new_from_file = QAction("Import Script from File", self)
        save_to_file = QAction("Save Selected Script to File", self)
        save_to_file.setShortcut(QKeySequence(QKeySequence.SaveAs))

        new_from_file.triggered.connect(self.onAddScriptFromFile)
        save_to_file.triggered.connect(self.saveScript)

        menu = QMenu(w)
        menu.addAction(new_from_file)
        menu.addAction(save_to_file)
        action.setMenu(menu)
        button = w.addAction(action)
        button.setPopupMode(QToolButton.InstantPopup)

        w.layout().setSpacing(1)

        self.controlBox.layout().addWidget(w)

        self.execute_button = gui.auto_commit(self.controlArea,
                                              self,
                                              "auto_execute",
                                              "Execute",
                                              auto_label="Auto Execute")

        self.splitCanvas = QSplitter(Qt.Vertical, self.mainArea)
        self.mainArea.layout().addWidget(self.splitCanvas)

        self.defaultFont = defaultFont = \
            "Monaco" if sys.platform == "darwin" else "Courier"

        self.textBox = gui.vBox(self, 'Python Script')
        self.splitCanvas.addWidget(self.textBox)
        self.text = PythonScriptEditor(self)
        self.textBox.layout().addWidget(self.text)

        self.textBox.setAlignment(Qt.AlignVCenter)
        self.text.setTabStopWidth(4)

        self.text.modificationChanged[bool].connect(self.onModificationChanged)

        self.saveAction = action = QAction("&Save", self.text)
        action.setToolTip("Save script to file")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        action.triggered.connect(self.saveScript)

        self.consoleBox = gui.vBox(self, 'Console')
        self.splitCanvas.addWidget(self.consoleBox)
        self.console = PythonConsole({}, self)
        self.consoleBox.layout().addWidget(self.console)
        self.console.document().setDefaultFont(QFont(defaultFont))
        self.consoleBox.setAlignment(Qt.AlignBottom)
        self.console.setTabStopWidth(4)

        select_row(self.libraryView, self.currentScriptIndex)

        self.splitCanvas.setSizes([2, 1])
        if self.splitterState is not None:
            self.splitCanvas.restoreState(QByteArray(self.splitterState))

        self.splitCanvas.splitterMoved[int, int].connect(self.onSpliterMoved)
        self.controlArea.layout().addStretch(1)
        self.resize(800, 600)

    def setExampleTable(self, et):
        self.in_data = et

    def setDistanceMatrix(self, dm):
        self.in_distance = dm

    def setLearner(self, learner):
        self.in_learner = learner

    def setClassifier(self, classifier):
        self.in_classifier = classifier

    def setObject(self, obj):
        self.in_object = obj

    def handleNewSignals(self):
        self.unconditional_commit()

    def selectedScriptIndex(self):
        rows = self.libraryView.selectionModel().selectedRows()
        if rows:
            return [i.row() for i in rows][0]
        else:
            return None

    def setSelectedScript(self, index):
        select_row(self.libraryView, index)

    def onAddScript(self, *args):
        self.libraryList.append(Script("New script", "", 0))
        self.setSelectedScript(len(self.libraryList) - 1)

    def onAddScriptFromFile(self, *args):
        filename, _ = QFileDialog.getOpenFileName(
            self, 'Open Python Script', os.path.expanduser("~/"),
            'Python files (*.py)\nAll files(*.*)')
        if filename:
            name = os.path.basename(filename)
            # TODO: use `tokenize.detect_encoding`
            with open(filename, encoding="utf-8") as f:
                contents = f.read()
            self.libraryList.append(Script(name, contents, 0, filename))
            self.setSelectedScript(len(self.libraryList) - 1)

    def onRemoveScript(self, *args):
        index = self.selectedScriptIndex()
        if index is not None:
            del self.libraryList[index]
            select_row(self.libraryView, max(index - 1, 0))

    def onSaveScriptToFile(self, *args):
        index = self.selectedScriptIndex()
        if index is not None:
            self.saveScript()

    def onSelectedScriptChanged(self, selected, deselected):
        index = [i.row() for i in selected.indexes()]
        if index:
            current = index[0]
            if current >= len(self.libraryList):
                self.addNewScriptAction.trigger()
                return

            self.text.setDocument(self.documentForScript(current))
            self.currentScriptIndex = current

    def documentForScript(self, script=0):
        if type(script) != Script:
            script = self.libraryList[script]

        if script not in self._cachedDocuments:
            doc = QTextDocument(self)
            doc.setDocumentLayout(QPlainTextDocumentLayout(doc))
            doc.setPlainText(script.script)
            doc.setDefaultFont(QFont(self.defaultFont))
            doc.highlighter = PythonSyntaxHighlighter(doc)
            doc.modificationChanged[bool].connect(self.onModificationChanged)
            doc.setModified(False)
            self._cachedDocuments[script] = doc
        return self._cachedDocuments[script]

    def commitChangesToLibrary(self, *args):
        index = self.selectedScriptIndex()
        if index is not None:
            self.libraryList[index].script = self.text.toPlainText()
            self.text.document().setModified(False)
            self.libraryList.emitDataChanged(index)

    def onModificationChanged(self, modified):
        index = self.selectedScriptIndex()
        if index is not None:
            self.libraryList[index].flags = Script.Modified if modified else 0
            self.libraryList.emitDataChanged(index)

    def onSpliterMoved(self, pos, ind):
        self.splitterState = bytes(self.splitCanvas.saveState())

    def updateSelecetdScriptState(self):
        index = self.selectedScriptIndex()
        if index is not None:
            script = self.libraryList[index]
            self.libraryList[index] = Script(script.name,
                                             self.text.toPlainText(), 0)

    def saveScript(self):
        index = self.selectedScriptIndex()
        if index is not None:
            script = self.libraryList[index]
            filename = script.filename
        else:
            filename = os.path.expanduser("~/")

        filename, _ = QFileDialog.getSaveFileName(
            self, 'Save Python Script', filename,
            'Python files (*.py)\nAll files(*.*)')

        if filename:
            fn = ""
            head, tail = os.path.splitext(filename)
            if not tail:
                fn = head + ".py"
            else:
                fn = filename

            f = open(fn, 'w')
            f.write(self.text.toPlainText())
            f.close()

    def initial_locals_state(self):
        d = dict([(i.name, getattr(self, i.name, None)) for i in self.inputs])
        d.update(dict([(o.name, None) for o in self.outputs]))
        return d

    def commit(self):
        self.Error.clear()
        self._script = str(self.text.toPlainText())
        lcls = self.initial_locals_state()
        lcls["_script"] = str(self.text.toPlainText())
        self.console.updateLocals(lcls)
        self.console.write("\nRunning script:\n")
        self.console.push("exec(_script)")
        self.console.new_prompt(sys.ps1)
        for out in self.outputs:
            signal = out.name
            out_var = self.console.locals.get(signal, None)
            if not isinstance(out_var, out.type) and out_var is not None:
                self.Error.add_message(
                    signal,
                    "Variable '{}' has to be an instance of '{}'.".format(
                        signal, out.type.__name__))
                getattr(self.Error, signal)()
                out_var = None
            self.send(signal, out_var)
Exemplo n.º 24
0
class OWPythonScript(OWWidget):
    name = "Python Script"
    description = "Write a Python script and run it on input data or models."
    category = "Transform"
    icon = "icons/PythonScript.svg"
    priority = 3150
    keywords = ["program", "function"]

    class Inputs:
        data = MultiInput("Data", Table, replaces=["in_data"], default=True)
        learner = MultiInput("Learner",
                             Learner,
                             replaces=["in_learner"],
                             default=True)
        classifier = MultiInput("Classifier",
                                Model,
                                replaces=["in_classifier"],
                                default=True)
        object = MultiInput("Object",
                            object,
                            replaces=["in_object"],
                            default=False)

    class Outputs:
        data = Output("Data", Table, replaces=["out_data"])
        learner = Output("Learner", Learner, replaces=["out_learner"])
        classifier = Output("Classifier", Model, replaces=["out_classifier"])
        object = Output("Object", object, replaces=["out_object"])

    signal_names = ("data", "learner", "classifier", "object")

    settings_version = 2
    scriptLibrary: 'List[_ScriptData]' = Setting([{
        "name": "Table from numpy",
        "script": DEFAULT_SCRIPT,
        "filename": None
    }])
    currentScriptIndex = Setting(0)
    scriptText: Optional[str] = Setting(None, schema_only=True)
    splitterState: Optional[bytes] = Setting(None)

    vimModeEnabled = Setting(False)

    class Error(OWWidget.Error):
        pass

    def __init__(self):
        super().__init__()

        for name in self.signal_names:
            setattr(self, name, [])

        self.splitCanvas = QSplitter(Qt.Vertical, self.mainArea)
        self.mainArea.layout().addWidget(self.splitCanvas)

        # Styling

        self.defaultFont = defaultFont = (
            'Menlo' if sys.platform == 'darwin' else 'Courier'
            if sys.platform in ['win32', 'cygwin'] else 'DejaVu Sans Mono')
        self.defaultFontSize = defaultFontSize = 13

        self.editorBox = gui.vBox(self, box="Editor", spacing=4)
        self.splitCanvas.addWidget(self.editorBox)

        darkMode = QApplication.instance().property('darkMode')
        scheme_name = 'Dark' if darkMode else 'Light'
        syntax_highlighting_scheme = SYNTAX_HIGHLIGHTING_STYLES[scheme_name]
        self.pygments_style_class = make_pygments_style(scheme_name)

        eFont = QFont(defaultFont)
        eFont.setPointSize(defaultFontSize)

        # Fake Signature

        self.func_sig = func_sig = FunctionSignature(
            self.editorBox, syntax_highlighting_scheme, eFont)

        # Editor

        editor = PythonEditor(self)
        editor.setFont(eFont)
        editor.setup_completer_appearance((300, 180), eFont)

        # Fake return

        return_stmt = ReturnStatement(self.editorBox,
                                      syntax_highlighting_scheme, eFont)
        self.return_stmt = return_stmt

        # Match indentation

        textEditBox = QWidget(self.editorBox)
        textEditBox.setLayout(QHBoxLayout())
        char_4_width = QFontMetrics(eFont).horizontalAdvance('0000')

        @editor.viewport_margins_updated.connect
        def _(width):
            func_sig.setIndent(width)
            textEditMargin = max(0, round(char_4_width - width))
            return_stmt.setIndent(textEditMargin + width)
            textEditBox.layout().setContentsMargins(textEditMargin, 0, 0, 0)

        self.text = editor
        textEditBox.layout().addWidget(editor)
        self.editorBox.layout().addWidget(func_sig)
        self.editorBox.layout().addWidget(textEditBox)
        self.editorBox.layout().addWidget(return_stmt)

        self.editorBox.setAlignment(Qt.AlignVCenter)
        self.text.setTabStopWidth(4)

        self.text.modificationChanged[bool].connect(self.onModificationChanged)

        # Controls

        self.editor_controls = gui.vBox(self.controlArea, box='Preferences')

        self.vim_box = gui.hBox(self.editor_controls, spacing=20)
        self.vim_indicator = VimIndicator(self.vim_box)

        vim_sp = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        vim_sp.setRetainSizeWhenHidden(True)
        self.vim_indicator.setSizePolicy(vim_sp)

        def enable_vim_mode():
            editor.vimModeEnabled = self.vimModeEnabled
            self.vim_indicator.setVisible(self.vimModeEnabled)

        enable_vim_mode()

        gui.checkBox(self.vim_box,
                     self,
                     'vimModeEnabled',
                     'Vim mode',
                     tooltip="Only for the coolest.",
                     callback=enable_vim_mode)
        self.vim_box.layout().addWidget(self.vim_indicator)

        @editor.vimModeIndicationChanged.connect
        def _(color, text):
            self.vim_indicator.indicator_color = color
            self.vim_indicator.indicator_text = text
            self.vim_indicator.update()

        # Library

        self.libraryListSource = []
        self._cachedDocuments = {}

        self.libraryList = itemmodels.PyListModel(
            [],
            self,
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable)

        self.libraryList.wrap(self.libraryListSource)

        self.controlBox = gui.vBox(self.controlArea, 'Library')
        self.controlBox.layout().setSpacing(1)

        self.libraryView = QListView(
            editTriggers=QListView.DoubleClicked | QListView.EditKeyPressed,
            sizePolicy=QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred))
        self.libraryView.setItemDelegate(ScriptItemDelegate(self))
        self.libraryView.setModel(self.libraryList)

        self.libraryView.selectionModel().selectionChanged.connect(
            self.onSelectedScriptChanged)
        self.controlBox.layout().addWidget(self.libraryView)

        w = itemmodels.ModelActionsWidget()

        self.addNewScriptAction = action = QAction("+", self)
        action.setToolTip("Add a new script to the library")
        action.triggered.connect(self.onAddScript)
        w.addAction(action)

        action = QAction(unicodedata.lookup("MINUS SIGN"), self)
        action.setToolTip("Remove script from library")
        action.triggered.connect(self.onRemoveScript)
        w.addAction(action)

        action = QAction("Update", self)
        action.setToolTip("Save changes in the editor to library")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.triggered.connect(self.commitChangesToLibrary)
        w.addAction(action)

        action = QAction("More", self, toolTip="More actions")

        new_from_file = QAction("Import Script from File", self)
        save_to_file = QAction("Save Selected Script to File", self)
        restore_saved = QAction("Undo Changes to Selected Script", self)
        save_to_file.setShortcut(QKeySequence(QKeySequence.SaveAs))

        new_from_file.triggered.connect(self.onAddScriptFromFile)
        save_to_file.triggered.connect(self.saveScript)
        restore_saved.triggered.connect(self.restoreSaved)

        menu = QMenu(w)
        menu.addAction(new_from_file)
        menu.addAction(save_to_file)
        menu.addAction(restore_saved)
        action.setMenu(menu)
        button = w.addAction(action)
        button.setPopupMode(QToolButton.InstantPopup)

        w.layout().setSpacing(1)

        self.controlBox.layout().addWidget(w)

        self.execute_button = gui.button(self.buttonsArea,
                                         self,
                                         'Run',
                                         callback=self.commit)

        self.run_action = QAction("Run script",
                                  self,
                                  triggered=self.commit,
                                  shortcut=QKeySequence(Qt.ControlModifier
                                                        | Qt.Key_R))
        self.addAction(self.run_action)

        self.saveAction = action = QAction("&Save", self.text)
        action.setToolTip("Save script to file")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        action.triggered.connect(self.saveScript)

        self.consoleBox = gui.vBox(self.splitCanvas, 'Console')
        self.console = PythonConsole({}, self)
        self.consoleBox.layout().addWidget(self.console)
        self.console.document().setDefaultFont(QFont(defaultFont))
        self.consoleBox.setAlignment(Qt.AlignBottom)
        self.splitCanvas.setSizes([2, 1])
        self.controlArea.layout().addStretch(10)

        self._restoreState()
        self.settingsAboutToBePacked.connect(self._saveState)

    def sizeHint(self) -> QSize:
        return super().sizeHint().expandedTo(QSize(800, 600))

    def _restoreState(self):
        self.libraryListSource = [
            Script.fromdict(s) for s in self.scriptLibrary
        ]
        self.libraryList.wrap(self.libraryListSource)
        select_row(self.libraryView, self.currentScriptIndex)

        if self.scriptText is not None:
            current = self.text.toPlainText()
            # do not mark scripts as modified
            if self.scriptText != current:
                self.text.document().setPlainText(self.scriptText)

        if self.splitterState is not None:
            self.splitCanvas.restoreState(QByteArray(self.splitterState))

    def _saveState(self):
        self.scriptLibrary = [s.asdict() for s in self.libraryListSource]
        self.scriptText = self.text.toPlainText()
        self.splitterState = bytes(self.splitCanvas.saveState())

    def set_input(self, index, obj, signal):
        dic = getattr(self, signal)
        dic[index] = obj

    def insert_input(self, index, obj, signal):
        dic = getattr(self, signal)
        dic.insert(index, obj)

    def remove_input(self, index, signal):
        dic = getattr(self, signal)
        dic.pop(index)

    @Inputs.data
    def set_data(self, index, data):
        self.set_input(index, data, "data")

    @Inputs.data.insert
    def insert_data(self, index, data):
        self.insert_input(index, data, "data")

    @Inputs.data.remove
    def remove_data(self, index):
        self.remove_input(index, "data")

    @Inputs.learner
    def set_learner(self, index, learner):
        self.set_input(index, learner, "learner")

    @Inputs.learner.insert
    def insert_learner(self, index, learner):
        self.insert_input(index, learner, "learner")

    @Inputs.learner.remove
    def remove_learner(self, index):
        self.remove_input(index, "learner")

    @Inputs.classifier
    def set_classifier(self, index, classifier):
        self.set_input(index, classifier, "classifier")

    @Inputs.classifier.insert
    def insert_classifier(self, index, classifier):
        self.insert_input(index, classifier, "classifier")

    @Inputs.classifier.remove
    def remove_classifier(self, index):
        self.remove_input(index, "classifier")

    @Inputs.object
    def set_object(self, index, object):
        self.set_input(index, object, "object")

    @Inputs.object.insert
    def insert_object(self, index, object):
        self.insert_input(index, object, "object")

    @Inputs.object.remove
    def remove_object(self, index):
        self.remove_input(index, "object")

    def handleNewSignals(self):
        # update fake signature labels
        self.func_sig.update_signal_text(
            {n: len(getattr(self, n))
             for n in self.signal_names})

        self.commit()

    def selectedScriptIndex(self):
        rows = self.libraryView.selectionModel().selectedRows()
        if rows:
            return [i.row() for i in rows][0]
        else:
            return None

    def setSelectedScript(self, index):
        select_row(self.libraryView, index)

    def onAddScript(self, *_):
        self.libraryList.append(
            Script("New script", self.text.toPlainText(), 0))
        self.setSelectedScript(len(self.libraryList) - 1)

    def onAddScriptFromFile(self, *_):
        filename, _ = QFileDialog.getOpenFileName(
            self, 'Open Python Script', os.path.expanduser("~/"),
            'Python files (*.py)\nAll files(*.*)')
        if filename:
            name = os.path.basename(filename)
            with tokenize.open(filename) as f:
                contents = f.read()
            self.libraryList.append(Script(name, contents, 0, filename))
            self.setSelectedScript(len(self.libraryList) - 1)

    def onRemoveScript(self, *_):
        index = self.selectedScriptIndex()
        if index is not None:
            del self.libraryList[index]
            select_row(self.libraryView, max(index - 1, 0))

    def onSaveScriptToFile(self, *_):
        index = self.selectedScriptIndex()
        if index is not None:
            self.saveScript()

    def onSelectedScriptChanged(self, selected, _deselected):
        index = [i.row() for i in selected.indexes()]
        if index:
            current = index[0]
            if current >= len(self.libraryList):
                self.addNewScriptAction.trigger()
                return

            self.text.setDocument(self.documentForScript(current))
            self.currentScriptIndex = current

    def documentForScript(self, script=0):
        if not isinstance(script, Script):
            script = self.libraryList[script]
        if script not in self._cachedDocuments:
            doc = QTextDocument(self)
            doc.setDocumentLayout(QPlainTextDocumentLayout(doc))
            doc.setPlainText(script.script)
            doc.setDefaultFont(QFont(self.defaultFont))
            doc.highlighter = PygmentsHighlighter(doc)
            doc.highlighter.set_style(self.pygments_style_class)
            doc.setDefaultFont(
                QFont(self.defaultFont, pointSize=self.defaultFontSize))
            doc.modificationChanged[bool].connect(self.onModificationChanged)
            doc.setModified(False)
            self._cachedDocuments[script] = doc
        return self._cachedDocuments[script]

    def commitChangesToLibrary(self, *_):
        index = self.selectedScriptIndex()
        if index is not None:
            self.libraryList[index].script = self.text.toPlainText()
            self.text.document().setModified(False)
            self.libraryList.emitDataChanged(index)

    def onModificationChanged(self, modified):
        index = self.selectedScriptIndex()
        if index is not None:
            self.libraryList[index].flags = Script.Modified if modified else 0
            self.libraryList.emitDataChanged(index)

    def restoreSaved(self):
        index = self.selectedScriptIndex()
        if index is not None:
            self.text.document().setPlainText(self.libraryList[index].script)
            self.text.document().setModified(False)

    def saveScript(self):
        index = self.selectedScriptIndex()
        if index is not None:
            script = self.libraryList[index]
            filename = script.filename
        else:
            filename = os.path.expanduser("~/")

        filename, _ = QFileDialog.getSaveFileName(
            self, 'Save Python Script', filename,
            'Python files (*.py)\nAll files(*.*)')

        if filename:
            fn = ""
            head, tail = os.path.splitext(filename)
            if not tail:
                fn = head + ".py"
            else:
                fn = filename

            f = open(fn, 'w')
            f.write(self.text.toPlainText())
            f.close()

    def initial_locals_state(self):
        d = {}
        for name in self.signal_names:
            value = getattr(self, name)
            all_values = list(value)
            one_value = all_values[0] if len(all_values) == 1 else None
            d["in_" + name + "s"] = all_values
            d["in_" + name] = one_value
        return d

    def commit(self):
        self.Error.clear()
        lcls = self.initial_locals_state()
        lcls["_script"] = str(self.text.toPlainText())
        self.console.updateLocals(lcls)
        self.console.write("\nRunning script:\n")
        self.console.push("exec(_script)")
        self.console.new_prompt(sys.ps1)
        for signal in self.signal_names:
            out_var = self.console.locals.get("out_" + signal)
            signal_type = getattr(self.Outputs, signal).type
            if not isinstance(out_var, signal_type) and out_var is not None:
                self.Error.add_message(
                    signal, "'{}' has to be an instance of '{}'.".format(
                        signal, signal_type.__name__))
                getattr(self.Error, signal)()
                out_var = None
            getattr(self.Outputs, signal).send(out_var)

    def keyPressEvent(self, e):
        if e.matches(QKeySequence.InsertLineSeparator):
            # run on Shift+Enter, Ctrl+Enter
            self.run_action.trigger()
            e.accept()
        else:
            super().keyPressEvent(e)

    def dragEnterEvent(self, event):  # pylint: disable=no-self-use
        urls = event.mimeData().urls()
        if urls:
            # try reading the file as text
            c = read_file_content(urls[0].toLocalFile(), limit=1000)
            if c is not None:
                event.acceptProposedAction()

    @classmethod
    def migrate_settings(cls, settings, version):
        if version is not None and version < 2:
            scripts = settings.pop("libraryListSource")  # type: List[Script]
            library = [
                dict(name=s.name, script=s.script, filename=s.filename)
                for s in scripts
            ]  # type: List[_ScriptData]
            settings["scriptLibrary"] = library

    def onDeleteWidget(self):
        self.text.terminate()
        super().onDeleteWidget()
Exemplo n.º 25
0
class DiscreteVariableEditor(VariableEditor):
    """An editor widget for editing a discrete variable.

    Extends the :class:`VariableEditor` to enable editing of
    variables values.

    """
    def setup_gui(self):
        layout = QVBoxLayout()
        self.setLayout(layout)

        self.main_form = QFormLayout()
        self.main_form.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
        layout.addLayout(self.main_form)

        self._setup_gui_name()
        self._setup_gui_values()
        self._setup_gui_labels()

    def _setup_gui_values(self):
        vlayout = QVBoxLayout()
        vlayout.setContentsMargins(0, 0, 0, 0)
        vlayout.setSpacing(1)

        self.values_edit = QListView()
        self.values_edit.setEditTriggers(QTreeView.CurrentChanged)
        self.values_model = itemmodels.PyListModel(flags=Qt.ItemIsSelectable | \
                                        Qt.ItemIsEnabled | Qt.ItemIsEditable)
        self.values_edit.setModel(self.values_model)

        self.values_edit.selectionModel().selectionChanged.connect(
            self.on_value_selection_changed)

        self.values_model.dataChanged.connect(self.on_values_changed)

        vlayout.addWidget(self.values_edit)
        hlayout = QHBoxLayout()
        hlayout.setContentsMargins(0, 0, 0, 0)
        hlayout.setSpacing(1)
        self.move_value_up = QAction(unicodedata.lookup("UPWARDS ARROW"),
                                     self,
                                     toolTip="Move up.",
                                     triggered=self.move_up,
                                     enabled=False,
                                     shortcut=QKeySequence(QKeySequence.New))

        self.move_value_down = QAction(unicodedata.lookup("DOWNWARDS ARROW"),
                                       self,
                                       toolTip="Move down.",
                                       triggered=self.move_down,
                                       enabled=False,
                                       shortcut=QKeySequence(
                                           QKeySequence.Delete))

        button_size = gui.toolButtonSizeHint()
        button_size = QSize(button_size, button_size)

        button = QToolButton(self)
        button.setFixedSize(button_size)
        button.setDefaultAction(self.move_value_up)
        hlayout.addWidget(button)

        button = QToolButton(self)
        button.setFixedSize(button_size)
        button.setDefaultAction(self.move_value_down)
        hlayout.addWidget(button)
        hlayout.addStretch(10)
        vlayout.addLayout(hlayout)

        self.main_form.addRow("Values:", vlayout)

    def set_data(self, var):
        """Set the variable to edit
        """
        VariableEditor.set_data(self, var)
        self.values_model[:] = list(var.values) if var is not None else []

    def get_data(self):
        """Retrieve the modified variable
        """
        name = str(self.name_edit.text()).strip()
        labels = self.labels_model.get_dict()
        values = map(str, self.values_model)

        if self.var is not None and not self.is_same():
            var = type(self.var)(name, values=values)
            var.attributes.update(labels)
            self.var = var
        else:
            var = self.var

        return var

    def is_same(self):
        """Is the current model state the same as the input.
        """
        values = list(map(str, self.values_model))
        return (VariableEditor.is_same(self) and self.var is not None
                and self.var.values == values)

    def clear(self):
        """Clear the model state.
        """
        VariableEditor.clear(self)
        self.values_model.clear()

    def move_rows(self, rows, offset):
        i = rows[0].row()
        self.values_model[i], self.values_model[i+offset] = \
            self.values_model[i+offset], self.values_model[i]
        self.maybe_commit()

    def move_up(self):
        rows = self.values_edit.selectionModel().selectedRows()
        self.move_rows(rows, -1)

    def move_down(self):
        rows = self.values_edit.selectionModel().selectedRows()
        self.move_rows(rows, 1)

    @Slot()
    def on_values_changed(self):
        self.maybe_commit()

    @Slot()
    def on_value_selection_changed(self):
        rows = self.values_edit.selectionModel().selectedRows()
        if rows:
            i = rows[0].row()
            self.move_value_up.setEnabled(i)
            self.move_value_down.setEnabled(i != len(self.var.values) - 1)
        else:
            self.move_value_up.setEnabled(False)
            self.move_value_down.setEnabled(False)
Exemplo n.º 26
0
class OWWordList(OWWidget):
    name = "Word List"
    description = "Create a list of words."
    icon = "icons/WordList.svg"
    priority = 1000

    class Inputs:
        words = Input("Words", Table)

    class Outputs:
        selected_words = Output("Selected Words", Table)
        words = Output("Words", Table)

    class Warning(OWWidget.Warning):
        no_string_vars = Msg("Input needs at least one Text variable.")

    NONE, CACHED, LIBRARY = range(3)  # library list modification types

    want_main_area = False
    resizing_enabled = True

    settingsHandler = DomainContextHandler()
    word_list_library: List[Dict] = Setting([
        {
            "name": WordList.generate_word_list_name([]),
            "words": []
        },
    ])
    word_list_index: int = Setting(0)
    words_var: Optional[StringVariable] = ContextSetting(None)
    update_rule_index: int = Setting(UpdateRules.INTERSECT)
    words: List[str] = Setting(None, schema_only=True)
    selected_words: Set[str] = Setting(set(), schema_only=True)

    def __init__(self):
        super().__init__(self)
        flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
        self.library_model = PyListModel([], self, flags=flags)
        self.words_model = PyListModel([], self, flags=flags, enable_dnd=True)

        self.library_view: QListView = None
        self.words_view: ListView = None

        self.__input_words_model = DomainModel(valid_types=(StringVariable, ))
        self.__input_words: Optional[Table] = None

        self.__library_box: QGroupBox = gui.vBox(None, "Library")
        self.__input_box: QGroupBox = gui.vBox(None, "Input")
        self.__words_box: QGroupBox = gui.vBox(None, box=True)
        self.__update_rule_rb: QRadioButton = None

        self.__add_word_action: QAction = None
        self.__remove_word_action: QAction = None

        self._setup_gui()
        self._restore_state()
        self.settingsAboutToBePacked.connect(self._save_state)

    def _setup_gui(self):
        layout = QGridLayout()
        gui.widgetBox(self.controlArea, orientation=layout)

        self._setup_library_box()
        self._setup_input_box()
        self._setup_words_box()

        layout.addWidget(self.__library_box, 0, 0)
        layout.addWidget(self.__input_box, 1, 0)
        layout.addWidget(self.__words_box, 0, 1, 0, 1)

    def _setup_library_box(self):
        self.library_view = QListView(
            editTriggers=QListView.DoubleClicked | QListView.EditKeyPressed,
            minimumWidth=200,
            sizePolicy=QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Expanding),
        )
        self.library_view.setItemDelegate(WordListItemDelegate(self))
        self.library_view.setModel(self.library_model)
        self.library_view.selectionModel().selectionChanged.connect(
            self.__on_library_selection_changed)

        self.__library_box.layout().setSpacing(1)
        self.__library_box.layout().addWidget(self.library_view)

        actions_widget = ModelActionsWidget()
        actions_widget.layout().setSpacing(1)

        action = QAction("+", self)
        action.setToolTip("Add a new word list to the library")
        action.triggered.connect(self.__on_add_word_list)
        actions_widget.addAction(action)

        action = QAction("\N{MINUS SIGN}", self)
        action.setToolTip("Remove word list from library")
        action.triggered.connect(self.__on_remove_word_list)
        actions_widget.addAction(action)

        action = QAction("Update", self)
        action.setToolTip("Save changes in the editor to library")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.triggered.connect(self.__on_update_word_list)
        actions_widget.addAction(action)

        gui.rubber(actions_widget.layout())

        action = QAction("More", self, toolTip="More actions")

        new_from_file = QAction("Import Words from File", self)
        new_from_file.triggered.connect(self.__on_import_word_list)

        save_to_file = QAction("Save Words to File", self)
        save_to_file.setShortcut(QKeySequence(QKeySequence.SaveAs))
        save_to_file.triggered.connect(self.__on_save_word_list)

        menu = QMenu(actions_widget)
        menu.addAction(new_from_file)
        menu.addAction(save_to_file)
        action.setMenu(menu)
        button = actions_widget.addAction(action)
        button.setPopupMode(QToolButton.InstantPopup)
        self.__library_box.layout().addWidget(actions_widget)

    def __on_library_selection_changed(self, selected: QItemSelection, *_):
        index = [i.row() for i in selected.indexes()]
        if index:
            current = index[0]
            word_list: WordList = self.library_model[current]
            self.word_list_index = current
            self.selected_words = set()
            self.words_model.wrap(list(word_list.cached_words))
            self._apply_update_rule()

    def __on_add_word_list(self):
        taken = [l.name for l in self.library_model]
        name = WordList.generate_word_list_name(taken)
        word_list = WordList(name, self.words_model[:])
        self.library_model.append(word_list)
        self._set_selected_word_list(len(self.library_model) - 1)

    def __on_remove_word_list(self):
        index = self._get_selected_word_list_index()
        if index is not None:
            del self.library_model[index]
            self._set_selected_word_list(max(index - 1, 0))
            self._apply_update_rule()

    def __on_update_word_list(self):
        self._set_word_list_modified(mod_type=self.LIBRARY)

    def __on_import_word_list(self):
        filename, _ = QFileDialog.getOpenFileName(
            self, "Open Word List", os.path.expanduser("~/"),
            "Text files (*.txt)\nAll files(*.*)")
        if filename:
            name = os.path.basename(filename)
            with open(filename, encoding="utf-8") as f:
                words = [line.strip() for line in f.readlines()]
            self.library_model.append(WordList(name, words, filename=filename))
            self._set_selected_word_list(len(self.library_model) - 1)
            self._apply_update_rule()

    def __on_save_word_list(self):
        index = self._get_selected_word_list_index()
        if index is not None:
            word_list = self.library_model[index]
            filename = word_list.filename
        else:
            filename = os.path.expanduser("~/")

        filename, _ = QFileDialog.getSaveFileName(
            self, "Save Word List", filename,
            "Text files (*.txt)\nAll files(*.*)")
        if filename:
            head, tail = os.path.splitext(filename)
            if not tail:
                filename = head + ".txt"

            with open(filename, "w", encoding="utf-8") as f:
                for word in self.words_model:
                    f.write(f"{word}\n")

    def _setup_input_box(self):
        gui.comboBox(self.__input_box,
                     self,
                     "words_var",
                     label="Word variable:",
                     orientation=Qt.Vertical,
                     model=self.__input_words_model,
                     callback=self._apply_update_rule)
        gui.radioButtons(self.__input_box,
                         self,
                         "update_rule_index",
                         UpdateRules.ITEMS,
                         label="Update: ",
                         orientation=Qt.Vertical,
                         callback=self.__on_update_rule_changed)
        self.__input_box.setEnabled(False)

    def __on_update_rule_changed(self):
        self._enable_words_actions()
        self._apply_update_rule()

    def _setup_words_box(self):
        self.words_view = ListView()
        self.words_view.drop_finished.connect(self.__on_words_data_changed)
        self.words_view.setModel(self.words_model)
        self.words_view.selectionModel().selectionChanged.connect(
            self.__on_words_selection_changed)

        self.words_model.dataChanged.connect(self.__on_words_data_changed)

        self.__words_box.layout().setSpacing(1)
        self.__words_box.layout().addWidget(self.words_view)

        actions_widget = ModelActionsWidget()
        actions_widget.layout().setSpacing(1)

        action = QAction("+", self.words_view, toolTip="Add a new word")
        action.triggered.connect(self.__on_add_word)
        actions_widget.addAction(action)
        self.__add_word_action = action

        action = QAction("\N{MINUS SIGN}", self, toolTip="Remove word")
        action.triggered.connect(self.__on_remove_word)
        actions_widget.addAction(action)
        self.__remove_word_action = action

        gui.rubber(actions_widget)

        action = QAction("Sort", self)
        action.setToolTip("Sort words alphabetically")
        action.triggered.connect(self.__on_apply_sorting)
        actions_widget.addAction(action)

        self.__words_box.layout().addWidget(actions_widget)

    def __on_words_data_changed(self):
        self._set_word_list_modified(mod_type=self.CACHED)
        self.commit()

    def __on_words_selection_changed(self):
        self.commit()

    def __on_add_word(self):
        row = self.words_model.rowCount()
        if not self.words_model.insertRow(self.words_model.rowCount()):
            return
        with disconnected(self.words_view.selectionModel().selectionChanged,
                          self.__on_words_selection_changed):
            self._set_selected_words([0])
            index = self.words_model.index(row, 0)
            self.words_view.setCurrentIndex(index)
            self.words_model.setItemData(index, {Qt.EditRole: ""})
        self.words_view.edit(index)

    def __on_remove_word(self):
        rows = self.words_view.selectionModel().selectedRows(0)
        if not rows:
            return

        indices = sorted([row.row() for row in rows], reverse=True)
        with disconnected(self.words_view.selectionModel().selectionChanged,
                          self.__on_words_selection_changed):
            for index in indices:
                self.words_model.removeRow(index)
            if self.words_model:
                self._set_selected_words([max(0, indices[-1] - 1)])
        self.__on_words_data_changed()

    def __on_apply_sorting(self):
        if not self.words_model:
            return
        words = self.words_model[:]
        mask = np.zeros(len(words), dtype=bool)
        selection = self._get_selected_words_indices()
        if selection:
            mask[selection] = True

        indices = np.argsort(words)
        self.words_model.wrap([words[i] for i in indices])
        self._set_word_list_modified(mod_type=self.CACHED)
        if selection:
            self._set_selected_words(list(np.flatnonzero(mask[indices])))
        else:
            self.commit()

    @Inputs.words
    def set_words(self, words: Optional[Table]):
        self.closeContext()
        self.__input_words = words
        self._check_input_words()
        self._init_controls()
        self.openContext(self.__input_words)
        self._apply_update_rule()

    def _check_input_words(self):
        self.Warning.no_string_vars.clear()
        if self.__input_words:
            metas = self.__input_words.domain.metas
            if not any(isinstance(m, StringVariable) for m in metas):
                self.Warning.no_string_vars()
                self.__input_words = None

    def _init_controls(self):
        words = self.__input_words
        domain = words.domain if words is not None else None
        self.__input_words_model.set_domain(domain)
        if len(self.__input_words_model) > 0:
            self.words_var = self.__input_words_model[0]
        self.__input_box.setEnabled(bool(self.__input_words_model))
        self._enable_words_actions()

    def _enable_words_actions(self):
        if bool(self.__input_words_model) \
                and self.update_rule_index != UpdateRules.LIBRARY:
            self.words_view.setEditTriggers(QListView.NoEditTriggers)
            self.__add_word_action.setEnabled(False)
            self.__remove_word_action.setEnabled(False)
        else:
            self.words_view.setEditTriggers(QListView.DoubleClicked
                                            | QListView.EditKeyPressed)
            self.__add_word_action.setEnabled(True)
            self.__remove_word_action.setEnabled(True)

    def _apply_update_rule(self):
        lib_index = self._get_selected_word_list_index()
        lib_words, in_words, update_rule = [], [], UpdateRules.LIBRARY
        if lib_index is not None:
            lib_words = self.library_model[lib_index].cached_words
        else:
            lib_words = self.words_model[:]
        if self.__input_words is not None:
            in_words = self.__input_words.get_column_view(self.words_var)[0]
            in_words = list(in_words)
            update_rule = self.update_rule_index

        UpdateRules.update(self.words_model, lib_words, in_words, update_rule)
        if lib_index is not None:
            cached = self.library_model[lib_index].cached_words
            modified = WordList.NotModified if cached == self.words_model[:] \
                else WordList.Modified
            self.library_model[lib_index].update_rule_flag = modified
            self._set_word_list_modified(mod_type=self.NONE)
            self.library_view.repaint()

        # Apply selection. selection_changed invokes commit().
        # If there is no selection, call commit explicitly.
        if any(w in self.selected_words for w in self.words_model):
            self.set_selected_words()
            self.words_view.repaint()
        else:
            self.commit()

    def commit(self):
        selection = self._get_selected_words_indices()
        self.selected_words = set(np.array(self.words_model)[selection])

        words, selected_words = None, None
        if self.words_model:
            words_var = StringVariable("Words")
            words_var.attributes = {"type": "words"}
            domain = Domain([], metas=[words_var])
            _words = Table.from_list(domain, [[w] for w in self.words_model])
            _words.name = "Words"
            if selection:
                selected_words = _words[selection]
            words = create_annotated_table(_words, selection)
        self.Outputs.words.send(words)
        self.Outputs.selected_words.send(selected_words)

    def _set_word_list_modified(self, mod_type):
        index = self._get_selected_word_list_index()
        if index is not None:
            if mod_type == self.LIBRARY:
                self.library_model[index].words = self.words_model[:]
                self.library_model[index].cached_words = self.words_model[:]
                self.library_model[index].update_rule_flag \
                    = WordList.NotModified
            elif mod_type == self.CACHED:
                self.library_model[index].cached_words = self.words_model[:]
            elif mod_type == self.NONE:
                pass
            else:
                raise NotImplementedError
            self.library_model.emitDataChanged(index)
            self.library_view.repaint()

    def _set_selected_word_list(self, index: int):
        sel_model: QItemSelectionModel = self.library_view.selectionModel()
        sel_model.select(self.library_model.index(index, 0),
                         QItemSelectionModel.ClearAndSelect)

    def _get_selected_word_list_index(self) -> Optional[int]:
        rows = self.library_view.selectionModel().selectedRows()
        return rows[0].row() if rows else None

    def _set_selected_words(self, indices: List[int]):
        selection = QItemSelection()
        sel_model: QItemSelectionModel = self.words_view.selectionModel()
        for i in indices:
            selection.append(QItemSelectionRange(self.words_model.index(i, 0)))
        sel_model.select(selection, QItemSelectionModel.ClearAndSelect)

    def _get_selected_words_indices(self) -> List[int]:
        rows = self.words_view.selectionModel().selectedRows()
        return [row.row() for row in rows]

    def set_selected_words(self):
        if self.selected_words:
            indices = [
                i for i, w in enumerate(self.words_model)
                if w in self.selected_words
            ]
            self._set_selected_words(indices)

    def _restore_state(self):
        source = [WordList.from_dict(s) for s in self.word_list_library]
        self.library_model.wrap(source)
        # __on_library_selection_changed() (invoked by _set_selected_word_list)
        # clears self.selected_words
        selected_words = self.selected_words
        self._set_selected_word_list(self.word_list_index)

        if self.words is not None:
            self.words_model.wrap(list(self.words))
            self._set_word_list_modified(mod_type=self.CACHED)
            if selected_words:
                self.selected_words = selected_words
                self.set_selected_words()
            elif len(self.word_list_library) > self.word_list_index and \
                self.word_list_library[self.word_list_index] != self.words:
                self.commit()

    def _save_state(self):
        self.word_list_library = [s.as_dict() for s in self.library_model]
        self.words = self.words_model[:]

    def send_report(self):
        library = self.library_model[self.word_list_index].name \
            if self.library_model else "/"
        settings = [("Library", library)]
        if self.__input_words:
            self.report_data("Input Words", self.__input_words)
            settings.append(("Word variable", self.words_var))
            rule = UpdateRules.ITEMS[self.update_rule_index]
            settings.append(("Update", rule))
        self.report_items("Settings", settings)
        self.report_paragraph("Words", ", ".join(self.words_model[:]))
Exemplo n.º 27
0
class OWSpiralogram(widget.OWWidget):
    name = 'Spiralogram'
    description = "Visualize time series' periodicity in a spiral heatmap."
    icon = 'icons/Spiralogram.svg'
    priority = 120

    class Inputs:
        time_series = Input("Time series", Table)

    class Outputs:
        time_series = Output("Time series", Timeseries)

    settingsHandler = settings.DomainContextHandler()

    ax1 = settings.ContextSetting('months of year')
    ax2 = settings.ContextSetting('years')

    agg_attr = settings.ContextSetting([])
    agg_func = settings.ContextSetting(0)

    invert_date_order = settings.Setting(False)

    graph_name = 'chart'

    def __init__(self):
        self.data = None
        self.indices = []
        box = gui.vBox(self.controlArea, 'Axes')
        self.combo_ax2_model = VariableListModel(parent=self)
        self.combo_ax1_model = VariableListModel(parent=self)
        for model in (self.combo_ax1_model, self.combo_ax2_model):
            model[:] = [_enum_str(i) for i in Spiralogram.AxesCategories]
        self.combo_ax2 = gui.comboBox(
            box, self, 'ax2', label='Y axis:', callback=self.replot,
            sendSelectedValue=True, orientation='horizontal',
            model=self.combo_ax2_model)
        self.combo_ax1 = gui.comboBox(
            box, self, 'ax1', label='Radial:', callback=self.replot,
            sendSelectedValue=True, orientation='horizontal',
            model=self.combo_ax1_model)
        gui.checkBox(box, self, 'invert_date_order', 'Invert Y axis order',
                     callback=self.replot)

        box = gui.vBox(self.controlArea, 'Aggregation')
        self.combo_func = gui.comboBox(
            box, self, 'agg_func', label='Function:', orientation='horizontal',
            callback=self.replot)
        func_model = ListModel(AGG_FUNCTIONS, parent=self)
        self.combo_func.setModel(func_model)

        self.attrlist_model = VariableListModel(parent=self)
        self.attrlist = QListView(selectionMode=QListView.SingleSelection)
        self.attrlist.setModel(self.attrlist_model)
        self.attrlist.selectionModel().selectionChanged.connect(
            self.attrlist_selectionChanged)
        box.layout().addWidget(self.attrlist)

        gui.rubber(self.controlArea)

        self.chart = chart = Spiralogram(self,
                                         selection_callback=self.on_selection)
        self.mainArea.layout().addWidget(chart)

    def attrlist_selectionChanged(self):
        self.agg_attr = [self.attrlist_model[i.row()]
                         for i in self.attrlist.selectionModel().selectedIndexes()]
        self.replot()

    @Inputs.time_series
    def set_data(self, data):
        self.data = data = None if data is None else Timeseries.from_data_table(data)

        def init_combos():
            for model in (self.combo_ax1_model, self.combo_ax2_model):
                model.clear()
            newmodel = []
            if data is not None and data.time_variable is not None:
                for model in (self.combo_ax1_model, self.combo_ax2_model):
                    model[:] = [_enum_str(i) for i in Spiralogram.AxesCategories]
            for var in data.domain.variables if data is not None else []:
                if (var.is_primitive() and
                        (var is not data.time_variable or
                         isinstance(var, TimeVariable) and data.time_delta is None)):
                    newmodel.append(var)
                if var.is_discrete:
                    for model in (self.combo_ax1_model, self.combo_ax2_model):
                        model.append(var)
            self.attrlist_model.wrap(newmodel)

        init_combos()
        self.chart.clear()

        if data is None:
            self.commit()
            return

        self.closeContext()
        self.ax2 = next((self.combo_ax2.itemText(i)
                         for i in range(self.combo_ax2.count())), '')
        self.ax1 = next((self.combo_ax1.itemText(i)
                         for i in range(1, self.combo_ax1.count())), self.ax2)
        self.agg_attr = [data.domain.variables[0]] if len(data.domain.variables) else []
        self.agg_func = 0
        if getattr(data, 'time_variable', None) is not None:
            self.openContext(data.domain)

        if self.agg_attr:
            self.attrlist.blockSignals(True)
            self.attrlist.selectionModel().clear()
            for attr in self.agg_attr:
                try:
                    row = self.attrlist_model.indexOf(attr)
                except ValueError:
                    continue
                self.attrlist.selectionModel().select(
                    self.attrlist_model.index(row),
                    QItemSelectionModel.SelectCurrent)
            self.attrlist.blockSignals(False)

        self.replot()

    def replot(self):
        if not self.combo_ax1.count() or not self.agg_attr:
            return self.chart.clear()

        vars = self.agg_attr
        func = AGG_FUNCTIONS[self.agg_func]
        if any(var.is_discrete for var in vars) and func != Mode:
            self.combo_func.setCurrentIndex(AGG_FUNCTIONS.index(Mode))
            func = Mode
        try:
            ax1 = Spiralogram.AxesCategories[_enum_str(self.ax1, True)]
        except KeyError:
            ax1 = self.data.domain[self.ax1]
        # TODO: Allow having only a sinle (i.e. radial) axis
        try:
            ax2 = Spiralogram.AxesCategories[_enum_str(self.ax2, True)]
        except KeyError:
            ax2 = self.data.domain[self.ax2]
        self.chart.setSeries(self.data, vars, ax1, ax2, func)

    def on_selection(self, indices):
        self.indices = self.chart.selection_indices(indices)
        self.commit()

    def commit(self):
        self.Outputs.time_series.send(self.data[self.indices] if self.data else None)
Exemplo n.º 28
0
class SortedListWidget(QWidget):
    sortingOrderChanged = Signal()

    class _MyItemDelegate(QStyledItemDelegate):

        def __init__(self, sortingModel, parent):
            QStyledItemDelegate.__init__(self, parent)
            self.sortingModel = sortingModel

        def sizeHint(self, option, index):
            size = QStyledItemDelegate.sizeHint(self, option, index)
            return QSize(size.width(), size.height() + 4)

        def createEditor(self, parent, option, index):
            cb = QComboBox(parent)
            cb.setModel(self.sortingModel)
            cb.showPopup()
            return cb

        def setEditorData(self, editor, index):
            pass  # TODO: sensible default

        def setModelData(self, editor, model, index):
            text = editor.currentText()
            model.setData(index, text)

    def __init__(self, *args):
        QWidget.__init__(self, *args)
        self.setContentsMargins(0, 0, 0, 0)
        gridLayout = QGridLayout()
        gridLayout.setContentsMargins(0, 0, 0, 0)
        gridLayout.setSpacing(1)

        model = QStandardItemModel(self)
        model.rowsInserted.connect(self.__changed)
        model.rowsRemoved.connect(self.__changed)
        model.dataChanged.connect(self.__changed)

        self._listView = QListView(self)
        self._listView.setModel(model)
#        self._listView.setDragEnabled(True)
        self._listView.setDropIndicatorShown(True)
        self._listView.setDragDropMode(QListView.InternalMove)
        self._listView.viewport().setAcceptDrops(True)
        self._listView.setMinimumHeight(100)

        gridLayout.addWidget(self._listView, 0, 0, 2, 2)

        vButtonLayout = QVBoxLayout()

        self._upAction = QAction(
            "\u2191", self, toolTip="Move up")

        self._upButton = QToolButton(self)
        self._upButton.setDefaultAction(self._upAction)
        self._upButton.setSizePolicy(
            QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)

        self._downAction = QAction(
            "\u2193", self, toolTip="Move down")

        self._downButton = QToolButton(self)
        self._downButton.setDefaultAction(self._downAction)
        self._downButton.setSizePolicy(
            QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)

        vButtonLayout.addWidget(self._upButton)
        vButtonLayout.addWidget(self._downButton)

        gridLayout.addLayout(vButtonLayout, 0, 2, 2, 1)

        hButtonLayout = QHBoxLayout()

        self._addAction = QAction("+", self)
        self._addButton = QToolButton(self)
        self._addButton.setDefaultAction(self._addAction)

        self._removeAction = QAction("-", self)
        self._removeButton = QToolButton(self)
        self._removeButton.setDefaultAction(self._removeAction)
        hButtonLayout.addWidget(self._addButton)
        hButtonLayout.addWidget(self._removeButton)
        hButtonLayout.addStretch(10)
        gridLayout.addLayout(hButtonLayout, 2, 0, 1, 2)

        self.setLayout(gridLayout)
        self._addAction.triggered.connect(self._onAddAction)
        self._removeAction.triggered.connect(self._onRemoveAction)
        self._upAction.triggered.connect(self._onUpAction)
        self._downAction.triggered.connect(self._onDownAction)

    def sizeHint(self):
        size = QWidget.sizeHint(self)
        return QSize(size.width(), 100)

    def _onAddAction(self):
        item = QStandardItem("")
        item.setFlags(item.flags() ^ Qt.ItemIsDropEnabled)
        self._listView.model().appendRow(item)
        self._listView.setCurrentIndex(item.index())
        self._listView.edit(item.index())

    def _onRemoveAction(self):
        current = self._listView.currentIndex()
        self._listView.model().takeRow(current.row())

    def _onUpAction(self):
        row = self._listView.currentIndex().row()
        model = self._listView.model()
        if row > 0:
            items = model.takeRow(row)
            model.insertRow(row - 1, items)
            self._listView.setCurrentIndex(model.index(row - 1, 0))

    def _onDownAction(self):
        row = self._listView.currentIndex().row()
        model = self._listView.model()
        if row < model.rowCount() and row >= 0:
            items = model.takeRow(row)
            if row == model.rowCount():
                model.appendRow(items)
            else:
                model.insertRow(row + 1, items)
            self._listView.setCurrentIndex(model.index(row + 1, 0))

    def setModel(self, model):
        """ Set a model to select items from
        """
        self._model = model
        self._listView.setItemDelegate(self._MyItemDelegate(self._model, self))

    def addItem(self, *args):
        """ Add a new entry in the list
        """
        item = QStandardItem(*args)
        item.setFlags(item.flags() ^ Qt.ItemIsDropEnabled)
        self._listView.model().appendRow(item)

    def setItems(self, items):
        self._listView.model().clear()
        for item in items:
            self.addItem(item)

    def items(self):
        order = []
        for row in range(self._listView.model().rowCount()):
            order.append(str(self._listView.model().item(row, 0).text()))
        return order

    def __changed(self):
        self.sortingOrderChanged.emit()

    sortingOrder = property(items, setItems)
Exemplo n.º 29
0
    def __setupUi(self):
        """Set up the UI.
        """
        if self.__macUnified:
            self.tab = QToolBar(
                floatable=False,
                movable=False,
                allowedAreas=Qt.TopToolBarArea,
            )
            self.addToolBar(Qt.TopToolBarArea, self.tab)
            self.setUnifiedTitleAndToolBarOnMac(True)

            # This does not seem to work
            self.setWindowFlags(self.windowFlags() & \
                                ~Qt.MacWindowToolBarButtonHint)

            self.tab.actionTriggered[QAction].connect(
                self.__macOnToolBarAction)

            central = QStackedWidget()

            central.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        else:
            self.tab = central = QTabWidget(self)

        # Add a close button to the bottom of the dialog
        # (to satisfy GNOME 3 which shows the dialog  without a title bar).
        container = container_widget_helper()
        container.layout().addWidget(central)
        buttonbox = QDialogButtonBox(QDialogButtonBox.Close)
        buttonbox.rejected.connect(self.close)
        container.layout().addWidget(buttonbox)

        self.setCentralWidget(container)

        self.stack = central

        # General Tab
        tab = QWidget()
        self.addTab(tab,
                    self.tr("General"),
                    toolTip=self.tr("General Options"))

        form = FormLayout()
        tab.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)

        nodes = QWidget(self, objectName="nodes")
        nodes.setLayout(QVBoxLayout())
        nodes.layout().setContentsMargins(0, 0, 0, 0)

        cb_anim = QCheckBox(self.tr("Enable node animations"),
                            objectName="enable-node-animations",
                            toolTip=self.tr(
                                "Enable shadow and ping animations for nodes "
                                "in the workflow."))
        self.bind(cb_anim, "checked", "schemeedit/enable-node-animations")
        nodes.layout().addWidget(cb_anim)

        form.addRow(self.tr("Nodes"), nodes)

        links = QWidget(self, objectName="links")
        links.setLayout(QVBoxLayout())
        links.layout().setContentsMargins(0, 0, 0, 0)

        cb_show = QCheckBox(self.tr("Show channel names between widgets"),
                            objectName="show-channel-names",
                            toolTip=self.tr(
                                "Show source and sink channel names "
                                "over the links."))

        self.bind(cb_show, "checked", "schemeedit/show-channel-names")

        links.layout().addWidget(cb_show)

        form.addRow(self.tr("Links"), links)

        quickmenu = QWidget(self, objectName="quickmenu-options")
        quickmenu.setLayout(QVBoxLayout())
        quickmenu.layout().setContentsMargins(0, 0, 0, 0)

        cb1 = QCheckBox(self.tr("Open on double click"),
                        toolTip=self.tr("Open quick menu on a double click "
                                        "on an empty spot in the canvas"))

        cb2 = QCheckBox(self.tr("Open on right click"),
                        toolTip=self.tr("Open quick menu on a right click "
                                        "on an empty spot in the canvas"))

        cb3 = QCheckBox(self.tr("Open on space key press"),
                        toolTip=self.tr(
                            "Open quick menu on Space key press "
                            "while the mouse is hovering over the canvas."))

        cb4 = QCheckBox(self.tr("Open on any key press"),
                        toolTip=self.tr(
                            "Open quick menu on any key press "
                            "while the mouse is hovering over the canvas."))

        cb5 = QCheckBox(self.tr("Show categories"),
                        toolTip=self.tr(
                            "In addition to searching, allow filtering "
                            "by categories."))

        self.bind(cb1, "checked", "quickmenu/trigger-on-double-click")
        self.bind(cb2, "checked", "quickmenu/trigger-on-right-click")
        self.bind(cb3, "checked", "quickmenu/trigger-on-space-key")
        self.bind(cb4, "checked", "quickmenu/trigger-on-any-key")
        self.bind(cb5, "checked", "quickmenu/show-categories")

        quickmenu.layout().addWidget(cb1)
        quickmenu.layout().addWidget(cb2)
        quickmenu.layout().addWidget(cb3)
        quickmenu.layout().addWidget(cb4)
        quickmenu.layout().addWidget(cb5)

        form.addRow(self.tr("Quick menu"), quickmenu)

        startup = QWidget(self, objectName="startup-group")
        startup.setLayout(QVBoxLayout())
        startup.layout().setContentsMargins(0, 0, 0, 0)

        cb_splash = QCheckBox(self.tr("Show splash screen"),
                              self,
                              objectName="show-splash-screen")

        cb_welcome = QCheckBox(self.tr("Show welcome screen"),
                               self,
                               objectName="show-welcome-screen")

        cb_crash = QCheckBox(self.tr("Load crashed scratch workflows"),
                             self,
                             objectName="load-crashed-workflows")

        self.bind(cb_splash, "checked", "startup/show-splash-screen")
        self.bind(cb_welcome, "checked", "startup/show-welcome-screen")
        self.bind(cb_crash, "checked", "startup/load-crashed-workflows")

        startup.layout().addWidget(cb_splash)
        startup.layout().addWidget(cb_welcome)
        startup.layout().addWidget(cb_crash)

        form.addRow(self.tr("On startup"), startup)

        toolbox = QWidget(self, objectName="toolbox-group")
        toolbox.setLayout(QVBoxLayout())
        toolbox.layout().setContentsMargins(0, 0, 0, 0)

        exclusive = QCheckBox(self.tr("Only one tab can be open at a time"))

        self.bind(exclusive, "checked", "mainwindow/toolbox-dock-exclusive")

        toolbox.layout().addWidget(exclusive)

        form.addRow(self.tr("Tool box"), toolbox)
        tab.setLayout(form)

        # Style tab
        tab = StyleConfigWidget()
        self.addTab(tab, self.tr("&Style"), toolTip="Application style")
        self.bind(tab, "selectedStyle_", "application-style/style-name")
        self.bind(tab, "selectedPalette_", "application-style/palette")

        # Output Tab
        tab = QWidget()
        self.addTab(tab, self.tr("Output"), toolTip="Output Redirection")

        form = FormLayout()

        combo = QComboBox()
        combo.addItems([
            self.tr("Critical"),
            self.tr("Error"),
            self.tr("Warn"),
            self.tr("Info"),
            self.tr("Debug")
        ])
        self.bind(combo, "currentIndex", "logging/level")
        form.addRow(self.tr("Logging"), combo)

        box = QWidget()
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        cb1 = QCheckBox(self.tr("Open in external browser"),
                        objectName="open-in-external-browser")
        self.bind(cb1, "checked", "help/open-in-external-browser")
        layout.addWidget(cb1)
        box.setLayout(layout)
        form.addRow(self.tr("Help window"), box)

        tab.setLayout(form)

        # Categories Tab
        tab = QWidget()
        layout = QVBoxLayout()
        view = QListView(editTriggers=QListView.NoEditTriggers)
        from .. import registry
        reg = registry.global_registry()
        model = QStandardItemModel()
        settings = QSettings()
        for cat in reg.categories():
            item = QStandardItem()
            item.setText(cat.name)
            item.setCheckable(True)
            visible, _ = category_state(cat, settings)
            item.setCheckState(Qt.Checked if visible else Qt.Unchecked)
            model.appendRow([item])

        view.setModel(model)
        layout.addWidget(view)
        tab.setLayout(layout)
        model.itemChanged.connect(lambda item: save_category_state(
            reg.category(str(item.text())),
            _State(item.checkState() == Qt.Checked, -1), settings))

        self.addTab(tab, "Categories")

        # Add-ons Tab
        tab = QWidget()
        self.addTab(tab,
                    self.tr("Add-ons"),
                    toolTip="Settings related to add-on installation")

        form = FormLayout()
        conda = QWidget(self, objectName="conda-group")
        conda.setLayout(QVBoxLayout())
        conda.layout().setContentsMargins(0, 0, 0, 0)

        cb_conda_install = QCheckBox(self.tr("Install add-ons with conda"),
                                     self,
                                     objectName="allow-conda")
        self.bind(cb_conda_install, "checked", "add-ons/allow-conda")
        conda.layout().addWidget(cb_conda_install)

        form.addRow(self.tr("Conda"), conda)

        form.addRow(self.tr("Pip"), QLabel("Pip install arguments:"))
        line_edit_pip = QLineEdit()
        self.bind(line_edit_pip, "text", "add-ons/pip-install-arguments")
        form.addRow("", line_edit_pip)

        tab.setLayout(form)

        # Network Tab
        tab = QWidget()
        self.addTab(tab,
                    self.tr("Network"),
                    toolTip="Settings related to networking")

        form = FormLayout()
        line_edit_http_proxy = QLineEdit()
        self.bind(line_edit_http_proxy, "text", "network/http-proxy")
        form.addRow("HTTP proxy:", line_edit_http_proxy)
        line_edit_https_proxy = QLineEdit()
        self.bind(line_edit_https_proxy, "text", "network/https-proxy")
        form.addRow("HTTPS proxy:", line_edit_https_proxy)
        tab.setLayout(form)

        if self.__macUnified:
            # Need some sensible size otherwise mac unified toolbar 'takes'
            # the space that should be used for layout of the contents
            self.adjustSize()
Exemplo n.º 30
0
class DiscreteVariableEditor(VariableEditor):
    """An editor widget for editing a discrete variable.

    Extends the :class:`VariableEditor` to enable editing of
    variables values.
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        form = self.layout().itemAt(0)
        assert isinstance(form, QFormLayout)

        #: A list model of discrete variable's values.
        self.values_model = itemmodels.PyListModel(
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable)

        vlayout = QVBoxLayout(spacing=1, margin=0)
        self.values_edit = QListView(editTriggers=QListView.DoubleClicked
                                     | QListView.EditKeyPressed)
        self.values_edit.setItemDelegate(CategoriesEditDelegate(self))
        self.values_edit.setModel(self.values_model)
        self.values_model.dataChanged.connect(self.on_values_changed)

        self.values_edit.selectionModel().selectionChanged.connect(
            self.on_value_selection_changed)
        self.values_model.layoutChanged.connect(
            self.on_value_selection_changed)
        self.values_model.rowsMoved.connect(self.on_value_selection_changed)

        vlayout.addWidget(self.values_edit)
        hlayout = QHBoxLayout(spacing=1, margin=0)

        self.categories_action_group = group = QActionGroup(
            self, objectName="action-group-categories", enabled=False)
        self.move_value_up = QAction(
            "\N{UPWARDS ARROW}",
            group,
            toolTip="Move the selected item up.",
            shortcut=QKeySequence(Qt.ControlModifier | Qt.AltModifier
                                  | Qt.Key_BracketLeft),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.move_value_up.triggered.connect(self.move_up)

        self.move_value_down = QAction(
            "\N{DOWNWARDS ARROW}",
            group,
            toolTip="Move the selected item down.",
            shortcut=QKeySequence(Qt.ControlModifier | Qt.AltModifier
                                  | Qt.Key_BracketRight),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.move_value_down.triggered.connect(self.move_down)

        self.add_new_item = QAction(
            "+",
            group,
            objectName="action-add-item",
            toolTip="Append a new item.",
            shortcut=QKeySequence(QKeySequence.New),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.remove_item = QAction(
            "\N{MINUS SIGN}",
            group,
            objectName="action-remove-item",
            toolTip="Delete the selected item.",
            shortcut=QKeySequence(QKeySequence.Delete),
            shortcutContext=Qt.WidgetShortcut,
        )

        self.add_new_item.triggered.connect(self._add_category)
        self.remove_item.triggered.connect(self._remove_category)

        button1 = FixedSizeButton(self,
                                  defaultAction=self.move_value_up,
                                  accessibleName="Move up")
        button2 = FixedSizeButton(self,
                                  defaultAction=self.move_value_down,
                                  accessibleName="Move down")
        button3 = FixedSizeButton(self,
                                  defaultAction=self.add_new_item,
                                  accessibleName="Add")
        button4 = FixedSizeButton(self,
                                  defaultAction=self.remove_item,
                                  accessibleName="Remove")
        self.values_edit.addActions([
            self.move_value_up, self.move_value_down, self.add_new_item,
            self.remove_item
        ])
        hlayout.addWidget(button1)
        hlayout.addWidget(button2)
        hlayout.addSpacing(3)
        hlayout.addWidget(button3)
        hlayout.addWidget(button4)

        hlayout.addStretch(10)
        vlayout.addLayout(hlayout)

        form.insertRow(1, "Values:", vlayout)

        QWidget.setTabOrder(self.name_edit, self.values_edit)
        QWidget.setTabOrder(self.values_edit, button1)
        QWidget.setTabOrder(button1, button2)
        QWidget.setTabOrder(button2, button3)
        QWidget.setTabOrder(button3, button4)

    def set_data(self, var, transform=()):
        # type: (Optional[Categorical], Sequence[Transform]) -> None
        """
        Set the variable to edit.
        """
        super().set_data(var, transform)
        tr = None  # type: Optional[CategoriesMapping]
        for tr_ in transform:
            if isinstance(tr_, CategoriesMapping):
                tr = tr_

        items = []
        if tr is not None:
            ci_index = {c: i for i, c in enumerate(var.categories)}
            for ci, cj in tr.mapping:
                if ci is None and cj is not None:
                    # level added
                    item = {
                        Qt.EditRole: cj,
                        EditStateRole: ItemEditState.Added,
                        SourcePosRole: None
                    }
                elif ci is not None and cj is None:
                    # ci level dropped
                    item = {
                        Qt.EditRole: ci,
                        EditStateRole: ItemEditState.Dropped,
                        SourcePosRole: ci_index[ci],
                        SourceNameRole: ci
                    }
                elif ci is not None and cj is not None:
                    # rename or reorder
                    item = {
                        Qt.EditRole: cj,
                        EditStateRole: ItemEditState.NoState,
                        SourcePosRole: ci_index[ci],
                        SourceNameRole: ci
                    }
                else:
                    assert False, "invalid mapping: {!r}".format(tr.mapping)
                items.append(item)
        elif var is not None:
            items = [{
                Qt.EditRole: c,
                EditStateRole: ItemEditState.NoState,
                SourcePosRole: i,
                SourceNameRole: c
            } for i, c in enumerate(var.categories)]
        else:
            items = []

        with disconnected(self.values_model.dataChanged,
                          self.on_values_changed):
            self.values_model.clear()
            self.values_model.insertRows(0, len(items))
            for i, item in enumerate(items):
                self.values_model.setItemData(self.values_model.index(i, 0),
                                              item)
        self.add_new_item.actionGroup().setEnabled(var is not None)

    def __categories_mapping(self):
        # type: () -> CategoriesMappingType
        model = self.values_model
        source = self.var.categories

        res = []
        for i in range(model.rowCount()):
            midx = model.index(i, 0)
            category = midx.data(Qt.EditRole)
            source_pos = midx.data(SourcePosRole)  # type: Optional[int]
            if source_pos is not None:
                source_name = source[source_pos]
            else:
                source_name = None
            state = midx.data(EditStateRole)
            if state == ItemEditState.Dropped:
                res.append((source_name, None))
            elif state == ItemEditState.Added:
                res.append((None, category))
            else:
                res.append((source_name, category))
        return res

    def get_data(self):
        """Retrieve the modified variable
        """
        var, tr = super().get_data()
        if var is None:
            return var, tr
        mapping = self.__categories_mapping()
        if any(_1 != _2 or _2 != _3
               for (_1, _2), _3 in zip_longest(mapping, var.categories)):
            tr.append(CategoriesMapping(mapping))
        return var, tr

    def clear(self):
        """Clear the model state.
        """
        super().clear()
        self.values_model.clear()

    def move_rows(self, rows, offset):
        if not rows:
            return
        assert len(rows) == 1
        i = rows[0].row()
        if offset > 0:
            offset += 1
        self.values_model.moveRows(QModelIndex(), i, 1, QModelIndex(),
                                   i + offset)
        self.variable_changed.emit()

    def move_up(self):
        rows = self.values_edit.selectionModel().selectedRows()
        self.move_rows(rows, -1)

    def move_down(self):
        rows = self.values_edit.selectionModel().selectedRows()
        self.move_rows(rows, 1)

    @Slot()
    def on_values_changed(self):
        self.variable_changed.emit()

    @Slot()
    def on_value_selection_changed(self):
        rows = self.values_edit.selectionModel().selectedRows()
        if rows:
            i = rows[0].row()
            self.move_value_up.setEnabled(i)
            self.move_value_down.setEnabled(
                i != self.values_model.rowCount() - 1)
        else:
            self.move_value_up.setEnabled(False)
            self.move_value_down.setEnabled(False)

    def _remove_category(self):
        """
        Remove the current selected category.

        If the item is an existing category present in the source variable it
        is marked as removed in the view. But if it was added in the set
        transformation it is removed entirely from the model and view.
        """
        view = self.values_edit
        rows = view.selectionModel().selectedRows(0)
        if not rows:
            return
        assert len(rows) == 1
        index = rows[0]  # type: QModelIndex
        model = index.model()
        state = index.data(EditStateRole)
        pos = index.data(Qt.UserRole)
        if pos is not None and pos >= 0:
            # existing level -> only mark/toggle its dropped state
            model.setData(
                index, ItemEditState.Dropped if state != ItemEditState.Dropped
                else ItemEditState.NoState, EditStateRole)
        elif state == ItemEditState.Added:
            # new level -> remove it
            model.removeRow(index.row())
        else:
            assert False, "invalid state '{}' for {}" \
                .format(state, index.row())

    def _add_category(self):
        """
        Add a new category
        """
        view = self.values_edit
        model = view.model()

        with disconnected(model.dataChanged, self.on_values_changed,
                          Qt.UniqueConnection):
            row = model.rowCount()
            if not model.insertRow(model.rowCount()):
                return
            index = model.index(row, 0)
            model.setItemData(
                index, {
                    Qt.EditRole: "",
                    SourcePosRole: None,
                    EditStateRole: ItemEditState.Added
                })
            view.setCurrentIndex(index)
            view.edit(index)
        self.on_values_changed()
Exemplo n.º 31
0
class OWImpute(OWWidget):
    name = "Impute"
    description = "Impute missing values in the data table."
    icon = "icons/Impute.svg"
    priority = 2130

    inputs = [("Data", Orange.data.Table, "set_data"),
              ("Learner", Learner, "set_learner")]
    outputs = [("Data", Orange.data.Table)]

    DEFAULT_LEARNER = SimpleTreeLearner()
    METHODS = [AsDefault(), impute.DoNotImpute(), impute.Average(),
               impute.AsValue(), impute.Model(DEFAULT_LEARNER), impute.Random(),
               impute.DropInstances(), impute.Default()]
    DEFAULT, DO_NOT_IMPUTE, MODEL_BASED_IMPUTER, AS_INPUT = 0, 1, 4, 7

    settingsHandler = settings.DomainContextHandler()

    _default_method_index = settings.Setting(DO_NOT_IMPUTE)
    variable_methods = settings.ContextSetting({})
    autocommit = settings.Setting(False)
    default_value = settings.Setting(0.)

    want_main_area = False
    resizing_enabled = False

    def __init__(self):
        super().__init__()
        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(10, 10, 10, 10)
        self.controlArea.layout().addLayout(main_layout)

        box = QGroupBox(title=self.tr("Default Method"), flat=False)
        box_layout = QVBoxLayout(box)
        main_layout.addWidget(box)

        button_group = QButtonGroup()
        button_group.buttonClicked[int].connect(self.set_default_method)
        for i, method in enumerate(self.METHODS):
            if not method.columns_only:
                button = QRadioButton(method.name)
                button.setChecked(i == self.default_method_index)
                button_group.addButton(button, i)
                box_layout.addWidget(button)

        self.default_button_group = button_group

        box = QGroupBox(title=self.tr("Individual Attribute Settings"),
                        flat=False)
        main_layout.addWidget(box)

        horizontal_layout = QHBoxLayout(box)
        main_layout.addWidget(box)

        self.varview = QListView(
            selectionMode=QListView.ExtendedSelection
        )
        self.varview.setItemDelegate(DisplayFormatDelegate())
        self.varmodel = itemmodels.VariableListModel()
        self.varview.setModel(self.varmodel)
        self.varview.selectionModel().selectionChanged.connect(
            self._on_var_selection_changed
        )
        self.selection = self.varview.selectionModel()

        horizontal_layout.addWidget(self.varview)

        method_layout = QVBoxLayout()
        horizontal_layout.addLayout(method_layout)

        button_group = QButtonGroup()
        for i, method in enumerate(self.METHODS):
            button = QRadioButton(text=method.name)
            button_group.addButton(button, i)
            method_layout.addWidget(button)

        self.value_combo = QComboBox(
            minimumContentsLength=8,
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLength,
            activated=self._on_value_selected
            )
        self.value_combo.currentIndexChanged.connect(self._on_value_changed)
        self.value_double = QDoubleSpinBox(
            editingFinished=self._on_value_selected,
            minimum=-1000., maximum=1000., singleStep=.1, decimals=3,
            value=self.default_value
            )
        self.value_stack = value_stack = QStackedLayout()
        value_stack.addWidget(self.value_combo)
        value_stack.addWidget(self.value_double)
        method_layout.addLayout(value_stack)

        button_group.buttonClicked[int].connect(
            self.set_method_for_current_selection
        )

        method_layout.addStretch(2)

        reset_button = QPushButton(
                "Restore All to Default", checked=False, checkable=False,
                clicked=self.reset_variable_methods, default=False,
                autoDefault=False)
        method_layout.addWidget(reset_button)

        self.variable_button_group = button_group

        box = gui.auto_commit(
            self.controlArea, self, "autocommit", "Apply",
            orientation=Qt.Horizontal, checkbox_label="Apply automatically")
        box.layout().insertSpacing(0, 80)
        box.layout().insertWidget(0, self.report_button)

        self.data = None
        self.modified = False
        self.default_method = self.METHODS[self.default_method_index]
        self.update_varview()

    @property
    def default_method_index(self):
        return self._default_method_index

    @default_method_index.setter
    def default_method_index(self, index):
        if self._default_method_index != index:
            self._default_method_index = index
            self.default_button_group.button(index).setChecked(True)
            self.default_method = self.METHODS[self.default_method_index]
            self.METHODS[self.DEFAULT].method = self.default_method

            # update variable view
            for index in map(self.varmodel.index, range(len(self.varmodel))):
                self.varmodel.setData(index,
                                      self.variable_methods.get(index.row(), self.METHODS[self.DEFAULT]),
                                      Qt.UserRole)
            self._invalidate()

    def set_default_method(self, index):
        """Set the current selected default imputation method.
        """
        self.default_method_index = index

    @check_sql_input
    def set_data(self, data):
        self.closeContext()
        self.varmodel[:] = []
        self.variable_methods = {}
        self.modified = False
        self.data = data

        if data is not None:
            self.varmodel[:] = data.domain.variables
            self.openContext(data.domain)

        self.update_varview()
        self.unconditional_commit()

    def set_learner(self, learner):
        self.learner = learner or self.DEFAULT_LEARNER
        imputer = self.METHODS[self.MODEL_BASED_IMPUTER]
        imputer.learner = self.learner

        button = self.default_button_group.button(self.MODEL_BASED_IMPUTER)
        button.setText(imputer.name)

        variable_button = self.variable_button_group.button(self.MODEL_BASED_IMPUTER)
        variable_button.setText(imputer.name)

        if learner is not None:
            self.default_method_index = self.MODEL_BASED_IMPUTER

        self.commit()

    def get_method_for_column(self, column_index):
        """Returns the imputation method for column by its index.
        """
        if not isinstance(column_index, int):
            column_index = column_index.row()

        return self.variable_methods.get(column_index,
                                         self.METHODS[self.DEFAULT])

    def _invalidate(self):
        self.modified = True
        self.commit()

    def commit(self):
        data = self.data

        if self.data is not None:
            if not len(self.data):
                self.send("Data", self.data)
                self.modified = False
                return

            drop_mask = np.zeros(len(self.data), bool)

            attributes = []
            class_vars = []

            self.warning()
            with self.progressBar(len(self.varmodel)) as progress:
                for i, var in enumerate(self.varmodel):
                    method = self.variable_methods.get(i, self.default_method)

                    if not method.supports_variable(var):
                        self.warning("Default method can not handle '{}'".
                                     format(var.name))
                    elif isinstance(method, impute.DropInstances):
                        drop_mask |= method(self.data, var)
                    else:
                        var = method(self.data, var)

                    if isinstance(var, Orange.data.Variable):
                        var = [var]

                    if i < len(self.data.domain.attributes):
                        attributes.extend(var)
                    else:
                        class_vars.extend(var)

                    progress.advance()

            domain = Orange.data.Domain(attributes, class_vars,
                                        self.data.domain.metas)
            data = self.data.from_table(domain, self.data[~drop_mask])

        self.send("Data", data)
        self.modified = False

    def send_report(self):
        specific = []
        for i, var in enumerate(self.varmodel):
            method = self.variable_methods.get(i, None)
            if method is not None:
                specific.append("{} ({})".format(var.name, str(method)))

        default = self.default_method.name
        if specific:
            self.report_items((
                ("Default method", default),
                ("Specific imputers", ", ".join(specific))
            ))
        else:
            self.report_items((("Method", default),))

    def _on_var_selection_changed(self):
        indexes = self.selection.selectedIndexes()
        methods = set(self.get_method_for_column(i.row()).name for i in indexes)

        selected_vars = [self.varmodel[index.row()] for index in indexes]
        has_discrete = any(var.is_discrete for var in selected_vars)

        if len(methods) == 1:
            method = methods.pop()
            for i, m in enumerate(self.METHODS):
                if method == m.name:
                    self.variable_button_group.button(i).setChecked(True)
        elif self.variable_button_group.checkedButton() is not None:
            self.variable_button_group.setExclusive(False)
            self.variable_button_group.checkedButton().setChecked(False)
            self.variable_button_group.setExclusive(True)

        for method, button in zip(self.METHODS,
                                  self.variable_button_group.buttons()):
            enabled = all(method.supports_variable(var) for var in
                          selected_vars)
            button.setEnabled(enabled)

        if not has_discrete:
            self.value_stack.setEnabled(True)
            self.value_stack.setCurrentWidget(self.value_double)
            self._on_value_changed()
        elif len(selected_vars) == 1:
            self.value_stack.setEnabled(True)
            self.value_stack.setCurrentWidget(self.value_combo)
            self.value_combo.clear()
            self.value_combo.addItems(selected_vars[0].values)
            self._on_value_changed()
        else:
            self.variable_button_group.button(self.AS_INPUT).setEnabled(False)
            self.value_stack.setEnabled(False)

    def set_method_for_current_selection(self, method_index):
        indexes = self.selection.selectedIndexes()
        self.set_method_for_indexes(indexes, method_index)

    def set_method_for_indexes(self, indexes, method_index):
        if method_index == self.DEFAULT:
            for index in indexes:
                self.variable_methods.pop(index, None)
        else:
            method = self.METHODS[method_index].copy()
            for index in indexes:
                self.variable_methods[index.row()] = method

        self.update_varview(indexes)
        self._invalidate()

    def update_varview(self, indexes=None):
        if indexes is None:
            indexes = map(self.varmodel.index, range(len(self.varmodel)))

        for index in indexes:
            self.varmodel.setData(index, self.get_method_for_column(index.row()), Qt.UserRole)

    def _on_value_selected(self):
        self.variable_button_group.button(self.AS_INPUT).setChecked(True)
        self._on_value_changed()

    def _on_value_changed(self):
        widget = self.value_stack.currentWidget()
        if widget is self.value_combo:
            value = self.value_combo.currentText()
        else:
            value = self.value_double.value()
            self.default_value = value

        self.METHODS[self.AS_INPUT].default = value
        index = self.variable_button_group.checkedId()
        if index == self.AS_INPUT:
            self.set_method_for_current_selection(index)

    def reset_variable_methods(self):
        indexes = map(self.varmodel.index, range(len(self.varmodel)))
        self.set_method_for_indexes(indexes, self.DEFAULT)
        self.variable_button_group.button(self.DEFAULT).setChecked(True)
class OWEditDomain(widget.OWWidget):
    name = "Edit Domain"
    description = "Rename features and their values."
    icon = "icons/EditDomain.svg"
    priority = 3125

    class Inputs:
        data = Input("Data", Orange.data.Table)

    class Outputs:
        data = Output("Data", Orange.data.Table)

    settingsHandler = settings.DomainContextHandler()

    domain_change_hints = settings.ContextSetting({})
    selected_index = settings.ContextSetting({})

    autocommit = settings.Setting(True)

    def __init__(self):
        super().__init__()

        self.data = None
        self.input_vars = ()
        self._invalidated = False

        box = gui.vBox(self.controlArea, "Domain Features")

        self.domain_model = itemmodels.VariableListModel()
        self.domain_view = QListView(selectionMode=QListView.SingleSelection,
                                     uniformItemSizes=True)
        self.domain_view.setModel(self.domain_model)
        self.domain_view.selectionModel().selectionChanged.connect(
            self._on_selection_changed)
        box.layout().addWidget(self.domain_view)

        box = gui.hBox(self.controlArea)
        gui.button(box, self, "Reset Selected", callback=self.reset_selected)
        gui.button(box, self, "Reset All", callback=self.reset_all)

        gui.auto_commit(self.controlArea, self, "autocommit", "Apply")

        box = gui.vBox(self.mainArea, "Edit")
        self.editor_stack = QStackedWidget()

        self.editor_stack.addWidget(DiscreteVariableEditor())
        self.editor_stack.addWidget(ContinuousVariableEditor())
        self.editor_stack.addWidget(VariableEditor())

        box.layout().addWidget(self.editor_stack)

        self.Error.add_message("duplicate_var_name",
                               "A variable name is duplicated.")

    @Inputs.data
    @check_sql_input
    def set_data(self, data):
        """Set input dataset."""
        self.closeContext()
        self.clear()
        self.data = data

        if self.data is not None:
            self._initialize()
            self.openContext(self.data)
            self._restore()

        self.unconditional_commit()

    def clear(self):
        """Clear the widget state."""
        self.data = None
        self.domain_model[:] = []
        self.input_vars = []
        self.domain_change_hints = {}
        self.selected_index = -1

    def reset_selected(self):
        """Reset the currently selected variable to its original state."""
        ind = self.selected_var_index()
        if ind >= 0:
            var = self.input_vars[ind]
            desc = variable_description(var, skip_attributes=True)
            if desc in self.domain_change_hints:
                del self.domain_change_hints[desc]

            self.domain_model[ind] = var
            self.editor_stack.currentWidget().set_data(var)
            self._invalidate()

    def reset_all(self):
        """Reset all variables to their original state."""
        self.domain_change_hints = {}
        if self.data is not None:
            # To invalidate stored hints
            self.domain_model[:] = self.input_vars
            itemmodels.select_row(self.domain_view, self.selected_index)
            self._invalidate()

    def selected_var_index(self):
        """Return the selected row in 'Domain Features' view."""
        rows = self.domain_view.selectedIndexes()
        assert len(rows) <= 1
        return rows[0].row() if rows else -1

    def _initialize(self):
        domain = self.data.domain
        self.input_vars = domain.variables + domain.metas
        self.domain_model[:] = list(self.input_vars)

    def _restore(self):
        # Restore the variable states from saved settings.
        def transform(var):
            vdesc = variable_description(var, skip_attributes=True)
            if vdesc in self.domain_change_hints:
                return variable_from_description(
                    self.domain_change_hints[vdesc],
                    compute_value=Orange.preprocess.transformation.Identity(
                        var),
                )
            else:
                return var

        self.domain_model[:] = map(transform, self.input_vars)

        # Restore the variable selection if possible
        index = self.selected_index
        if index >= len(self.input_vars):
            index = 0 if len(self.input_vars) else -1
        if index >= 0:
            itemmodels.select_row(self.domain_view, index)

    def _on_selection_changed(self):
        self.selected_index = self.selected_var_index()
        self.open_editor(self.selected_index)

    def open_editor(self, index):
        self.clear_editor()
        if index < 0:
            return

        var = self.domain_model[index]

        editor_index = 2
        if var.is_discrete:
            editor_index = 0
        elif var.is_continuous:
            editor_index = 1
        editor = self.editor_stack.widget(editor_index)
        self.editor_stack.setCurrentWidget(editor)

        editor.set_data(var)
        editor.variable_changed.connect(self._on_variable_changed)

    def clear_editor(self):
        current = self.editor_stack.currentWidget()
        try:
            current.variable_changed.disconnect(self._on_variable_changed)
        except Exception:
            pass
        current.set_data(None)

    def _on_variable_changed(self):
        """User edited the current variable in editor."""
        assert 0 <= self.selected_index <= len(self.domain_model)
        editor = self.editor_stack.currentWidget()

        # Replace the variable in the 'Domain Features' view/model
        old_var = self.input_vars[self.selected_index]
        new_var = editor.get_data().copy(
            compute_value=Orange.preprocess.transformation.Identity(old_var))
        self.domain_model[self.selected_index] = new_var

        # Store the transformation hint.
        old_var_desc = variable_description(old_var, skip_attributes=True)
        self.domain_change_hints[old_var_desc] = variable_description(new_var)

        self._invalidate()

    def _invalidate(self):
        self.commit()

    def commit(self):
        """Send the changed data to output."""
        new_data = None
        var_names = [vn.name for vn in self.domain_model]
        self.Error.duplicate_var_name.clear()
        if self.data is not None:
            if len(var_names) == len(set(var_names)):
                input_domain = self.data.domain
                n_attrs = len(input_domain.attributes)
                n_class_vars = len(input_domain.class_vars)
                all_new_vars = list(self.domain_model)
                attrs = all_new_vars[:n_attrs]
                class_vars = all_new_vars[n_attrs:n_attrs + n_class_vars]
                new_metas = all_new_vars[n_attrs + n_class_vars:]
                new_domain = Orange.data.Domain(attrs, class_vars, new_metas)
                new_data = self.data.transform(new_domain)
            else:
                self.Error.duplicate_var_name()

        self.Outputs.data.send(new_data)

    def sizeHint(self):
        sh = super().sizeHint()
        return sh.expandedTo(QSize(660, 550))

    def send_report(self):
        if self.data is not None:
            self.report_raw(
                "",
                EditDomainReport(
                    old_domain=chain(self.data.domain.variables,
                                     self.data.domain.metas),
                    new_domain=self.domain_model,
                ).to_html(),
            )
        else:
            self.report_data(None)
Exemplo n.º 33
0
    def __setupUi(self):
        """Set up the UI.
        """
        if self.__macUnified:
            self.tab = QToolBar(
                floatable=False, movable=False, allowedAreas=Qt.TopToolBarArea,
            )
            self.addToolBar(Qt.TopToolBarArea, self.tab)
            self.setUnifiedTitleAndToolBarOnMac(True)

            # This does not seem to work
            self.setWindowFlags(self.windowFlags() & \
                                ~Qt.MacWindowToolBarButtonHint)

            self.tab.actionTriggered[QAction].connect(
                self.__macOnToolBarAction
            )

            central = QStackedWidget()

            central.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        else:
            self.tab = central = QTabWidget(self)

        # Add a close button to the bottom of the dialog
        # (to satisfy GNOME 3 which shows the dialog  without a title bar).
        container = container_widget_helper()
        container.layout().addWidget(central)
        buttonbox = QDialogButtonBox(QDialogButtonBox.Close)
        buttonbox.rejected.connect(self.close)
        container.layout().addWidget(buttonbox)

        self.setCentralWidget(container)

        self.stack = central

        # General Tab
        tab = QWidget()
        self.addTab(tab, self.tr("General"),
                    toolTip=self.tr("General Options"))

        form = QFormLayout()
        tab.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)

        nodes = QWidget(self, objectName="nodes")
        nodes.setLayout(QVBoxLayout())
        nodes.layout().setContentsMargins(0, 0, 0, 0)

        cb_anim = QCheckBox(
            self.tr("Enable node animations"),
            objectName="enable-node-animations",
            toolTip=self.tr("Enable shadow and ping animations for nodes "
                            "in the workflow.")
        )
        self.bind(cb_anim, "checked", "schemeedit/enable-node-animations")
        nodes.layout().addWidget(cb_anim)

        form.addRow(self.tr("Nodes"), nodes)

        links = QWidget(self, objectName="links")
        links.setLayout(QVBoxLayout())
        links.layout().setContentsMargins(0, 0, 0, 0)

        cb_show = QCheckBox(
            self.tr("Show channel names between widgets"),
            objectName="show-channel-names",
            toolTip=self.tr("Show source and sink channel names "
                            "over the links.")
        )

        self.bind(cb_show, "checked", "schemeedit/show-channel-names")

        links.layout().addWidget(cb_show)

        form.addRow(self.tr("Links"), links)

        quickmenu = QWidget(self, objectName="quickmenu-options")
        quickmenu.setLayout(QVBoxLayout())
        quickmenu.layout().setContentsMargins(0, 0, 0, 0)

        cb1 = QCheckBox(self.tr("On double click"),
                        toolTip=self.tr("Open quick menu on a double click "
                                        "on an empty spot in the canvas"))

        cb2 = QCheckBox(self.tr("On right click"),
                        toolTip=self.tr("Open quick menu on a right click "
                                        "on an empty spot in the canvas"))

        cb3 = QCheckBox(self.tr("On space key press"),
                        toolTip=self.tr("On Space key press while the mouse"
                                        "is hovering over the canvas."))

        cb4 = QCheckBox(self.tr("On any key press"),
                        toolTip=self.tr("On any key press while the mouse"
                                        "is hovering over the canvas."))

        self.bind(cb1, "checked", "quickmenu/trigger-on-double-click")
        self.bind(cb2, "checked", "quickmenu/trigger-on-right-click")
        self.bind(cb3, "checked", "quickmenu/trigger-on-space-key")
        self.bind(cb4, "checked", "quickmenu/trigger-on-any-key")

        quickmenu.layout().addWidget(cb1)
        quickmenu.layout().addWidget(cb2)
        quickmenu.layout().addWidget(cb3)
        quickmenu.layout().addWidget(cb4)

        form.addRow(self.tr("Open quick menu on"), quickmenu)

        startup = QWidget(self, objectName="startup-group")
        startup.setLayout(QVBoxLayout())
        startup.layout().setContentsMargins(0, 0, 0, 0)

        cb_splash = QCheckBox(self.tr("Show splash screen"), self,
                              objectName="show-splash-screen")

        cb_welcome = QCheckBox(self.tr("Show welcome screen"), self,
                                objectName="show-welcome-screen")

        self.bind(cb_splash, "checked", "startup/show-splash-screen")
        self.bind(cb_welcome, "checked", "startup/show-welcome-screen")

        startup.layout().addWidget(cb_splash)
        startup.layout().addWidget(cb_welcome)

        form.addRow(self.tr("On startup"), startup)

        toolbox = QWidget(self, objectName="toolbox-group")
        toolbox.setLayout(QVBoxLayout())
        toolbox.layout().setContentsMargins(0, 0, 0, 0)

        exclusive = QCheckBox(self.tr("Only one tab can be open at a time"))

        self.bind(exclusive, "checked", "mainwindow/toolbox-dock-exclusive")

        toolbox.layout().addWidget(exclusive)

        form.addRow(self.tr("Tool box"), toolbox)
        tab.setLayout(form)

        # Output Tab
        tab = QWidget()
        self.addTab(tab, self.tr("Output"),
                    toolTip="Output Redirection")

        form = QFormLayout()

        box = QWidget()
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        combo = QComboBox()
        combo.addItems([self.tr("Critical"),
                        self.tr("Error"),
                        self.tr("Warn"),
                        self.tr("Info"),
                        self.tr("Debug")])
        self.bind(combo, "currentIndex", "logging/level")
        layout.addWidget(combo)
        box.setLayout(layout)
        form.addRow(self.tr("Logging"), box)

        box = QWidget()
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        cb1 = QCheckBox(self.tr("Open in external browser"),
                        objectName="open-in-external-browser")
        self.bind(cb1, "checked", "help/open-in-external-browser")
        layout.addWidget(cb1)
        box.setLayout(layout)
        form.addRow(self.tr("Help window"), box)

        tab.setLayout(form)

        # Categories Tab
        tab = QWidget()
        layout = QVBoxLayout()
        view = QListView(
            editTriggers=QListView.NoEditTriggers
        )
        from .. import registry
        reg = registry.global_registry()
        model = QStandardItemModel()
        settings = QSettings()
        for cat in reg.categories():
            item = QStandardItem()
            item.setText(cat.name)
            item.setCheckable(True)
            visible, _ = category_state(cat, settings)
            item.setCheckState(Qt.Checked if visible else Qt.Unchecked)
            model.appendRow([item])

        view.setModel(model)
        layout.addWidget(view)
        tab.setLayout(layout)
        model.itemChanged.connect(
            lambda item:
                save_category_state(
                    reg.category(str(item.text())),
                    _State(item.checkState() == Qt.Checked, -1),
                    settings
                )
        )

        self.addTab(tab, "Categories")

        # Add-ons Tab
        tab = QWidget()
        self.addTab(tab, self.tr("Add-ons"),
                    toolTip="Settings related to add-on installation")

        form = QFormLayout()
        conda = QWidget(self, objectName="conda-group")
        conda.setLayout(QVBoxLayout())
        conda.layout().setContentsMargins(0, 0, 0, 0)

        cb_conda_install = QCheckBox(self.tr("Install add-ons with conda"), self,
                                     objectName="allow-conda-experimental")
        self.bind(cb_conda_install, "checked", "add-ons/allow-conda-experimental")
        conda.layout().addWidget(cb_conda_install)

        form.addRow(self.tr("Conda"), conda)

        form.addRow(self.tr("Pip"), QLabel("Pip install arguments:"))
        line_edit_pip = QLineEdit()
        self.bind(line_edit_pip, "text", "add-ons/pip-install-arguments")
        form.addRow("", line_edit_pip)

        tab.setLayout(form)

        # Network Tab
        tab = QWidget()
        self.addTab(tab, self.tr("Network"),
                    toolTip="Settings related to networking")

        form = QFormLayout()
        line_edit_http_proxy = QLineEdit()
        self.bind(line_edit_http_proxy, "text", "network/http-proxy")
        form.addRow("HTTP proxy:", line_edit_http_proxy)
        line_edit_https_proxy = QLineEdit()
        self.bind(line_edit_https_proxy, "text", "network/https-proxy")
        form.addRow("HTTPS proxy:", line_edit_https_proxy)
        tab.setLayout(form)

        if self.__macUnified:
            # Need some sensible size otherwise mac unified toolbar 'takes'
            # the space that should be used for layout of the contents
            self.adjustSize()
Exemplo n.º 34
0
class OWDistributions(widget.OWWidget):
    name = "Distributions"
    description = "Display value distributions of a data feature in a graph."
    icon = "icons/Distribution.svg"
    priority = 120

    class Inputs:
        data = Input("Data", Orange.data.Table, doc="Set the input data set")

    settingsHandler = settings.DomainContextHandler(
        match_values=settings.DomainContextHandler.MATCH_VALUES_ALL)
    #: Selected variable index
    variable_idx = settings.ContextSetting(-1)
    #: Selected group variable
    groupvar_idx = settings.ContextSetting(0)

    relative_freq = settings.Setting(False)
    disc_cont = settings.Setting(False)

    smoothing_index = settings.Setting(5)
    show_prob = settings.ContextSetting(0)

    graph_name = "plot"

    ASH_HIST = 50

    bins = [2, 3, 4, 5, 8, 10, 12, 15, 20, 30, 50]
    smoothing_facs = list(reversed([0.1, 0.2, 0.4, 0.6, 0.8, 1, 1.5, 2, 4, 6, 10]))

    def __init__(self):
        super().__init__()
        self.data = None

        self.distributions = None
        self.contingencies = None
        self.var = self.cvar = None
        varbox = gui.vBox(self.controlArea, "Variable")

        self.varmodel = itemmodels.VariableListModel()
        self.groupvarmodel = []

        self.varview = QListView(
            selectionMode=QListView.SingleSelection)
        self.varview.setSizePolicy(
            QSizePolicy.Minimum, QSizePolicy.Expanding)
        self.varview.setModel(self.varmodel)
        self.varview.setSelectionModel(
            itemmodels.ListSingleSelectionModel(self.varmodel))
        self.varview.selectionModel().selectionChanged.connect(
            self._on_variable_idx_changed)
        varbox.layout().addWidget(self.varview)

        box = gui.vBox(self.controlArea, "Precision")

        gui.separator(self.controlArea, 4, 4)

        box2 = gui.hBox(box)
        self.l_smoothing_l = gui.widgetLabel(box2, "Smooth")
        gui.hSlider(box2, self, "smoothing_index",
                    minValue=0, maxValue=len(self.smoothing_facs) - 1,
                    callback=self._on_set_smoothing, createLabel=False)
        self.l_smoothing_r = gui.widgetLabel(box2, "Precise")

        self.cb_disc_cont = gui.checkBox(
            gui.indentedBox(box, sep=4),
            self, "disc_cont", "Bin numeric variables",
            callback=self._on_groupvar_idx_changed,
            tooltip="Show numeric variables as categorical.")

        box = gui.vBox(self.controlArea, "Group by")
        self.icons = gui.attributeIconDict
        self.groupvarview = gui.comboBox(
            box, self, "groupvar_idx",
            callback=self._on_groupvar_idx_changed,
            valueType=str, contentsLength=12)
        box2 = gui.indentedBox(box, sep=4)
        self.cb_rel_freq = gui.checkBox(
            box2, self, "relative_freq", "Show relative frequencies",
            callback=self._on_relative_freq_changed,
            tooltip="Normalize probabilities so that probabilities "
                    "for each group-by value sum to 1.")
        gui.separator(box2)
        self.cb_prob = gui.comboBox(
            box2, self, "show_prob", label="Show probabilities:",
            orientation=Qt.Horizontal,
            callback=self._on_relative_freq_changed,
            tooltip="Show probabilities for a chosen group-by value "
                    "(at each point probabilities for all group-by values sum to 1).")

        self.plotview = pg.PlotWidget(background=None)
        self.plotview.setRenderHint(QPainter.Antialiasing)
        self.mainArea.layout().addWidget(self.plotview)
        w = QLabel()
        w.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.mainArea.layout().addWidget(w, Qt.AlignCenter)
        self.ploti = pg.PlotItem()
        self.plot = self.ploti.vb
        self.ploti.hideButtons()
        self.plotview.setCentralItem(self.ploti)

        self.plot_prob = pg.ViewBox()
        self.ploti.hideAxis('right')
        self.ploti.scene().addItem(self.plot_prob)
        self.ploti.getAxis("right").linkToView(self.plot_prob)
        self.ploti.getAxis("right").setLabel("Probability")
        self.plot_prob.setZValue(10)
        self.plot_prob.setXLink(self.ploti)
        self.update_views()
        self.ploti.vb.sigResized.connect(self.update_views)
        self.plot_prob.setRange(yRange=[0, 1])

        def disable_mouse(plot):
            plot.setMouseEnabled(False, False)
            plot.setMenuEnabled(False)

        disable_mouse(self.plot)
        disable_mouse(self.plot_prob)

        self.tooltip_items = []
        self.plot.scene().installEventFilter(
            HelpEventDelegate(self.help_event, self))

        pen = QPen(self.palette().color(QPalette.Text))
        for axis in ("left", "bottom"):
            self.ploti.getAxis(axis).setPen(pen)

        self._legend = LegendItem()
        self._legend.setParentItem(self.plot)
        self._legend.hide()
        self._legend.anchor((1, 0), (1, 0))

    def update_views(self):
        self.plot_prob.setGeometry(self.plot.sceneBoundingRect())
        self.plot_prob.linkedViewChanged(self.plot, self.plot_prob.XAxis)

    @Inputs.data
    def set_data(self, data):
        self.closeContext()
        self.clear()
        self.warning()
        self.data = data
        self.distributions = None
        self.contingencies = None
        if self.data is not None:
            if not self.data:
                self.warning("Empty input data cannot be visualized")
                return
            domain = self.data.domain
            self.varmodel[:] = list(domain.variables) + \
                               [meta for meta in domain.metas
                                if meta.is_continuous or meta.is_discrete]
            self.groupvarview.clear()
            self.groupvarmodel = \
                ["(None)"] + [var for var in domain.variables if var.is_discrete] + \
                [meta for meta in domain.metas if meta.is_discrete]
            self.groupvarview.addItem("(None)")
            for var in self.groupvarmodel[1:]:
                self.groupvarview.addItem(self.icons[var], var.name)
            if domain.has_discrete_class:
                self.groupvar_idx = \
                    self.groupvarmodel[1:].index(domain.class_var) + 1
            self.openContext(domain)
            self.variable_idx = min(max(self.variable_idx, 0),
                                    len(self.varmodel) - 1)
            self.groupvar_idx = min(max(self.groupvar_idx, 0),
                                    len(self.groupvarmodel) - 1)
            itemmodels.select_row(self.varview, self.variable_idx)
            self._setup()

    def clear(self):
        self.plot.clear()
        self.plot_prob.clear()
        self.varmodel[:] = []
        self.groupvarmodel = []
        self.variable_idx = -1
        self.groupvar_idx = 0
        self._legend.clear()
        self._legend.hide()
        self.groupvarview.clear()
        self.cb_prob.clear()

    def _setup_smoothing(self):
        if not self.disc_cont and self.var and self.var.is_continuous:
            self.cb_disc_cont.setText("Bin numeric variables")
            self.l_smoothing_l.setText("Smooth")
            self.l_smoothing_r.setText("Precise")
        else:
            self.cb_disc_cont.setText("Bin numeric variables into {} bins".
                                      format(self.bins[self.smoothing_index]))
            self.l_smoothing_l.setText(" " + str(self.bins[0]))
            self.l_smoothing_r.setText(" " + str(self.bins[-1]))

    @property
    def smoothing_factor(self):
        return self.smoothing_facs[self.smoothing_index]

    def _setup(self):
        self.plot.clear()
        self.plot_prob.clear()
        self._legend.clear()
        self._legend.hide()

        varidx = self.variable_idx
        self.var = self.cvar = None
        if varidx >= 0:
            self.var = self.varmodel[varidx]
        if self.groupvar_idx > 0:
            self.cvar = self.groupvarmodel[self.groupvar_idx]
            self.cb_prob.clear()
            self.cb_prob.addItem("(None)")
            self.cb_prob.addItems(self.cvar.values)
            self.cb_prob.addItem("(All)")
            self.show_prob = min(max(self.show_prob, 0),
                                 len(self.cvar.values) + 1)
        data = self.data
        self._setup_smoothing()
        if self.var is None:
            return
        if self.disc_cont:
            domain = Orange.data.Domain(
                [self.var, self.cvar] if self.cvar else [self.var])
            data = Orange.data.Table(domain, data)
            disc = EqualWidth(n=self.bins[self.smoothing_index])
            data = Discretize(method=disc, remove_const=False)(data)
            self.var = data.domain[0]
        self.set_left_axis_name()
        self.enable_disable_rel_freq()
        if self.cvar:
            self.contingencies = \
                contingency.get_contingency(data, self.var, self.cvar)
            self.display_contingency()
        else:
            self.distributions = \
                distribution.get_distribution(data, self.var)
            self.display_distribution()
        self.plot.autoRange()

    def help_event(self, ev):
        self.plot.mapSceneToView(ev.scenePos())
        ctooltip = []
        for vb, item in self.tooltip_items:
            mouse_over_curve = isinstance(item, pg.PlotCurveItem) \
                and item.mouseShape().contains(vb.mapSceneToView(ev.scenePos()))
            mouse_over_bar = isinstance(item, DistributionBarItem) \
                and item.boundingRect().contains(vb.mapSceneToView(ev.scenePos()))
            if mouse_over_curve or mouse_over_bar:
                ctooltip.append(item.tooltip)
        if ctooltip:
            QToolTip.showText(ev.screenPos(), "\n\n".join(ctooltip), widget=self.plotview)
            return True
        return False

    def display_distribution(self):
        dist = self.distributions
        var = self.var
        if dist is None or not len(dist):
            return
        self.plot.clear()
        self.plot_prob.clear()
        self.ploti.hideAxis('right')
        self.tooltip_items = []

        bottomaxis = self.ploti.getAxis("bottom")
        bottomaxis.setLabel(var.name)
        bottomaxis.resizeEvent()

        self.set_left_axis_name()
        if var and var.is_continuous:
            bottomaxis.setTicks(None)
            if not len(dist[0]):
                return
            edges, curve = ash_curve(dist, None, m=OWDistributions.ASH_HIST,
                                     smoothing_factor=self.smoothing_factor)
            edges = edges + (edges[1] - edges[0])/2
            edges = edges[:-1]
            item = pg.PlotCurveItem()
            pen = QPen(QBrush(Qt.white), 3)
            pen.setCosmetic(True)
            item.setData(edges, curve, antialias=True, stepMode=False,
                         fillLevel=0, brush=QBrush(Qt.gray), pen=pen)
            self.plot.addItem(item)
            item.tooltip = "Density"
            self.tooltip_items.append((self.plot, item))
        else:
            bottomaxis.setTicks([list(enumerate(var.values))])
            for i, w in enumerate(dist):
                geom = QRectF(i - 0.33, 0, 0.66, w)
                item = DistributionBarItem(geom, [1.0],
                                           [QColor(128, 128, 128)])
                self.plot.addItem(item)
                item.tooltip = "Frequency for %s: %r" % (var.values[i], w)
                self.tooltip_items.append((self.plot, item))

    def _on_relative_freq_changed(self):
        self.set_left_axis_name()
        if self.cvar and self.cvar.is_discrete:
            self.display_contingency()
        else:
            self.display_distribution()
        self.plot.autoRange()

    def display_contingency(self):
        """
        Set the contingency to display.
        """
        cont = self.contingencies
        var, cvar = self.var, self.cvar
        if cont is None or not len(cont):
            return
        self.plot.clear()
        self.plot_prob.clear()
        self._legend.clear()
        self.tooltip_items = []

        if self.show_prob:
            self.ploti.showAxis('right')
        else:
            self.ploti.hideAxis('right')

        bottomaxis = self.ploti.getAxis("bottom")
        bottomaxis.setLabel(var.name)
        bottomaxis.resizeEvent()

        cvar_values = cvar.values
        colors = [QColor(*col) for col in cvar.colors]

        if var and var.is_continuous:
            bottomaxis.setTicks(None)

            weights, cols, cvar_values, curves = [], [], [], []
            for i, dist in enumerate(cont):
                v, W = dist
                if len(v):
                    weights.append(numpy.sum(W))
                    cols.append(colors[i])
                    cvar_values.append(cvar.values[i])
                    curves.append(ash_curve(
                        dist, cont, m=OWDistributions.ASH_HIST,
                        smoothing_factor=self.smoothing_factor))
            weights = numpy.array(weights)
            sumw = numpy.sum(weights)
            weights /= sumw
            colors = cols
            curves = [(X, Y * w) for (X, Y), w in zip(curves, weights)]

            curvesline = [] #from histograms to lines
            for X, Y in curves:
                X = X + (X[1] - X[0])/2
                X = X[:-1]
                X = numpy.array(X)
                Y = numpy.array(Y)
                curvesline.append((X, Y))

            for t in ["fill", "line"]:
                curve_data = list(zip(curvesline, colors, weights, cvar_values))
                for (X, Y), color, w, cval in reversed(curve_data):
                    item = pg.PlotCurveItem()
                    pen = QPen(QBrush(color), 3)
                    pen.setCosmetic(True)
                    color = QColor(color)
                    color.setAlphaF(0.2)
                    item.setData(X, Y/(w if self.relative_freq else 1),
                                 antialias=True, stepMode=False,
                                 fillLevel=0 if t == "fill" else None,
                                 brush=QBrush(color), pen=pen)
                    self.plot.addItem(item)
                    if t == "line":
                        item.tooltip = "{}\n{}={}".format(
                            "Normalized density " if self.relative_freq else "Density ",
                            cvar.name, cval)
                        self.tooltip_items.append((self.plot, item))

            if self.show_prob:
                all_X = numpy.array(numpy.unique(numpy.hstack([X for X, _ in curvesline])))
                inter_X = numpy.array(numpy.linspace(all_X[0], all_X[-1], len(all_X)*2))
                curvesinterp = [numpy.interp(inter_X, X, Y) for (X, Y) in curvesline]
                sumprob = numpy.sum(curvesinterp, axis=0)
                legal = sumprob > 0.05 * numpy.max(sumprob)

                i = len(curvesinterp) + 1
                show_all = self.show_prob == i
                for Y, color, cval in reversed(list(zip(curvesinterp, colors, cvar_values))):
                    i -= 1
                    if show_all or self.show_prob == i:
                        item = pg.PlotCurveItem()
                        pen = QPen(QBrush(color), 3, style=Qt.DotLine)
                        pen.setCosmetic(True)
                        prob = Y[legal] / sumprob[legal]
                        item.setData(
                            inter_X[legal], prob, antialias=True, stepMode=False,
                            fillLevel=None, brush=None, pen=pen)
                        self.plot_prob.addItem(item)
                        item.tooltip = "Probability that \n" + cvar.name + "=" + cval
                        self.tooltip_items.append((self.plot_prob, item))

        elif var and var.is_discrete:
            bottomaxis.setTicks([list(enumerate(var.values))])

            cont = numpy.array(cont)

            maxh = 0 #maximal column height
            maxrh = 0 #maximal relative column height
            scvar = cont.sum(axis=1)
            #a cvar with sum=0 with allways have distribution counts 0,
            #therefore we can divide it by anything
            scvar[scvar == 0] = 1
            for i, (value, dist) in enumerate(zip(var.values, cont.T)):
                maxh = max(maxh, max(dist))
                maxrh = max(maxrh, max(dist/scvar))

            for i, (value, dist) in enumerate(zip(var.values, cont.T)):
                dsum = sum(dist)
                geom = QRectF(i - 0.333, 0, 0.666,
                              maxrh if self.relative_freq else maxh)
                if self.show_prob:
                    prob = dist / dsum
                    ci = 1.96 * numpy.sqrt(prob * (1 - prob) / dsum)
                else:
                    ci = None
                item = DistributionBarItem(geom, dist/scvar/maxrh
                                           if self.relative_freq
                                           else dist/maxh, colors)
                self.plot.addItem(item)
                tooltip = "\n".join(
                    "%s: %.*f" % (n, 3 if self.relative_freq else 1, v)
                    for n, v in zip(cvar_values, dist/scvar if self.relative_freq else dist))
                item.tooltip = "{} ({}={}):\n{}".format(
                    "Normalized frequency " if self.relative_freq else "Frequency ",
                    cvar.name, value, tooltip)
                self.tooltip_items.append((self.plot, item))

                if self.show_prob:
                    item.tooltip += "\n\nProbabilities:"
                    for ic, a in enumerate(dist):
                        if self.show_prob - 1 != ic and \
                                self.show_prob - 1 != len(dist):
                            continue
                        position = -0.333 + ((ic+0.5)*0.666/len(dist))
                        if dsum < 1e-6:
                            continue
                        prob = a / dsum
                        if not 1e-6 < prob < 1 - 1e-6:
                            continue
                        ci = 1.96 * sqrt(prob * (1 - prob) / dsum)
                        item.tooltip += "\n%s: %.3f ± %.3f" % (cvar_values[ic], prob, ci)
                        mark = pg.ScatterPlotItem()
                        errorbar = pg.ErrorBarItem()
                        pen = QPen(QBrush(QColor(0)), 1)
                        pen.setCosmetic(True)
                        errorbar.setData(x=[i+position], y=[prob],
                                         bottom=min(numpy.array([ci]), prob),
                                         top=min(numpy.array([ci]), 1 - prob),
                                         beam=numpy.array([0.05]),
                                         brush=QColor(1), pen=pen)
                        mark.setData([i+position], [prob], antialias=True, symbol="o",
                                     fillLevel=None, pxMode=True, size=10,
                                     brush=QColor(colors[ic]), pen=pen)
                        self.plot_prob.addItem(errorbar)
                        self.plot_prob.addItem(mark)

        for color, name in zip(colors, cvar_values):
            self._legend.addItem(
                ScatterPlotItem(pen=color, brush=color, size=10, shape="s"),
                escape(name)
            )
        self._legend.show()

    def set_left_axis_name(self):
        leftaxis = self.ploti.getAxis("left")
        set_label = leftaxis.setLabel
        if self.var and self.var.is_continuous:
            set_label(["Density", "Relative density"]
                      [self.cvar is not None and self.relative_freq])
        else:
            set_label(["Frequency", "Relative frequency"]
                      [self.cvar is not None and self.relative_freq])
        leftaxis.resizeEvent()

    def enable_disable_rel_freq(self):
        self.cb_prob.setDisabled(self.var is None or self.cvar is None)
        self.cb_rel_freq.setDisabled(
            self.var is None or self.cvar is None)

    def _on_variable_idx_changed(self):
        self.variable_idx = selected_index(self.varview)
        self._setup()

    def _on_groupvar_idx_changed(self):
        self._setup()

    def _on_set_smoothing(self):
        self._setup()

    def onDeleteWidget(self):
        self.plot.clear()
        super().onDeleteWidget()

    def get_widget_name_extension(self):
        if self.variable_idx >= 0:
            return self.varmodel[self.variable_idx]

    def send_report(self):
        self.plotview.scene().setSceneRect(self.plotview.sceneRect())
        if self.variable_idx < 0:
            return
        self.report_plot()
        text = "Distribution of '{}'".format(
            self.varmodel[self.variable_idx])
        if self.groupvar_idx:
            group_var = self.groupvarmodel[self.groupvar_idx]
            prob = self.cb_prob
            indiv_probs = 0 < prob.currentIndex() < prob.count() - 1
            if not indiv_probs or self.relative_freq:
                text += " grouped by '{}'".format(group_var)
                if self.relative_freq:
                    text += " (relative frequencies)"
            if indiv_probs:
                text += "; probabilites for '{}={}'".format(
                    group_var, prob.currentText())
        self.report_caption(text)
Exemplo n.º 35
0
    def showPopup(self):
        # type: () -> None
        """
        Reimplemented from QComboBox.showPopup

        Popup up a customized view and filter edit line.

        Note
        ----
        The .popup(), .lineEdit(), .completer() of the base class are not used.
        """
        if self.__popup is not None:
            # We have user entered state that cannot be disturbed
            # (entered filter text, scroll offset, ...)
            return  # pragma: no cover

        if self.count() == 0:
            return

        opt = QStyleOptionComboBox()
        self.initStyleOption(opt)
        popup = QListView(
            uniformItemSizes=True,
            horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff,
            verticalScrollBarPolicy=Qt.ScrollBarAsNeeded,
            iconSize=self.iconSize(),
        )
        popup.setFocusProxy(self.__searchline)
        popup.setParent(self, Qt.Popup | Qt.FramelessWindowHint)
        popup.setItemDelegate(_ComboBoxListDelegate(popup))
        proxy = QSortFilterProxyModel(
            popup, filterCaseSensitivity=Qt.CaseInsensitive
        )
        proxy.setFilterKeyColumn(self.modelColumn())
        proxy.setSourceModel(self.model())
        popup.setModel(proxy)
        root = proxy.mapFromSource(self.rootModelIndex())
        popup.setRootIndex(root)

        self.__popup = popup
        self.__proxy = proxy
        self.__searchline.setText("")
        self.__searchline.setPlaceholderText("Filter...")
        self.__searchline.setVisible(True)
        self.__searchline.textEdited.connect(proxy.setFilterFixedString)

        style = self.style()  # type: QStyle

        popuprect_origin = style.subControlRect(
            QStyle.CC_ComboBox, opt, QStyle.SC_ComboBoxListBoxPopup, self
        )  # type: QRect
        popuprect_origin = QRect(
            self.mapToGlobal(popuprect_origin.topLeft()),
            popuprect_origin.size()
        )
        editrect = style.subControlRect(
            QStyle.CC_ComboBox, opt, QStyle.SC_ComboBoxEditField, self
        )  # type: QRect
        self.__searchline.setGeometry(editrect)
        desktop = QApplication.desktop()
        screenrect = desktop.availableGeometry(self)  # type: QRect

        # get the height for the view
        listrect = QRect()
        for i in range(min(proxy.rowCount(root), self.maxVisibleItems())):
            index = proxy.index(i, self.modelColumn(), root)
            if index.isValid():
                listrect = listrect.united(popup.visualRect(index))
            if listrect.height() >= screenrect.height():
                break
        window = popup.window()  # type: QWidget
        window.ensurePolished()
        if window.layout() is not None:
            window.layout().activate()
        else:
            QApplication.sendEvent(window, QEvent(QEvent.LayoutRequest))

        margins = qwidget_margin_within(popup.viewport(), window)
        height = (listrect.height() + 2 * popup.spacing() +
                  margins.top() + margins.bottom())

        popup_size = (QSize(popuprect_origin.width(), height)
                      .expandedTo(window.minimumSize())
                      .boundedTo(window.maximumSize())
                      .boundedTo(screenrect.size()))
        popuprect = QRect(popuprect_origin.bottomLeft(), popup_size)

        popuprect = dropdown_popup_geometry(
            popuprect, popuprect_origin, screenrect)
        popup.setGeometry(popuprect)

        current = proxy.mapFromSource(
            self.model().index(self.currentIndex(), self.modelColumn(),
                               self.rootModelIndex()))
        popup.setCurrentIndex(current)
        popup.scrollTo(current, QAbstractItemView.EnsureVisible)
        popup.show()
        popup.setFocus(Qt.PopupFocusReason)
        popup.installEventFilter(self)
        popup.viewport().installEventFilter(self)
        popup.viewport().setMouseTracking(True)
        self.update()
        self.__popupTimer.restart()
Exemplo n.º 36
0
    def showPopup(self):
        # type: () -> None
        """
        Reimplemented from QComboBox.showPopup

        Popup up a customized view and filter edit line.

        Note
        ----
        The .popup(), .lineEdit(), .completer() of the base class are not used.
        """
        if self.__popup is not None:
            # We have user entered state that cannot be disturbed
            # (entered filter text, scroll offset, ...)
            return  # pragma: no cover

        if self.count() == 0:
            return

        opt = QStyleOptionComboBox()
        self.initStyleOption(opt)
        popup = QListView(
            uniformItemSizes=True,
            horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff,
            verticalScrollBarPolicy=Qt.ScrollBarAsNeeded,
            iconSize=self.iconSize(),
        )
        popup.setFocusProxy(self.__searchline)
        popup.setParent(self, Qt.Popup | Qt.FramelessWindowHint)
        popup.setItemDelegate(_ComboBoxListDelegate(popup))
        proxy = QSortFilterProxyModel(
            popup, filterCaseSensitivity=Qt.CaseInsensitive
        )
        proxy.setFilterKeyColumn(self.modelColumn())
        proxy.setSourceModel(self.model())
        popup.setModel(proxy)
        root = proxy.mapFromSource(self.rootModelIndex())
        popup.setRootIndex(root)

        self.__popup = popup
        self.__proxy = proxy
        self.__searchline.setText("")
        self.__searchline.setPlaceholderText("Filter...")
        self.__searchline.setVisible(True)
        self.__searchline.textEdited.connect(proxy.setFilterFixedString)

        style = self.style()  # type: QStyle

        popuprect_origin = style.subControlRect(
            QStyle.CC_ComboBox, opt, QStyle.SC_ComboBoxListBoxPopup, self
        )  # type: QRect
        popuprect_origin = QRect(
            self.mapToGlobal(popuprect_origin.topLeft()),
            popuprect_origin.size()
        )
        editrect = style.subControlRect(
            QStyle.CC_ComboBox, opt, QStyle.SC_ComboBoxEditField, self
        )  # type: QRect
        self.__searchline.setGeometry(editrect)
        desktop = QApplication.desktop()
        screenrect = desktop.availableGeometry(self)  # type: QRect

        # get the height for the view
        listrect = QRect()
        for i in range(min(proxy.rowCount(root), self.maxVisibleItems())):
            index = proxy.index(i, self.modelColumn(), root)
            if index.isValid():
                listrect = listrect.united(popup.visualRect(index))
            if listrect.height() >= screenrect.height():
                break
        window = popup.window()  # type: QWidget
        window.ensurePolished()
        if window.layout() is not None:
            window.layout().activate()
        else:
            QApplication.sendEvent(window, QEvent(QEvent.LayoutRequest))

        margins = qwidget_margin_within(popup.viewport(), window)
        height = (listrect.height() + 2 * popup.spacing() +
                  margins.top() + margins.bottom())

        popup_size = (QSize(popuprect_origin.width(), height)
                      .expandedTo(window.minimumSize())
                      .boundedTo(window.maximumSize())
                      .boundedTo(screenrect.size()))
        popuprect = QRect(popuprect_origin.bottomLeft(), popup_size)

        popuprect = dropdown_popup_geometry(
            popuprect, popuprect_origin, screenrect)
        popup.setGeometry(popuprect)

        current = proxy.mapFromSource(
            self.model().index(self.currentIndex(), self.modelColumn(),
                               self.rootModelIndex()))
        popup.setCurrentIndex(current)
        popup.scrollTo(current, QAbstractItemView.EnsureVisible)
        popup.show()
        popup.setFocus(Qt.PopupFocusReason)
        popup.installEventFilter(self)
        popup.viewport().installEventFilter(self)
        popup.viewport().setMouseTracking(True)
        self.update()
        self.__popupTimer.restart()
Exemplo n.º 37
0
class OWImpute(OWWidget):
    name = "Impute"
    description = "Impute missing values in the data table."
    icon = "icons/Impute.svg"
    priority = 2130

    class Inputs:
        data = Input("Data", Orange.data.Table)
        learner = Input("Learner", Learner)

    class Outputs:
        data = Output("Data", Orange.data.Table)

    class Error(OWWidget.Error):
        imputation_failed = Msg("Imputation failed for '{}'")
        model_based_imputer_sparse = Msg("Model based imputer does not work for sparse data")

    DEFAULT_LEARNER = SimpleTreeLearner()
    METHODS = [AsDefault(), impute.DoNotImpute(), impute.Average(),
               impute.AsValue(), impute.Model(DEFAULT_LEARNER), impute.Random(),
               impute.DropInstances(), impute.Default()]
    DEFAULT, DO_NOT_IMPUTE, MODEL_BASED_IMPUTER, AS_INPUT = 0, 1, 4, 7

    settingsHandler = settings.DomainContextHandler()

    _default_method_index = settings.Setting(DO_NOT_IMPUTE)
    variable_methods = settings.ContextSetting({})
    autocommit = settings.Setting(True)

    want_main_area = False
    resizing_enabled = False

    def __init__(self):
        super().__init__()
        # copy METHODS (some are modified by the widget)
        self.methods = copy.deepcopy(OWImpute.METHODS)

        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(10, 10, 10, 10)
        self.controlArea.layout().addLayout(main_layout)

        box = QGroupBox(title=self.tr("Default Method"), flat=False)
        box_layout = QVBoxLayout(box)
        main_layout.addWidget(box)

        button_group = QButtonGroup()
        button_group.buttonClicked[int].connect(self.set_default_method)
        for i, method in enumerate(self.methods):
            if not method.columns_only:
                button = QRadioButton(method.name)
                button.setChecked(i == self.default_method_index)
                button_group.addButton(button, i)
                box_layout.addWidget(button)

        self.default_button_group = button_group

        box = QGroupBox(title=self.tr("Individual Attribute Settings"),
                        flat=False)
        main_layout.addWidget(box)

        horizontal_layout = QHBoxLayout(box)
        main_layout.addWidget(box)

        self.varview = QListView(
            selectionMode=QListView.ExtendedSelection
        )
        self.varview.setItemDelegate(DisplayFormatDelegate())
        self.varmodel = itemmodels.VariableListModel()
        self.varview.setModel(self.varmodel)
        self.varview.selectionModel().selectionChanged.connect(
            self._on_var_selection_changed
        )
        self.selection = self.varview.selectionModel()

        horizontal_layout.addWidget(self.varview)

        method_layout = QVBoxLayout()
        horizontal_layout.addLayout(method_layout)

        button_group = QButtonGroup()
        for i, method in enumerate(self.methods):
            button = QRadioButton(text=method.name)
            button_group.addButton(button, i)
            method_layout.addWidget(button)

        self.value_combo = QComboBox(
            minimumContentsLength=8,
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLength,
            activated=self._on_value_selected
            )
        self.value_double = QDoubleSpinBox(
            editingFinished=self._on_value_selected,
            minimum=-1000., maximum=1000., singleStep=.1, decimals=3,
            )
        self.value_stack = value_stack = QStackedWidget()
        value_stack.addWidget(self.value_combo)
        value_stack.addWidget(self.value_double)
        method_layout.addWidget(value_stack)

        button_group.buttonClicked[int].connect(
            self.set_method_for_current_selection
        )

        method_layout.addStretch(2)

        reset_button = QPushButton(
                "Restore All to Default", checked=False, checkable=False,
                clicked=self.reset_variable_methods, default=False,
                autoDefault=False)
        method_layout.addWidget(reset_button)

        self.variable_button_group = button_group

        box = gui.auto_commit(
            self.controlArea, self, "autocommit", "Apply",
            orientation=Qt.Horizontal,
            checkbox_label="Apply automatically")
        box.button.setFixedWidth(180)
        box.layout().insertStretch(0)

        self.data = None
        self.learner = None
        self.modified = False
        self.default_method = self.methods[self.default_method_index]
        self.executor = qconcurrent.ThreadExecutor(self)
        self.__task = None

    @property
    def default_method_index(self):
        return self._default_method_index

    @default_method_index.setter
    def default_method_index(self, index):
        if self._default_method_index != index:
            self._default_method_index = index
            self.default_button_group.button(index).setChecked(True)
            self.default_method = self.methods[self.default_method_index]
            self.methods[self.DEFAULT].method = self.default_method

            # update variable view
            for index in map(self.varmodel.index, range(len(self.varmodel))):
                method = self.variable_methods.get(
                    index.row(), self.methods[self.DEFAULT])
                self.varmodel.setData(index, method, Qt.UserRole)
            self._invalidate()

    def set_default_method(self, index):
        """Set the current selected default imputation method.
        """
        self.default_method_index = index

    @Inputs.data
    @check_sql_input
    def set_data(self, data):
        self.closeContext()
        self.varmodel[:] = []
        self.variable_methods = {}
        self.modified = False
        self.data = data

        if data is not None:
            self.varmodel[:] = data.domain.variables
            self.openContext(data.domain)

        self.update_varview()
        self.unconditional_commit()

    @Inputs.learner
    def set_learner(self, learner):
        self.learner = learner or self.DEFAULT_LEARNER
        imputer = self.methods[self.MODEL_BASED_IMPUTER]
        imputer.learner = self.learner

        button = self.default_button_group.button(self.MODEL_BASED_IMPUTER)
        button.setText(imputer.name)

        variable_button = self.variable_button_group.button(self.MODEL_BASED_IMPUTER)
        variable_button.setText(imputer.name)

        if learner is not None:
            self.default_method_index = self.MODEL_BASED_IMPUTER

        self.update_varview()
        self.commit()

    def get_method_for_column(self, column_index):
        """Returns the imputation method for column by its index.
        """
        if not isinstance(column_index, int):
            column_index = column_index.row()

        return self.variable_methods.get(column_index,
                                         self.methods[self.DEFAULT])

    def _invalidate(self):
        self.modified = True
        if self.__task is not None:
            self.cancel()
        self.commit()

    def commit(self):
        self.cancel()
        self.warning()
        self.Error.imputation_failed.clear()
        self.Error.model_based_imputer_sparse.clear()

        if self.data is None or len(self.data) == 0 or len(self.varmodel) == 0:
            self.Outputs.data.send(self.data)
            self.modified = False
            return

        data = self.data
        impute_state = [
            (i, var, self.variable_methods.get(i, self.default_method))
            for i, var in enumerate(self.varmodel)
        ]

        def impute_one(method, var, data):
            # type: (impute.BaseImputeMethod, Variable, Table) -> Any
            if isinstance(method, impute.Model) and data.is_sparse():
                raise SparseNotSupported()
            elif isinstance(method, impute.DropInstances):
                return RowMask(method(data, var))
            elif not method.supports_variable(var):
                raise VariableNotSupported(var)
            else:
                return method(data, var)

        futures = []
        for _, var, method in impute_state:
            f = self.executor.submit(
                impute_one, copy.deepcopy(method), var, data)
            futures.append(f)

        w = qconcurrent.FutureSetWatcher(futures)
        w.doneAll.connect(self.__commit_finish)
        w.progressChanged.connect(self.__progress_changed)
        self.__task = Task(futures, w)
        self.progressBarInit(processEvents=False)
        self.setBlocking(True)

    @Slot()
    def __commit_finish(self):
        assert QThread.currentThread() is self.thread()
        assert self.__task is not None
        futures = self.__task.futures
        assert len(futures) == len(self.varmodel)
        assert self.data is not None

        self.__task = None
        self.setBlocking(False)
        self.progressBarFinished()

        data = self.data
        attributes = []
        class_vars = []
        drop_mask = np.zeros(len(self.data), bool)

        for i, (var, fut) in enumerate(zip(self.varmodel, futures)):
            assert fut.done()
            newvar = []
            try:
                res = fut.result()
            except SparseNotSupported:
                self.Error.model_based_imputer_sparse()
                # ?? break
            except VariableNotSupported:
                self.warning("Default method can not handle '{}'".
                             format(var.name))
            except Exception:  # pylint: disable=broad-except
                log = logging.getLogger(__name__)
                log.info("Error for %s", var, exc_info=True)
                self.Error.imputation_failed(var.name)
                attributes = class_vars = None
                break
            else:
                if isinstance(res, RowMask):
                    drop_mask |= res.mask
                    newvar = var
                else:
                    newvar = res

            if isinstance(newvar, Orange.data.Variable):
                newvar = [newvar]

            if i < len(data.domain.attributes):
                attributes.extend(newvar)
            else:
                class_vars.extend(newvar)

        if attributes is None:
            data = None
        else:
            domain = Orange.data.Domain(attributes, class_vars,
                                        data.domain.metas)
            try:
                data = self.data.from_table(domain, data[~drop_mask])
            except Exception:  # pylint: disable=broad-except
                log = logging.getLogger(__name__)
                log.info("Error", exc_info=True)
                self.Error.imputation_failed("Unknown")
                data = None

        self.Outputs.data.send(data)
        self.modified = False

    @Slot(int, int)
    def __progress_changed(self, n, d):
        assert QThread.currentThread() is self.thread()
        assert self.__task is not None
        self.progressBarSet(100. * n / d)

    def cancel(self):
        if self.__task is not None:
            task, self.__task = self.__task, None
            task.cancel()
            task.watcher.doneAll.disconnect(self.__commit_finish)
            task.watcher.progressChanged.disconnect(self.__progress_changed)
            concurrent.futures.wait(task.futures)
            task.watcher.flush()
            self.progressBarFinished()
            self.setBlocking(False)

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

    def send_report(self):
        specific = []
        for i, var in enumerate(self.varmodel):
            method = self.variable_methods.get(i, None)
            if method is not None:
                specific.append("{} ({})".format(var.name, str(method)))

        default = self.default_method.name
        if specific:
            self.report_items((
                ("Default method", default),
                ("Specific imputers", ", ".join(specific))
            ))
        else:
            self.report_items((("Method", default),))

    def _on_var_selection_changed(self):
        indexes = self.selection.selectedIndexes()
        methods = [self.get_method_for_column(i.row()) for i in indexes]

        def method_key(method):
            """
            Decompose method into its type and parameters.
            """
            # The return value should be hashable and  __eq__ comparable
            if isinstance(method, AsDefault):
                return AsDefault, (method.method,)
            elif isinstance(method, impute.Model):
                return impute.Model, (method.learner,)
            elif isinstance(method, impute.Default):
                return impute.Default, (method.default,)
            else:
                return type(method), None

        methods = set(method_key(m) for m in methods)
        selected_vars = [self.varmodel[index.row()] for index in indexes]
        has_discrete = any(var.is_discrete for var in selected_vars)
        fixed_value = None
        value_stack_enabled = False
        current_value_widget = None

        if len(methods) == 1:
            method_type, parameters = methods.pop()
            for i, m in enumerate(self.methods):
                if method_type == type(m):
                    self.variable_button_group.button(i).setChecked(True)

            if method_type is impute.Default:
                (fixed_value,) = parameters

        elif self.variable_button_group.checkedButton() is not None:
            # Uncheck the current button
            self.variable_button_group.setExclusive(False)
            self.variable_button_group.checkedButton().setChecked(False)
            self.variable_button_group.setExclusive(True)
            assert self.variable_button_group.checkedButton() is None

        for method, button in zip(self.methods,
                                  self.variable_button_group.buttons()):
            enabled = all(method.supports_variable(var) for var in
                          selected_vars)
            button.setEnabled(enabled)

        if not has_discrete:
            value_stack_enabled = True
            current_value_widget = self.value_double
        elif len(selected_vars) == 1:
            value_stack_enabled = True
            current_value_widget = self.value_combo
            self.value_combo.clear()
            self.value_combo.addItems(selected_vars[0].values)
        else:
            value_stack_enabled = False
            current_value_widget = None
            self.variable_button_group.button(self.AS_INPUT).setEnabled(False)

        self.value_stack.setEnabled(value_stack_enabled)
        if current_value_widget is not None:
            self.value_stack.setCurrentWidget(current_value_widget)
            if fixed_value is not None:
                if current_value_widget is self.value_combo:
                    self.value_combo.setCurrentIndex(fixed_value)
                elif current_value_widget is self.value_double:
                    self.value_double.setValue(fixed_value)
                else:
                    assert False

    def set_method_for_current_selection(self, method_index):
        indexes = self.selection.selectedIndexes()
        self.set_method_for_indexes(indexes, method_index)

    def set_method_for_indexes(self, indexes, method_index):
        if method_index == self.DEFAULT:
            for index in indexes:
                self.variable_methods.pop(index.row(), None)
        elif method_index == OWImpute.AS_INPUT:
            current = self.value_stack.currentWidget()
            if current is self.value_combo:
                value = self.value_combo.currentIndex()
            else:
                value = self.value_double.value()
            for index in indexes:
                method = impute.Default(default=value)
                self.variable_methods[index.row()] = method
        else:
            method = self.methods[method_index]
            for index in indexes:
                self.variable_methods[index.row()] = method

        self.update_varview(indexes)
        self._invalidate()

    def update_varview(self, indexes=None):
        if indexes is None:
            indexes = map(self.varmodel.index, range(len(self.varmodel)))

        for index in indexes:
            self.varmodel.setData(index, self.get_method_for_column(index.row()), Qt.UserRole)

    def _on_value_selected(self):
        # The fixed 'Value' in the widget has been changed by the user.
        self.variable_button_group.button(self.AS_INPUT).setChecked(True)
        self.set_method_for_current_selection(self.AS_INPUT)

    def reset_variable_methods(self):
        indexes = list(map(self.varmodel.index, range(len(self.varmodel))))
        self.set_method_for_indexes(indexes, self.DEFAULT)
        self.variable_button_group.button(self.DEFAULT).setChecked(True)
Exemplo n.º 38
0
class OWDiscretize(widget.OWWidget):
    name = "Discretize"
    description = "Discretize the numeric data features."
    icon = "icons/Discretize.svg"
    inputs = [InputSignal("Data", Orange.data.Table, "set_data",
                          doc="Input data table")]
    outputs = [OutputSignal("Data", Orange.data.Table,
                            doc="Table with discretized features")]

    settingsHandler = settings.DomainContextHandler()
    saved_var_states = settings.ContextSetting({})

    default_method = settings.Setting(2)
    default_k = settings.Setting(3)
    autosend = settings.Setting(True)

    #: Discretization methods
    Default, Leave, MDL, EqualFreq, EqualWidth, Remove, Custom = range(7)

    want_main_area = False
    resizing_enabled = False

    def  __init__(self):
        super().__init__()

        #: input data
        self.data = None
        #: Current variable discretization state
        self.var_state = {}
        #: Saved variable discretization settings (context setting)
        self.saved_var_states = {}

        self.method = 0
        self.k = 5

        box = gui.vBox(self.controlArea, self.tr("Default Discretization"))
        self.default_bbox = rbox = gui.radioButtons(
            box, self, "default_method", callback=self._default_disc_changed)
        rb = gui.hBox(rbox)
        self.left = gui.vBox(rb)
        right = gui.vBox(rb)
        rb.layout().setStretch(0, 1)
        rb.layout().setStretch(1, 1)
        options = self.options = [
            self.tr("Default"),
            self.tr("Leave numeric"),
            self.tr("Entropy-MDL discretization"),
            self.tr("Equal-frequency discretization"),
            self.tr("Equal-width discretization"),
            self.tr("Remove numeric variables")
        ]

        for opt in options[1:]:
            t = gui.appendRadioButton(rbox, opt)
            # This condition is ugly, but it keeps the same order of
            # options for backward compatibility of saved schemata
            [right, self.left][opt.startswith("Equal")].layout().addWidget(t)
        gui.separator(right, 18, 18)

        def _intbox(widget, attr, callback):
            box = gui.indentedBox(widget)
            s = gui.spin(
                box, self, attr, minv=2, maxv=10, label="Num. of intervals:",
                callback=callback)
            s.setMaximumWidth(60)
            s.setAlignment(Qt.AlignRight)
            gui.rubber(s.box)
            return box.box

        self.k_general = _intbox(self.left, "default_k",
                                 self._default_disc_changed)
        self.k_general.layout().setContentsMargins(0, 0, 0, 0)
        vlayout = QHBoxLayout()
        box = gui.widgetBox(
            self.controlArea, "Individual Attribute Settings",
            orientation=vlayout, spacing=8
        )

        # List view with all attributes
        self.varview = QListView(selectionMode=QListView.ExtendedSelection)
        self.varview.setItemDelegate(DiscDelegate())
        self.varmodel = itemmodels.VariableListModel()
        self.varview.setModel(self.varmodel)
        self.varview.selectionModel().selectionChanged.connect(
            self._var_selection_changed
        )

        vlayout.addWidget(self.varview)
        # Controls for individual attr settings
        self.bbox = controlbox = gui.radioButtons(
            box, self, "method", callback=self._disc_method_changed
        )
        vlayout.addWidget(controlbox)

        for opt in options[:5]:
            gui.appendRadioButton(controlbox, opt)

        self.k_specific = _intbox(controlbox, "k", self._disc_method_changed)

        gui.appendRadioButton(controlbox, "Remove attribute")

        gui.rubber(controlbox)
        controlbox.setEnabled(False)

        self.controlbox = controlbox

        box = gui.auto_commit(
            self.controlArea, self, "autosend", "Apply",
            orientation=Qt.Horizontal,
            checkbox_label="Apply automatically")
        box.layout().insertSpacing(0, 20)
        box.layout().insertWidget(0, self.report_button)
        self._update_spin_positions()


    def set_data(self, data):
        self.closeContext()
        self.data = data
        if self.data is not None:
            self._initialize(data)
            self.openContext(data)
            # Restore the per variable discretization settings
            self._restore(self.saved_var_states)
            # Complete the induction of cut points
            self._update_points()
        else:
            self._clear()
        self.unconditional_commit()

    def _initialize(self, data):
        # Initialize the default variable states for new data.
        self.class_var = data.domain.class_var
        cvars = [var for var in data.domain if var.is_continuous]
        self.varmodel[:] = cvars

        class_var = data.domain.class_var
        has_disc_class = data.domain.has_discrete_class

        self.default_bbox.buttons[self.MDL - 1].setEnabled(has_disc_class)
        self.bbox.buttons[self.MDL].setEnabled(has_disc_class)

        # If the newly disabled MDL button is checked then change it
        if not has_disc_class and self.default_method == self.MDL - 1:
            self.default_method = 0
        if not has_disc_class and self.method == self.MDL:
            self.method = 0

        # Reset (initialize) the variable discretization states.
        self._reset()

    def _restore(self, saved_state):
        # Restore variable states from a saved_state dictionary.
        def_method = self._current_default_method()
        for i, var in enumerate(self.varmodel):
            key = variable_key(var)
            if key in saved_state:
                state = saved_state[key]
                if isinstance(state.method, Default):
                    state = DState(Default(def_method), None, None)
                self._set_var_state(i, state)

    def _reset(self):
        # restore the individual variable settings back to defaults.
        def_method = self._current_default_method()
        self.var_state = {}
        for i in range(len(self.varmodel)):
            state = DState(Default(def_method), None, None)
            self._set_var_state(i, state)

    def _set_var_state(self, index, state):
        # set the state of variable at `index` to `state`.
        self.var_state[index] = state
        self.varmodel.setData(self.varmodel.index(index), state, Qt.UserRole)

    def _clear(self):
        self.data = None
        self.varmodel[:] = []
        self.var_state = {}
        self.saved_var_states = {}
        self.default_bbox.buttons[self.MDL - 1].setEnabled(True)
        self.bbox.buttons[self.MDL].setEnabled(True)

    def _update_points(self):
        """
        Update the induced cut points.
        """
        if self.data is None or not len(self.data):
            return

        def induce_cuts(method, data, var):
            dvar = _dispatch[type(method)](method, data, var)
            if dvar is None:
                # removed
                return [], None
            elif dvar is var:
                # no transformation took place
                return None, var
            elif is_discretized(dvar):
                return dvar.compute_value.points, dvar
            else:
                assert False
        for i, var in enumerate(self.varmodel):
            state = self.var_state[i]
            if state.points is None and state.disc_var is None:
                points, dvar = induce_cuts(state.method, self.data, var)
                new_state = state._replace(points=points, disc_var=dvar)
                self._set_var_state(i, new_state)

    def _method_index(self, method):
        return METHODS.index((type(method), ))

    def _current_default_method(self):
        method = self.default_method + 1
        k = self.default_k
        if method == OWDiscretize.Leave:
            def_method = Leave()
        elif method == OWDiscretize.MDL:
            def_method = MDL()
        elif method == OWDiscretize.EqualFreq:
            def_method = EqualFreq(k)
        elif method == OWDiscretize.EqualWidth:
            def_method = EqualWidth(k)
        elif method == OWDiscretize.Remove:
            def_method = Remove()
        else:
            assert False
        return def_method

    def _current_method(self):
        if self.method == OWDiscretize.Default:
            method = Default(self._current_default_method())
        elif self.method == OWDiscretize.Leave:
            method = Leave()
        elif self.method == OWDiscretize.MDL:
            method = MDL()
        elif self.method == OWDiscretize.EqualFreq:
            method = EqualFreq(self.k)
        elif self.method == OWDiscretize.EqualWidth:
            method = EqualWidth(self.k)
        elif self.method == OWDiscretize.Remove:
            method = Remove()
        elif self.method == OWDiscretize.Custom:
            method = Custom(self.cutpoints)
        else:
            assert False
        return method

    def _update_spin_positions(self):
        self.k_general.setDisabled(self.default_method not in [2, 3])
        if self.default_method == 2:
            self.left.layout().insertWidget(1, self.k_general)
        elif self.default_method == 3:
            self.left.layout().insertWidget(2, self.k_general)

        self.k_specific.setDisabled(self.method not in [3, 4])
        if self.method == 3:
            self.bbox.layout().insertWidget(4, self.k_specific)
        elif self.method == 4:
            self.bbox.layout().insertWidget(5, self.k_specific)

    def _default_disc_changed(self):
        self._update_spin_positions()
        method = self._current_default_method()
        state = DState(Default(method), None, None)
        for i, _ in enumerate(self.varmodel):
            if isinstance(self.var_state[i].method, Default):
                self._set_var_state(i, state)
        self._update_points()
        self.commit()

    def _disc_method_changed(self):
        self._update_spin_positions()
        indices = self.selected_indices()
        method = self._current_method()
        state = DState(method, None, None)
        for idx in indices:
            self._set_var_state(idx, state)
        self._update_points()
        self.commit()

    def _var_selection_changed(self, *args):
        indices = self.selected_indices()
        # set of all methods for the current selection
        methods = [self.var_state[i].method for i in indices]
        mset = set(methods)
        self.controlbox.setEnabled(len(mset) > 0)
        if len(mset) == 1:
            method = mset.pop()
            self.method = self._method_index(method)
            if isinstance(method, (EqualFreq, EqualWidth)):
                self.k = method.k
            elif isinstance(method, Custom):
                self.cutpoints = method.points
        else:
            # deselect the current button
            self.method = -1
            bg = self.controlbox.group
            button_group_reset(bg)
        self._update_spin_positions()

    def selected_indices(self):
        rows = self.varview.selectionModel().selectedRows()
        return [index.row() for index in rows]

    def discretized_var(self, source):
        index = list(self.varmodel).index(source)
        state = self.var_state[index]
        if state.disc_var is None:
            return None
        elif state.disc_var is source:
            return source
        elif state.points == []:
            return None
        else:
            return state.disc_var

    def discretized_domain(self):
        """
        Return the current effective discretized domain.
        """
        if self.data is None:
            return None

        def disc_var(source):
            if source and source.is_continuous:
                return self.discretized_var(source)
            else:
                return source

        attributes = [disc_var(v) for v in self.data.domain.attributes]
        attributes = [v for v in attributes if v is not None]

        class_var = disc_var(self.data.domain.class_var)

        domain = Orange.data.Domain(
            attributes, class_var,
            metas=self.data.domain.metas
        )
        return domain

    def commit(self):
        output = None
        if self.data is not None and len(self.data):
            domain = self.discretized_domain()
            output = self.data.from_table(domain, self.data)
        self.send("Data", output)

    def storeSpecificSettings(self):
        super().storeSpecificSettings()
        self.saved_var_states = {
            variable_key(var):
                self.var_state[i]._replace(points=None, disc_var=None)
            for i, var in enumerate(self.varmodel)
        }

    def send_report(self):
        self.report_items((
            ("Default method", self.options[self.default_method + 1]),))
        if self.varmodel:
            self.report_items("Thresholds", [
                (var.name,
                 DiscDelegate.cutsText(self.var_state[i]) or "leave numeric")
                for i, var in enumerate(self.varmodel)])
Exemplo n.º 39
0
class OWGenotypeDistances(widget.OWWidget):
    name = "Expression Profile Distances"
    description = ("Compute distances between expression profiles of "
                   "different experimental factors.")
    icon = "../widgets/icons/GenotypeDistances.svg"
    priority = 1050

    inputs = [("Data", Orange.data.Table, "set_data")]
    outputs = [("Distances", Orange.misc.DistMatrix),
               ("Sorted Data", Orange.data.Table)]

    settingsHandler = SetContextHandler()

    separate_keys = settings.ContextSetting({})
    relevant_keys = settings.ContextSetting({})

    distance_measure = settings.Setting(0)
    auto_commit = settings.Setting(False)

    DISTANCE_FUNCTIONS = [("Distance from Pearson correlation", dist_pcorr),
                          ("Euclidean distance", dist_eucl),
                          ("Distance from Spearman correlation", dist_spearman)
                          ]

    def __init__(self, parent=None):
        super().__init__(self, parent)

        self.data = None
        self.partitions = []
        self.matrix = None
        self.split_groups = []
        self._disable_updates = False

        ########
        # GUI
        ########

        box = gui.widgetBox(self.controlArea, "Input")

        self.info_box = gui.widgetLabel(box, "No data on input\n")

        box = gui.widgetBox(self.controlArea, "Separate By", addSpace=True)

        self.separate_view = QListView(selectionMode=QListView.MultiSelection)
        box.layout().addWidget(self.separate_view)

        box = gui.widgetBox(self.controlArea, "Sort By", addSpace=True)
        self.relevant_view = QListView(selectionMode=QListView.MultiSelection)

        box.layout().addWidget(self.relevant_view)

        self.distance_view = gui.comboBox(
            self.controlArea,
            self,
            "distance_measure",
            box="Distance Measure",
            items=[name for name, _ in self.DISTANCE_FUNCTIONS])

        gui.rubber(self.controlArea)

        gui.auto_commit(self.controlArea, self, "auto_commit", "Commit")
        self.groups_box = gui.widgetBox(self.mainArea, "Groups")
        self.groups_scroll_area = QScrollArea()
        self.groups_box.layout().addWidget(self.groups_scroll_area)

    def sizeHint(self):
        return QSize(800, 600)

    def clear(self):
        self.data = None
        self.partitions = []
        self.split_groups = []
        self.matrix = None

    def get_suitable_keys(self, data):
        """Return suitable attr label keys from the data where the key has at least
        two unique values in the data.

        """
        attrs = [attr.attributes.items() for attr in data.domain.attributes]
        attrs = reduce(operator.iadd, attrs, [])
        # in case someone put non string values in attributes dict
        attrs = [(str(key), str(value)) for key, value in attrs]
        attrs = set(attrs)
        values = defaultdict(set)
        for key, value in attrs:
            values[key].add(value)
        keys = [key for key in values if len(values[key]) > 1]
        return keys

    def set_data(self, data=None):
        """Set the input data table.
        """
        self.closeContext()
        self.clear()
        self.error(0)
        self.warning(0)
        if data and not self.get_suitable_keys(data):
            self.error(0, "Data has no suitable column labels.")
            data = None

        self.data = data

        if data:
            self.info_box.setText("{0} genes\n{1} experiments".format(
                len(data), len(data.domain)))
            self.update_control()
            self.split_data()
        else:
            self.separate_view.setModel(itemmodels.PyListModel([]))
            self.relevant_view.setModel(itemmodels.PyListModel([]))
            self.groups_scroll_area.setWidget(QWidget())
            self.info_box.setText("No data on input.\n")
        self.commit()

    def update_control(self):
        """Update the control area of the widget. Populate the list
        views with keys from attribute labels.
        """
        keys = self.get_suitable_keys(self.data)

        model = itemmodels.PyListModel(keys)
        self.separate_view.setModel(model)
        self.separate_view.selectionModel().selectionChanged.connect(
            self.on_separate_key_changed)

        model = itemmodels.PyListModel(keys)
        self.relevant_view.setModel(model)
        self.relevant_view.selectionModel().selectionChanged.connect(
            self.on_relevant_key_changed)

        self.openContext(keys)

        # Get the selected keys from the open context
        separate_keys = self.separate_keys
        relevant_keys = self.relevant_keys

        def select(model, selection_model, selected_items):
            all_items = list(model)
            try:
                indices = [all_items.index(item) for item in selected_items]
            except:
                indices = []
            selection = QItemSelection()
            for ind in indices:
                index = model.index(ind)
                selection.select(index, index)

            selection_model.select(selection, QItemSelectionModel.Select)

        self._disable_updates = True
        try:
            select(self.relevant_view.model(),
                   self.relevant_view.selectionModel(), relevant_keys)

            select(self.separate_view.model(),
                   self.separate_view.selectionModel(), separate_keys)
        finally:
            self._disable_updates = False

    def on_separate_key_changed(self, *args):
        if not self._disable_updates:
            self.separate_keys = self.selected_separeate_by_keys()
            self.split_data()

    def on_relevant_key_changed(self, *args):
        if not self._disable_updates:
            self.relevant_keys = self.selected_relevant_keys()
            self.split_data()

    def selected_separeate_by_keys(self):
        """Return the currently selected separate by keys
        """
        rows = self.separate_view.selectionModel().selectedRows()
        rows = sorted([idx.row() for idx in rows])
        keys = [self.separate_view.model()[row] for row in rows]
        return keys

    def selected_relevant_keys(self):
        """Return the currently selected relevant keys
        """
        rows = self.relevant_view.selectionModel().selectedRows()
        rows = sorted([idx.row() for idx in rows])
        keys = [self.relevant_view.model()[row] for row in rows]
        return keys

    def split_data(self):
        """Split the data and update the Groups widget
        """
        separate_keys = self.selected_separeate_by_keys()
        relevant_keys = self.selected_relevant_keys()

        self.warning(0)
        if not separate_keys:
            self.warning(0, "No separate by column selected.")

        partitions, uniquepos = separate_by(self.data,
                                            separate_keys,
                                            consider=relevant_keys)
        partitions = partitions.items()

        all_values = defaultdict(set)
        for a in [at.attributes for at in self.data.domain.attributes]:
            for k, v in a.items():
                all_values[k].add(v)

        # sort groups
        pkeys = [key for key, _ in partitions]
        types = [
            data_type([a[i] for a in pkeys]) for i in range(len(pkeys[0]))
        ]

        partitions = sorted(partitions,
                            key=lambda x: tuple(types[i](v)
                                                for i, v in enumerate(x[0])))

        split_groups = []

        # Collect relevant key value pairs for all columns
        relevant_items = None

        for keys, indices in partitions:
            if relevant_items == None:
                relevant_items = [defaultdict(set) for _ in indices]
            for i, ind in enumerate(indices):
                if ind is not None:
                    attr = self.data.domain[ind]
                    for key in relevant_keys:
                        relevant_items[i][key].add(attr.attributes[key])

        #those with different values between rows are not relevant
        for d in relevant_items:
            for k, s in list(d.items()):
                if len(s) > 1:
                    del d[k]
                else:
                    d[k] = s.pop()

        def get_attr(attr_index, i):
            if attr_index is None:
                attr = Orange.data.ContinuousVariable(
                    next(missing_name_gen), compute_value=lambda x: None)
                attr.attributes.update(relevant_items[i])
                return attr
            else:
                return self.data.domain[attr_index]

        for keys, indices in partitions:
            attrs = [
                get_attr(attr_index, i) for i, attr_index in enumerate(indices)
            ]
            for attr in attrs:
                attr.attributes.update(zip(separate_keys, keys))
            domain = Orange.data.Domain(attrs, [], self.data.domain.metas)
            split_groups.append((keys, domain))

        self.set_groups(separate_keys, split_groups, relevant_keys,
                        relevant_items, all_values, uniquepos)

        self.partitions = partitions
        self.split_groups = split_groups

        self.commit()

    def set_groups(self, keys, groups, relevant_keys, relevant_items,
                   all_values, uniquepos):
        """Set the current data groups and update the Group widget
        """
        layout = QVBoxLayout()
        header_widths = []
        header_views = []
        palette = self.palette()
        all_values = all_values.keys()

        def for_print(rd):
            attrs = []
            for d in rd:
                attr = Orange.data.ContinuousVariable(next(inactive_name_gen))
                attr.attributes.update(d)
                attrs.append(attr)
            return Orange.data.Domain(attrs, None)

        for separatev, domain in [(None, for_print(relevant_items))] + groups:
            label = None
            if separatev is not None:
                ann_vals = " <b>|</b> ".join(["<b>{0}</b> = {1}".format(key,val) \
                     for key, val in zip(keys, separatev)])
                label = QLabel(ann_vals)

            model = QStandardItemModel()
            for i, attr in enumerate(domain.attributes):
                item = QStandardItem()
                if separatev is not None:
                    isunique = uniquepos[separatev][i]
                else:
                    isunique = all(a[i] for a in uniquepos.values())

                if str(attr.name).startswith(
                        "!!missing "
                ):  # TODO: Change this to not depend on name
                    header_text = ["{0}={1}".format(key, attr.attributes.get(key, "?")) \
                                   for key in all_values if key not in relevant_items[i]]
                    header_text = "\n".join(
                        header_text) if header_text else "Empty"
                    item.setData(header_text, Qt.DisplayRole)
                    item.setFlags(Qt.NoItemFlags)
                    item.setData(QColor(Qt.red), Qt.ForegroundRole)
                    item.setData(
                        palette.color(QPalette.Disabled, QPalette.Window),
                        Qt.BackgroundRole)
                    item.setData("Missing feature.", Qt.ToolTipRole)
                elif str(attr.name).startswith("!!inactive "):
                    header_text = ["{0}={1}".format(key, attr.attributes.get(key, "?")) \
                                   for key in all_values if key in relevant_items[i]]
                    header_text = "\n".join(
                        header_text) if header_text else "No descriptor"
                    item.setData(header_text, Qt.DisplayRole)
                    item.setData(
                        palette.color(QPalette.Disabled, QPalette.Window),
                        Qt.BackgroundRole)
                else:
                    header_text = ["{0}={1}".format(key, attr.attributes.get(key, "?")) \
                                   for key in all_values if key not in relevant_items[i]]
                    header_text = "\n".join(
                        header_text) if header_text else "Empty"
                    item.setData(header_text, Qt.DisplayRole)
                    item.setData(attr.name, Qt.ToolTipRole)

                if not isunique:
                    item.setData(QColor(Qt.red), Qt.ForegroundRole)

                model.setHorizontalHeaderItem(i, item)
            attr_count = len(domain.attributes)
            view = MyHeaderView(Qt.Horizontal)
            view.setResizeMode(QHeaderView.Fixed)
            view.setModel(model)
            hint = view.sizeHint()
            view.setMaximumHeight(hint.height())

            widths = [view.sectionSizeHint(i) for i in range(attr_count)]
            header_widths.append(widths)
            header_views.append(view)

            if label:
                layout.addWidget(label)
            layout.addWidget(view)
            layout.addSpacing(8)

        # Make all header sections the same width
        width_sum = 0
        max_header_count = max([h.count() for h in header_views])
        for i in range(max_header_count):
            max_width = max([w[i] for w in header_widths if i < len(w)] or [0])
            for view in header_views:
                if i < view.count():
                    view.resizeSection(i, max_width)
            width_sum += max_width + 2

        for h in header_views:
            h.setMinimumWidth(h.length() + 4)

        widget = QWidget()
        widget.setLayout(layout)
        widget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum)
        layout.activate()

        max_width = max(h.length() for h in header_views) + 20

        left, _, right, _ = self.getContentsMargins()
        widget.setMinimumWidth(width_sum)
        widget.setMinimumWidth(max_width + left + right)
        self.groups_scroll_area.setWidget(widget)

    def compute_distances(self, separate_keys, partitions, data):
        """Compute the distances between genotypes.
        """
        if separate_keys and partitions:
            self.progressBarInit()
            #             matrix = Orange.misc.DistMatrix(len(partitions))
            matrix = numpy.zeros((len(partitions), len(partitions)))

            profiles = [linearize(data, indices) for _, indices in partitions]
            dist_func = self.DISTANCE_FUNCTIONS[self.distance_measure][1]
            #             from Orange.utils import progress_bar_milestones
            count = (len(profiles) * len(profiles) - 1) / 2
            #             milestones = progress_bar_milestones(count)
            iter_count = 0
            for i in range(len(profiles)):
                for j in range(i + 1, len(profiles)):
                    matrix[i, j] = dist_func(profiles[i], profiles[j])
                    matrix[j, i] = matrix[i, j]
                    iter_count += 1
                    #                     if iter_count in milestones:
                    self.progressBarSet(100.0 * iter_count / count)
            self.progressBarFinished()

            items = [[
                "{0}={1}".format(key, value)
                for key, value in zip(separate_keys, values)
            ] for values, _ in partitions]
            items = [" | ".join(item) for item in items]
            #             matrix.setattr("items", items)
            matrix = Orange.misc.DistMatrix(matrix)
        else:
            matrix = None

        self.matrix = matrix

    def commit(self):
        separate_keys = self.selected_separeate_by_keys()
        self.compute_distances(separate_keys, self.partitions, self.data)
        if self.split_groups:
            all_attrs = []
            for group, domain in self.split_groups:
                attrs = []
                group_name = " | ".join("{0}={1}".format(*item)
                                        for item in zip(separate_keys, group))
                for attr in domain.attributes:
                    newattr = clone_attr(attr)
                    newattr.attributes[
                        "<GENOTYPE GROUP>"] = group_name  # Need a better way to pass the groups to downstream widgets.
                    attrs.append(newattr)

                all_attrs.extend(attrs)

            domain = Orange.data.Domain(all_attrs, self.data.domain.class_vars,
                                        self.data.domain.metas)
            data = Orange.data.Table(domain, self.data)
        else:
            data = None
        self.send("Sorted Data", data)
        self.send("Distances", self.matrix)

    def send_report(self):
        self.report_items(
            (("Separate By", ", ".join(self.selected_separeate_by_keys())),
             ("Sort By", ", ".join(self.selected_relevant_keys())),
             ("Distance Measure",
              self.DISTANCE_FUNCTIONS[self.distance_measure][0])))
        layout = self.groups_scroll_area.widget().layout()
        html = "<table>"
        for i in range(layout.count()):
            item = layout.itemAt(i)
            if isinstance(item, QSpacerItem):
                html += "<tr><td></td></tr>"
            elif isinstance(item, QWidgetItem):
                hor = item.widget()
                if isinstance(hor, QLabel):
                    label = hor.text()
                    html += "<tr><td><b>%s</b></td></tr>" % label
                elif isinstance(hor, QHeaderView):
                    model = hor.model()
                    content = (model.horizontalHeaderItem(col)
                               for col in range(model.columnCount()))
                    content = (item.text().replace('\n', "<br/>")
                               for item in content)
                    html += "<tr>" + ''.join("<td>{}</td>".format(item)
                                             for item in content) + "</tr>"
        html += "</table>"
        self.report_raw("Groups", html)
class OWDistributions(widget.OWWidget):
    name = "Distributions"
    description = "Display value distributions of a data feature in a graph."
    icon = "icons/Distribution.svg"
    priority = 120

    class Inputs:
        data = Input("Data", Orange.data.Table, doc="Set the input dataset")

    settingsHandler = settings.DomainContextHandler(
        match_values=settings.DomainContextHandler.MATCH_VALUES_ALL
    )
    #: Selected variable index
    variable_idx = settings.ContextSetting(-1)
    #: Selected group variable
    groupvar_idx = settings.ContextSetting(0)

    relative_freq = settings.Setting(False)
    disc_cont = settings.Setting(False)

    smoothing_index = settings.Setting(5)
    show_prob = settings.ContextSetting(0)

    graph_name = "plot"

    ASH_HIST = 50

    bins = [2, 3, 4, 5, 8, 10, 12, 15, 20, 30, 50]
    smoothing_facs = list(reversed([0.1, 0.2, 0.4, 0.6, 0.8, 1, 1.5, 2, 4, 6, 10]))

    def __init__(self):
        super().__init__()
        self.data = None

        self.distributions = None
        self.contingencies = None
        self.var = self.cvar = None
        varbox = gui.vBox(self.controlArea, "Variable")

        self.varmodel = itemmodels.VariableListModel()
        self.groupvarmodel = []

        self.varview = QListView(
            selectionMode=QListView.SingleSelection, uniformItemSizes=True
        )
        self.varview.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)
        self.varview.setModel(self.varmodel)
        self.varview.setSelectionModel(
            itemmodels.ListSingleSelectionModel(self.varmodel)
        )
        self.varview.selectionModel().selectionChanged.connect(
            self._on_variable_idx_changed
        )
        varbox.layout().addWidget(self.varview)

        box = gui.vBox(self.controlArea, "Precision")

        gui.separator(self.controlArea, 4, 4)

        box2 = gui.hBox(box)
        self.l_smoothing_l = gui.widgetLabel(box2, "Smooth")
        gui.hSlider(
            box2,
            self,
            "smoothing_index",
            minValue=0,
            maxValue=len(self.smoothing_facs) - 1,
            callback=self._on_set_smoothing,
            createLabel=False,
        )
        self.l_smoothing_r = gui.widgetLabel(box2, "Precise")

        self.cb_disc_cont = gui.checkBox(
            gui.indentedBox(box, sep=4),
            self,
            "disc_cont",
            "Bin numeric variables",
            callback=self._on_groupvar_idx_changed,
            tooltip="Show numeric variables as categorical.",
        )

        box = gui.vBox(self.controlArea, "Group by")
        self.icons = gui.attributeIconDict
        self.groupvarview = gui.comboBox(
            box,
            self,
            "groupvar_idx",
            callback=self._on_groupvar_idx_changed,
            valueType=str,
            contentsLength=12,
        )
        box2 = gui.indentedBox(box, sep=4)
        self.cb_rel_freq = gui.checkBox(
            box2,
            self,
            "relative_freq",
            "Show relative frequencies",
            callback=self._on_relative_freq_changed,
            tooltip="Normalize probabilities so that probabilities "
            "for each group-by value sum to 1.",
        )
        gui.separator(box2)
        self.cb_prob = gui.comboBox(
            box2,
            self,
            "show_prob",
            label="Show probabilities:",
            orientation=Qt.Horizontal,
            callback=self._on_relative_freq_changed,
            tooltip="Show probabilities for a chosen group-by value "
            "(at each point probabilities for all group-by values sum to 1).",
        )

        self.plotview = pg.PlotWidget(background=None)
        self.plotview.setRenderHint(QPainter.Antialiasing)
        self.mainArea.layout().addWidget(self.plotview)
        w = QLabel()
        w.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.mainArea.layout().addWidget(w, Qt.AlignCenter)
        self.ploti = pg.PlotItem()
        self.plot = self.ploti.vb
        self.ploti.hideButtons()
        self.plotview.setCentralItem(self.ploti)

        self.plot_prob = pg.ViewBox()
        self.ploti.hideAxis("right")
        self.ploti.scene().addItem(self.plot_prob)
        self.ploti.getAxis("right").linkToView(self.plot_prob)
        self.ploti.getAxis("right").setLabel("Probability")
        self.plot_prob.setZValue(10)
        self.plot_prob.setXLink(self.ploti)
        self.update_views()
        self.ploti.vb.sigResized.connect(self.update_views)
        self.plot_prob.setRange(yRange=[0, 1])

        def disable_mouse(plot):
            plot.setMouseEnabled(False, False)
            plot.setMenuEnabled(False)

        disable_mouse(self.plot)
        disable_mouse(self.plot_prob)

        self.tooltip_items = []
        self.plot.scene().installEventFilter(HelpEventDelegate(self.help_event, self))

        pen = QPen(self.palette().color(QPalette.Text))
        for axis in ("left", "bottom"):
            self.ploti.getAxis(axis).setPen(pen)

        self._legend = LegendItem()
        self._legend.setParentItem(self.plot)
        self._legend.hide()
        self._legend.anchor((1, 0), (1, 0))

    def update_views(self):
        self.plot_prob.setGeometry(self.plot.sceneBoundingRect())
        self.plot_prob.linkedViewChanged(self.plot, self.plot_prob.XAxis)

    @Inputs.data
    def set_data(self, data):
        self.closeContext()
        self.clear()
        self.warning()
        self.data = data
        self.distributions = None
        self.contingencies = None
        if self.data is not None:
            if not self.data:
                self.warning("Empty input data cannot be visualized")
                return
            domain = self.data.domain
            self.varmodel[:] = list(domain.variables) + [
                meta for meta in domain.metas if meta.is_continuous or meta.is_discrete
            ]
            self.groupvarview.clear()
            self.groupvarmodel = (
                ["(None)"]
                + [var for var in domain.variables if var.is_discrete]
                + [meta for meta in domain.metas if meta.is_discrete]
            )
            self.groupvarview.addItem("(None)")
            for var in self.groupvarmodel[1:]:
                self.groupvarview.addItem(self.icons[var], var.name)
            if domain.has_discrete_class:
                self.groupvar_idx = self.groupvarmodel[1:].index(domain.class_var) + 1
            self.openContext(domain)
            self.variable_idx = min(max(self.variable_idx, 0), len(self.varmodel) - 1)
            self.groupvar_idx = min(
                max(self.groupvar_idx, 0), len(self.groupvarmodel) - 1
            )
            itemmodels.select_row(self.varview, self.variable_idx)
            self._setup()

    def clear(self):
        self.plot.clear()
        self.plot_prob.clear()
        self.varmodel[:] = []
        self.groupvarmodel = []
        self.variable_idx = -1
        self.groupvar_idx = 0
        self._legend.clear()
        self._legend.hide()
        self.groupvarview.clear()
        self.cb_prob.clear()

    def _setup_smoothing(self):
        if not self.disc_cont and self.var and self.var.is_continuous:
            self.cb_disc_cont.setText("Bin numeric variables")
            self.l_smoothing_l.setText("Smooth")
            self.l_smoothing_r.setText("Precise")
        else:
            self.cb_disc_cont.setText(
                "Bin numeric variables into {} bins".format(
                    self.bins[self.smoothing_index]
                )
            )
            self.l_smoothing_l.setText(" " + str(self.bins[0]))
            self.l_smoothing_r.setText(" " + str(self.bins[-1]))

    @property
    def smoothing_factor(self):
        return self.smoothing_facs[self.smoothing_index]

    def _setup(self):
        self.plot.clear()
        self.plot_prob.clear()
        self._legend.clear()
        self._legend.hide()

        varidx = self.variable_idx
        self.var = self.cvar = None
        if varidx >= 0:
            self.var = self.varmodel[varidx]
        if self.groupvar_idx > 0:
            self.cvar = self.groupvarmodel[self.groupvar_idx]
            self.cb_prob.clear()
            self.cb_prob.addItem("(None)")
            self.cb_prob.addItems(self.cvar.values)
            self.cb_prob.addItem("(All)")
            self.show_prob = min(max(self.show_prob, 0), len(self.cvar.values) + 1)
        data = self.data
        self._setup_smoothing()
        if self.var is None:
            return
        if self.disc_cont:
            domain = Orange.data.Domain(
                [self.var, self.cvar] if self.cvar else [self.var]
            )
            data = Orange.data.Table(domain, data)
            disc = EqualWidth(n=self.bins[self.smoothing_index])
            data = Discretize(method=disc, remove_const=False)(data)
            self.var = data.domain[0]
        self.set_left_axis_name()
        self.enable_disable_rel_freq()
        if self.cvar:
            self.contingencies = contingency.get_contingency(data, self.var, self.cvar)
            self.display_contingency()
        else:
            self.distributions = distribution.get_distribution(data, self.var)
            self.display_distribution()
        self.plot.autoRange()

    def help_event(self, ev):
        self.plot.mapSceneToView(ev.scenePos())
        ctooltip = []
        for vb, item in self.tooltip_items:
            mouse_over_curve = isinstance(
                item, pg.PlotCurveItem
            ) and item.mouseShape().contains(vb.mapSceneToView(ev.scenePos()))
            mouse_over_bar = isinstance(
                item, DistributionBarItem
            ) and item.boundingRect().contains(vb.mapSceneToView(ev.scenePos()))
            if mouse_over_curve or mouse_over_bar:
                ctooltip.append(item.tooltip)
        if ctooltip:
            QToolTip.showText(
                ev.screenPos(), "\n\n".join(ctooltip), widget=self.plotview
            )
            return True
        return False

    def display_distribution(self):
        dist = self.distributions
        var = self.var
        if dist is None or not len(dist):
            return
        self.plot.clear()
        self.plot_prob.clear()
        self.ploti.hideAxis("right")
        self.tooltip_items = []

        bottomaxis = self.ploti.getAxis("bottom")
        bottomaxis.setLabel(var.name)
        bottomaxis.resizeEvent()

        self.set_left_axis_name()
        if var and var.is_continuous:
            bottomaxis.setTicks(None)
            if not len(dist[0]):
                return
            edges, curve = ash_curve(
                dist,
                None,
                m=OWDistributions.ASH_HIST,
                smoothing_factor=self.smoothing_factor,
            )
            edges = edges + (edges[1] - edges[0]) / 2
            edges = edges[:-1]
            item = pg.PlotCurveItem()
            pen = QPen(QBrush(Qt.white), 3)
            pen.setCosmetic(True)
            item.setData(
                edges,
                curve,
                antialias=True,
                stepMode=False,
                fillLevel=0,
                brush=QBrush(Qt.gray),
                pen=pen,
            )
            self.plot.addItem(item)
            item.tooltip = "Density"
            self.tooltip_items.append((self.plot, item))
        else:
            bottomaxis.setTicks([list(enumerate(var.values))])
            for i, w in enumerate(dist):
                geom = QRectF(i - 0.33, 0, 0.66, w)
                item = DistributionBarItem(geom, [1.0], [QColor(128, 128, 128)])
                self.plot.addItem(item)
                item.tooltip = "Frequency for %s: %r" % (var.values[i], w)
                self.tooltip_items.append((self.plot, item))

    def _on_relative_freq_changed(self):
        self.set_left_axis_name()
        if self.cvar and self.cvar.is_discrete:
            self.display_contingency()
        else:
            self.display_distribution()
        self.plot.autoRange()

    def display_contingency(self):
        """
        Set the contingency to display.
        """
        cont = self.contingencies
        var, cvar = self.var, self.cvar
        if cont is None or not len(cont):
            return
        self.plot.clear()
        self.plot_prob.clear()
        self._legend.clear()
        self.tooltip_items = []

        if self.show_prob:
            self.ploti.showAxis("right")
        else:
            self.ploti.hideAxis("right")

        bottomaxis = self.ploti.getAxis("bottom")
        bottomaxis.setLabel(var.name)
        bottomaxis.resizeEvent()

        cvar_values = cvar.values
        colors = [QColor(*col) for col in cvar.colors]

        if var and var.is_continuous:
            bottomaxis.setTicks(None)

            weights, cols, cvar_values, curves = [], [], [], []
            for i, dist in enumerate(cont):
                v, W = dist
                if len(v):
                    weights.append(numpy.sum(W))
                    cols.append(colors[i])
                    cvar_values.append(cvar.values[i])
                    curves.append(
                        ash_curve(
                            dist,
                            cont,
                            m=OWDistributions.ASH_HIST,
                            smoothing_factor=self.smoothing_factor,
                        )
                    )
            weights = numpy.array(weights)
            sumw = numpy.sum(weights)
            weights /= sumw
            colors = cols
            curves = [(X, Y * w) for (X, Y), w in zip(curves, weights)]

            curvesline = []  # from histograms to lines
            for X, Y in curves:
                X = X + (X[1] - X[0]) / 2
                X = X[:-1]
                X = numpy.array(X)
                Y = numpy.array(Y)
                curvesline.append((X, Y))

            for t in ["fill", "line"]:
                curve_data = list(zip(curvesline, colors, weights, cvar_values))
                for (X, Y), color, w, cval in reversed(curve_data):
                    item = pg.PlotCurveItem()
                    pen = QPen(QBrush(color), 3)
                    pen.setCosmetic(True)
                    color = QColor(color)
                    color.setAlphaF(0.2)
                    item.setData(
                        X,
                        Y / (w if self.relative_freq else 1),
                        antialias=True,
                        stepMode=False,
                        fillLevel=0 if t == "fill" else None,
                        brush=QBrush(color),
                        pen=pen,
                    )
                    self.plot.addItem(item)
                    if t == "line":
                        item.tooltip = "{}\n{}={}".format(
                            "Normalized density " if self.relative_freq else "Density ",
                            cvar.name,
                            cval,
                        )
                        self.tooltip_items.append((self.plot, item))

            if self.show_prob:
                all_X = numpy.array(
                    numpy.unique(numpy.hstack([X for X, _ in curvesline]))
                )
                inter_X = numpy.array(
                    numpy.linspace(all_X[0], all_X[-1], len(all_X) * 2)
                )
                curvesinterp = [numpy.interp(inter_X, X, Y) for (X, Y) in curvesline]
                sumprob = numpy.sum(curvesinterp, axis=0)
                legal = sumprob > 0.05 * numpy.max(sumprob)

                i = len(curvesinterp) + 1
                show_all = self.show_prob == i
                for Y, color, cval in reversed(
                    list(zip(curvesinterp, colors, cvar_values))
                ):
                    i -= 1
                    if show_all or self.show_prob == i:
                        item = pg.PlotCurveItem()
                        pen = QPen(QBrush(color), 3, style=Qt.DotLine)
                        pen.setCosmetic(True)
                        prob = Y[legal] / sumprob[legal]
                        item.setData(
                            inter_X[legal],
                            prob,
                            antialias=True,
                            stepMode=False,
                            fillLevel=None,
                            brush=None,
                            pen=pen,
                        )
                        self.plot_prob.addItem(item)
                        item.tooltip = "Probability that \n" + cvar.name + "=" + cval
                        self.tooltip_items.append((self.plot_prob, item))

        elif var and var.is_discrete:
            bottomaxis.setTicks([list(enumerate(var.values))])

            cont = numpy.array(cont)

            maxh = 0  # maximal column height
            maxrh = 0  # maximal relative column height
            scvar = cont.sum(axis=1)
            # a cvar with sum=0 with allways have distribution counts 0,
            # therefore we can divide it by anything
            scvar[scvar == 0] = 1
            for i, (value, dist) in enumerate(zip(var.values, cont.T)):
                maxh = max(maxh, max(dist))
                maxrh = max(maxrh, max(dist / scvar))

            for i, (value, dist) in enumerate(zip(var.values, cont.T)):
                dsum = sum(dist)
                geom = QRectF(
                    i - 0.333, 0, 0.666, maxrh if self.relative_freq else maxh
                )
                if self.show_prob:
                    prob = dist / dsum
                    ci = 1.96 * numpy.sqrt(prob * (1 - prob) / dsum)
                else:
                    ci = None
                item = DistributionBarItem(
                    geom,
                    dist / scvar / maxrh if self.relative_freq else dist / maxh,
                    colors,
                )
                self.plot.addItem(item)
                tooltip = "\n".join(
                    "%s: %.*f" % (n, 3 if self.relative_freq else 1, v)
                    for n, v in zip(
                        cvar_values, dist / scvar if self.relative_freq else dist
                    )
                )
                item.tooltip = "{} ({}={}):\n{}".format(
                    "Normalized frequency " if self.relative_freq else "Frequency ",
                    cvar.name,
                    value,
                    tooltip,
                )
                self.tooltip_items.append((self.plot, item))

                if self.show_prob:
                    item.tooltip += "\n\nProbabilities:"
                    for ic, a in enumerate(dist):
                        if self.show_prob - 1 != ic and self.show_prob - 1 != len(dist):
                            continue
                        position = -0.333 + ((ic + 0.5) * 0.666 / len(dist))
                        if dsum < 1e-6:
                            continue
                        prob = a / dsum
                        if not 1e-6 < prob < 1 - 1e-6:
                            continue
                        ci = 1.96 * sqrt(prob * (1 - prob) / dsum)
                        item.tooltip += "\n%s: %.3f ± %.3f" % (
                            cvar_values[ic],
                            prob,
                            ci,
                        )
                        mark = pg.ScatterPlotItem()
                        errorbar = pg.ErrorBarItem()
                        pen = QPen(QBrush(QColor(0)), 1)
                        pen.setCosmetic(True)
                        errorbar.setData(
                            x=[i + position],
                            y=[prob],
                            bottom=min(numpy.array([ci]), prob),
                            top=min(numpy.array([ci]), 1 - prob),
                            beam=numpy.array([0.05]),
                            brush=QColor(1),
                            pen=pen,
                        )
                        mark.setData(
                            [i + position],
                            [prob],
                            antialias=True,
                            symbol="o",
                            fillLevel=None,
                            pxMode=True,
                            size=10,
                            brush=QColor(colors[ic]),
                            pen=pen,
                        )
                        self.plot_prob.addItem(errorbar)
                        self.plot_prob.addItem(mark)

        for color, name in zip(colors, cvar_values):
            self._legend.addItem(
                ScatterPlotItem(pen=color, brush=color, size=10, shape="s"),
                escape(name),
            )
        self._legend.show()

    def set_left_axis_name(self):
        leftaxis = self.ploti.getAxis("left")
        set_label = leftaxis.setLabel
        if self.var and self.var.is_continuous:
            set_label(
                ["Density", "Relative density"][
                    self.cvar is not None and self.relative_freq
                ]
            )
        else:
            set_label(
                ["Frequency", "Relative frequency"][
                    self.cvar is not None and self.relative_freq
                ]
            )
        leftaxis.resizeEvent()

    def enable_disable_rel_freq(self):
        self.cb_prob.setDisabled(self.var is None or self.cvar is None)
        self.cb_rel_freq.setDisabled(self.var is None or self.cvar is None)

    def _on_variable_idx_changed(self):
        self.variable_idx = selected_index(self.varview)
        self._setup()

    def _on_groupvar_idx_changed(self):
        self._setup()

    def _on_set_smoothing(self):
        self._setup()

    def onDeleteWidget(self):
        self.plot.clear()
        super().onDeleteWidget()

    def get_widget_name_extension(self):
        if self.variable_idx >= 0:
            return self.varmodel[self.variable_idx]

    def send_report(self):
        self.plotview.scene().setSceneRect(self.plotview.sceneRect())
        if self.variable_idx < 0:
            return
        self.report_plot()
        text = "Distribution of '{}'".format(self.varmodel[self.variable_idx])
        if self.groupvar_idx:
            group_var = self.groupvarmodel[self.groupvar_idx]
            prob = self.cb_prob
            indiv_probs = 0 < prob.currentIndex() < prob.count() - 1
            if not indiv_probs or self.relative_freq:
                text += " grouped by '{}'".format(group_var)
                if self.relative_freq:
                    text += " (relative frequencies)"
            if indiv_probs:
                text += "; probabilites for '{}={}'".format(
                    group_var, prob.currentText()
                )
        self.report_caption(text)
Exemplo n.º 41
0
class DiscreteVariableEditor(VariableEditor):
    """An editor widget for editing a discrete variable.

    Extends the :class:`VariableEditor` to enable editing of
    variables values.
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        form = self.layout().itemAt(0)
        assert isinstance(form, QFormLayout)

        #: A list model of discrete variable's values.
        self.values_model = itemmodels.PyListModel(
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
        )

        vlayout = QVBoxLayout(spacing=1, margin=0)
        self.values_edit = QListView(
            editTriggers=QListView.DoubleClicked | QListView.EditKeyPressed
        )
        self.values_edit.setItemDelegate(CategoriesEditDelegate(self))
        self.values_edit.setModel(self.values_model)
        self.values_model.dataChanged.connect(self.on_values_changed)

        self.values_edit.selectionModel().selectionChanged.connect(
            self.on_value_selection_changed)
        self.values_model.layoutChanged.connect(self.on_value_selection_changed)
        self.values_model.rowsMoved.connect(self.on_value_selection_changed)

        vlayout.addWidget(self.values_edit)
        hlayout = QHBoxLayout(spacing=1, margin=0)

        self.categories_action_group = group = QActionGroup(
            self, objectName="action-group-categories", enabled=False
        )
        self.move_value_up = QAction(
            "\N{UPWARDS ARROW}", group,
            toolTip="Move the selected item up.",
            shortcut=QKeySequence(Qt.ControlModifier | Qt.AltModifier |
                                  Qt.Key_BracketLeft),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.move_value_up.triggered.connect(self.move_up)

        self.move_value_down = QAction(
            "\N{DOWNWARDS ARROW}", group,
            toolTip="Move the selected item down.",
            shortcut=QKeySequence(Qt.ControlModifier | Qt.AltModifier |
                                  Qt.Key_BracketRight),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.move_value_down.triggered.connect(self.move_down)

        self.add_new_item = QAction(
            "+", group,
            objectName="action-add-item",
            toolTip="Append a new item.",
            shortcut=QKeySequence(QKeySequence.New),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.remove_item = QAction(
            "\N{MINUS SIGN}", group,
            objectName="action-remove-item",
            toolTip="Delete the selected item.",
            shortcut=QKeySequence(QKeySequence.Delete),
            shortcutContext=Qt.WidgetShortcut,
        )

        self.add_new_item.triggered.connect(self._add_category)
        self.remove_item.triggered.connect(self._remove_category)

        button1 = FixedSizeButton(
            self, defaultAction=self.move_value_up,
            accessibleName="Move up"
        )
        button2 = FixedSizeButton(
            self, defaultAction=self.move_value_down,
            accessibleName="Move down"
        )
        button3 = FixedSizeButton(
            self, defaultAction=self.add_new_item,
            accessibleName="Add"
        )
        button4 = FixedSizeButton(
            self, defaultAction=self.remove_item,
            accessibleName="Remove"
        )
        self.values_edit.addActions([self.move_value_up, self.move_value_down,
                                     self.add_new_item, self.remove_item])
        hlayout.addWidget(button1)
        hlayout.addWidget(button2)
        hlayout.addSpacing(3)
        hlayout.addWidget(button3)
        hlayout.addWidget(button4)

        hlayout.addStretch(10)
        vlayout.addLayout(hlayout)

        form.insertRow(1, "Values:", vlayout)

        QWidget.setTabOrder(self.name_edit, self.values_edit)
        QWidget.setTabOrder(self.values_edit, button1)
        QWidget.setTabOrder(button1, button2)
        QWidget.setTabOrder(button2, button3)
        QWidget.setTabOrder(button3, button4)

    def set_data(self, var, transform=()):
        # type: (Optional[Categorical], Sequence[Transform]) -> None
        """
        Set the variable to edit.
        """
        super().set_data(var, transform)
        tr = None  # type: Optional[CategoriesMapping]
        for tr_ in transform:
            if isinstance(tr_, CategoriesMapping):
                tr = tr_

        items = []
        if tr is not None:
            ci_index = {c: i for i, c in enumerate(var.categories)}
            for ci, cj in tr.mapping:
                if ci is None and cj is not None:
                    # level added
                    item = {
                        Qt.EditRole: cj,
                        EditStateRole: ItemEditState.Added,
                        SourcePosRole: None
                    }
                elif ci is not None and cj is None:
                    # ci level dropped
                    item = {
                        Qt.EditRole: ci,
                        EditStateRole: ItemEditState.Dropped,
                        SourcePosRole: ci_index[ci],
                        SourceNameRole: ci
                    }
                elif ci is not None and cj is not None:
                    # rename or reorder
                    item = {
                        Qt.EditRole: cj,
                        EditStateRole: ItemEditState.NoState,
                        SourcePosRole: ci_index[ci],
                        SourceNameRole: ci
                    }
                else:
                    assert False, "invalid mapping: {!r}".format(tr.mapping)
                items.append(item)
        elif var is not None:
            items = [
                {Qt.EditRole: c,
                 EditStateRole: ItemEditState.NoState,
                 SourcePosRole: i,
                 SourceNameRole: c}
                for i, c in enumerate(var.categories)
            ]
        else:
            items = []

        with disconnected(self.values_model.dataChanged,
                          self.on_values_changed):
            self.values_model.clear()
            self.values_model.insertRows(0, len(items))
            for i, item in enumerate(items):
                self.values_model.setItemData(
                    self.values_model.index(i, 0),
                    item
                )
        self.add_new_item.actionGroup().setEnabled(var is not None)

    def __categories_mapping(self):
        # type: () -> CategoriesMappingType
        model = self.values_model
        source = self.var.categories

        res = []
        for i in range(model.rowCount()):
            midx = model.index(i, 0)
            category = midx.data(Qt.EditRole)
            source_pos = midx.data(SourcePosRole)  # type: Optional[int]
            if source_pos is not None:
                source_name = source[source_pos]
            else:
                source_name = None
            state = midx.data(EditStateRole)
            if state == ItemEditState.Dropped:
                res.append((source_name, None))
            elif state == ItemEditState.Added:
                res.append((None, category))
            else:
                res.append((source_name, category))
        return res

    def get_data(self):
        """Retrieve the modified variable
        """
        var, tr = super().get_data()
        if var is None:
            return var, tr
        mapping = self.__categories_mapping()
        if any(_1 != _2 or _2 != _3
               for (_1, _2), _3 in zip_longest(mapping, var.categories)):
            tr.append(CategoriesMapping(mapping))
        return var, tr

    def clear(self):
        """Clear the model state.
        """
        super().clear()
        self.values_model.clear()

    def move_rows(self, rows, offset):
        if not rows:
            return
        assert len(rows) == 1
        i = rows[0].row()
        if offset > 0:
            offset += 1
        self.values_model.moveRows(QModelIndex(), i, 1, QModelIndex(), i + offset)
        self.variable_changed.emit()

    def move_up(self):
        rows = self.values_edit.selectionModel().selectedRows()
        self.move_rows(rows, -1)

    def move_down(self):
        rows = self.values_edit.selectionModel().selectedRows()
        self.move_rows(rows, 1)

    @Slot()
    def on_values_changed(self):
        self.variable_changed.emit()

    @Slot()
    def on_value_selection_changed(self):
        rows = self.values_edit.selectionModel().selectedRows()
        if rows:
            i = rows[0].row()
            self.move_value_up.setEnabled(i)
            self.move_value_down.setEnabled(i != self.values_model.rowCount() - 1)
        else:
            self.move_value_up.setEnabled(False)
            self.move_value_down.setEnabled(False)

    def _remove_category(self):
        """
        Remove the current selected category.

        If the item is an existing category present in the source variable it
        is marked as removed in the view. But if it was added in the set
        transformation it is removed entirely from the model and view.
        """
        view = self.values_edit
        rows = view.selectionModel().selectedRows(0)
        if not rows:
            return
        assert len(rows) == 1
        index = rows[0]  # type: QModelIndex
        model = index.model()
        state = index.data(EditStateRole)
        pos = index.data(Qt.UserRole)
        if pos is not None and pos >= 0:
            # existing level -> only mark/toggle its dropped state
            model.setData(
                index,
                ItemEditState.Dropped if state != ItemEditState.Dropped
                else ItemEditState.NoState,
                EditStateRole)
        elif state == ItemEditState.Added:
            # new level -> remove it
            model.removeRow(index.row())
        else:
            assert False, "invalid state '{}' for {}" \
                .format(state, index.row())

    def _add_category(self):
        """
        Add a new category
        """
        view = self.values_edit
        model = view.model()

        with disconnected(model.dataChanged, self.on_values_changed,
                          Qt.UniqueConnection):
            row = model.rowCount()
            if not model.insertRow(model.rowCount()):
                return
            index = model.index(row, 0)
            model.setItemData(
                index, {
                    Qt.EditRole: "",
                    SourcePosRole: None,
                    EditStateRole: ItemEditState.Added
                }
            )
            view.setCurrentIndex(index)
            view.edit(index)
        self.on_values_changed()
Exemplo n.º 42
0
class OWPythonScript(widget.OWWidget):
    name = "Python Script"
    description = "Write a Python script and run it on input data or models."
    icon = "icons/PythonScript.svg"
    priority = 3150
    keywords = ["file", "program"]

    class Inputs:
        data = Input("Data", Table, replaces=["in_data"],
                     default=True, multiple=True)
        learner = Input("Learner", Learner, replaces=["in_learner"],
                        default=True, multiple=True)
        classifier = Input("Classifier", Model, replaces=["in_classifier"],
                           default=True, multiple=True)
        object = Input("Object", object, replaces=["in_object"],
                       default=False, multiple=True)

    class Outputs:
        data = Output("Data", Table, replaces=["out_data"])
        learner = Output("Learner", Learner, replaces=["out_learner"])
        classifier = Output("Classifier", Model, replaces=["out_classifier"])
        object = Output("Object", object, replaces=["out_object"])

    signal_names = ("data", "learner", "classifier", "object")

    settingsHandler = PrepareSavingSettingsHandler()

    libraryListSource = \
        Setting([Script("Hello world", "print('Hello world')\n")])
    currentScriptIndex = Setting(0)
    scriptText = Setting(None, schema_only=True)
    splitterState = Setting(None)

    class Error(OWWidget.Error):
        pass

    def __init__(self):
        super().__init__()

        for name in self.signal_names:
            setattr(self, name, {})

        for s in self.libraryListSource:
            s.flags = 0

        self._cachedDocuments = {}

        self.infoBox = gui.vBox(self.controlArea, 'Info')
        gui.label(
            self.infoBox, self,
            "<p>Execute python script.</p><p>Input variables:<ul><li> " +
            "<li>".join(map("in_{0}, in_{0}s".format, self.signal_names)) +
            "</ul></p><p>Output variables:<ul><li>" +
            "<li>".join(map("out_{0}".format, self.signal_names)) +
            "</ul></p>"
        )

        self.libraryList = itemmodels.PyListModel(
            [], self,
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable)

        self.libraryList.wrap(self.libraryListSource)

        self.controlBox = gui.vBox(self.controlArea, 'Library')
        self.controlBox.layout().setSpacing(1)

        self.libraryView = QListView(
            editTriggers=QListView.DoubleClicked |
            QListView.EditKeyPressed,
            sizePolicy=QSizePolicy(QSizePolicy.Ignored,
                                   QSizePolicy.Preferred)
        )
        self.libraryView.setItemDelegate(ScriptItemDelegate(self))
        self.libraryView.setModel(self.libraryList)

        self.libraryView.selectionModel().selectionChanged.connect(
            self.onSelectedScriptChanged
        )
        self.controlBox.layout().addWidget(self.libraryView)

        w = itemmodels.ModelActionsWidget()

        self.addNewScriptAction = action = QAction("+", self)
        action.setToolTip("Add a new script to the library")
        action.triggered.connect(self.onAddScript)
        w.addAction(action)

        action = QAction(unicodedata.lookup("MINUS SIGN"), self)
        action.setToolTip("Remove script from library")
        action.triggered.connect(self.onRemoveScript)
        w.addAction(action)

        action = QAction("Update", self)
        action.setToolTip("Save changes in the editor to library")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.triggered.connect(self.commitChangesToLibrary)
        w.addAction(action)

        action = QAction("More", self, toolTip="More actions")

        new_from_file = QAction("Import Script from File", self)
        save_to_file = QAction("Save Selected Script to File", self)
        restore_saved = QAction("Undo Changes to Selected Script", self)
        save_to_file.setShortcut(QKeySequence(QKeySequence.SaveAs))

        new_from_file.triggered.connect(self.onAddScriptFromFile)
        save_to_file.triggered.connect(self.saveScript)
        restore_saved.triggered.connect(self.restoreSaved)

        menu = QMenu(w)
        menu.addAction(new_from_file)
        menu.addAction(save_to_file)
        menu.addAction(restore_saved)
        action.setMenu(menu)
        button = w.addAction(action)
        button.setPopupMode(QToolButton.InstantPopup)

        w.layout().setSpacing(1)

        self.controlBox.layout().addWidget(w)

        self.execute_button = gui.button(self.controlArea, self, 'Run', callback=self.commit)

        self.splitCanvas = QSplitter(Qt.Vertical, self.mainArea)
        self.mainArea.layout().addWidget(self.splitCanvas)

        self.defaultFont = defaultFont = \
            "Monaco" if sys.platform == "darwin" else "Courier"

        self.textBox = gui.vBox(self, 'Python Script')
        self.splitCanvas.addWidget(self.textBox)
        self.text = PythonScriptEditor(self)
        self.textBox.layout().addWidget(self.text)

        self.textBox.setAlignment(Qt.AlignVCenter)
        self.text.setTabStopWidth(4)

        self.text.modificationChanged[bool].connect(self.onModificationChanged)

        self.saveAction = action = QAction("&Save", self.text)
        action.setToolTip("Save script to file")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        action.triggered.connect(self.saveScript)

        self.consoleBox = gui.vBox(self, 'Console')
        self.splitCanvas.addWidget(self.consoleBox)
        self.console = PythonConsole({}, self)
        self.consoleBox.layout().addWidget(self.console)
        self.console.document().setDefaultFont(QFont(defaultFont))
        self.consoleBox.setAlignment(Qt.AlignBottom)
        self.console.setTabStopWidth(4)

        select_row(self.libraryView, self.currentScriptIndex)

        self.restoreScriptText()

        self.splitCanvas.setSizes([2, 1])
        if self.splitterState is not None:
            self.splitCanvas.restoreState(QByteArray(self.splitterState))

        self.splitCanvas.splitterMoved[int, int].connect(self.onSpliterMoved)
        self.controlArea.layout().addStretch(1)
        self.resize(800, 600)

    def storeSpecificSettings(self):
        self.saveScriptText()

    def restoreScriptText(self):
        if self.scriptText is not None:
            current = self.text.toPlainText()
            # do not mark scripts as modified
            if self.scriptText != current:
                self.text.document().setPlainText(self.scriptText)

    def saveScriptText(self):
        self.scriptText = self.text.toPlainText()

    def handle_input(self, obj, id, signal):
        id = id[0]
        dic = getattr(self, signal)
        if obj is None:
            if id in dic.keys():
                del dic[id]
        else:
            dic[id] = obj

    @Inputs.data
    def set_data(self, data, id):
        self.handle_input(data, id, "data")

    @Inputs.learner
    def set_learner(self, data, id):
        self.handle_input(data, id, "learner")

    @Inputs.classifier
    def set_classifier(self, data, id):
        self.handle_input(data, id, "classifier")

    @Inputs.object
    def set_object(self, data, id):
        self.handle_input(data, id, "object")

    def handleNewSignals(self):
        self.commit()

    def selectedScriptIndex(self):
        rows = self.libraryView.selectionModel().selectedRows()
        if rows:
            return [i.row() for i in rows][0]
        else:
            return None

    def setSelectedScript(self, index):
        select_row(self.libraryView, index)

    def onAddScript(self, *args):
        self.libraryList.append(Script("New script", self.text.toPlainText(), 0))
        self.setSelectedScript(len(self.libraryList) - 1)

    def onAddScriptFromFile(self, *args):
        filename, _ = QFileDialog.getOpenFileName(
            self, 'Open Python Script',
            os.path.expanduser("~/"),
            'Python files (*.py)\nAll files(*.*)'
        )
        if filename:
            name = os.path.basename(filename)
            # TODO: use `tokenize.detect_encoding`
            with open(filename, encoding="utf-8") as f:
                contents = f.read()
            self.libraryList.append(Script(name, contents, 0, filename))
            self.setSelectedScript(len(self.libraryList) - 1)

    def onRemoveScript(self, *args):
        index = self.selectedScriptIndex()
        if index is not None:
            del self.libraryList[index]
            select_row(self.libraryView, max(index - 1, 0))

    def onSaveScriptToFile(self, *args):
        index = self.selectedScriptIndex()
        if index is not None:
            self.saveScript()

    def onSelectedScriptChanged(self, selected, deselected):
        index = [i.row() for i in selected.indexes()]
        if index:
            current = index[0]
            if current >= len(self.libraryList):
                self.addNewScriptAction.trigger()
                return

            self.text.setDocument(self.documentForScript(current))
            self.currentScriptIndex = current

    def documentForScript(self, script=0):
        if type(script) != Script:
            script = self.libraryList[script]
        if script not in self._cachedDocuments:
            doc = QTextDocument(self)
            doc.setDocumentLayout(QPlainTextDocumentLayout(doc))
            doc.setPlainText(script.script)
            doc.setDefaultFont(QFont(self.defaultFont))
            doc.highlighter = PythonSyntaxHighlighter(doc)
            doc.modificationChanged[bool].connect(self.onModificationChanged)
            doc.setModified(False)
            self._cachedDocuments[script] = doc
        return self._cachedDocuments[script]

    def commitChangesToLibrary(self, *args):
        index = self.selectedScriptIndex()
        if index is not None:
            self.libraryList[index].script = self.text.toPlainText()
            self.text.document().setModified(False)
            self.libraryList.emitDataChanged(index)

    def onModificationChanged(self, modified):
        index = self.selectedScriptIndex()
        if index is not None:
            self.libraryList[index].flags = Script.Modified if modified else 0
            self.libraryList.emitDataChanged(index)

    def onSpliterMoved(self, pos, ind):
        self.splitterState = bytes(self.splitCanvas.saveState())

    def restoreSaved(self):
        index = self.selectedScriptIndex()
        if index is not None:
            self.text.document().setPlainText(self.libraryList[index].script)
            self.text.document().setModified(False)

    def saveScript(self):
        index = self.selectedScriptIndex()
        if index is not None:
            script = self.libraryList[index]
            filename = script.filename
        else:
            filename = os.path.expanduser("~/")

        filename, _ = QFileDialog.getSaveFileName(
            self, 'Save Python Script',
            filename,
            'Python files (*.py)\nAll files(*.*)'
        )

        if filename:
            fn = ""
            head, tail = os.path.splitext(filename)
            if not tail:
                fn = head + ".py"
            else:
                fn = filename

            f = open(fn, 'w')
            f.write(self.text.toPlainText())
            f.close()

    def initial_locals_state(self):
        d = {}
        for name in self.signal_names:
            value = getattr(self, name)
            all_values = list(value.values())
            one_value = all_values[0] if len(all_values) == 1 else None
            d["in_" + name + "s"] = all_values
            d["in_" + name] = one_value
        return d

    def commit(self):
        self.Error.clear()
        self._script = str(self.text.toPlainText())
        lcls = self.initial_locals_state()
        lcls["_script"] = str(self.text.toPlainText())
        self.console.updateLocals(lcls)
        self.console.write("\nRunning script:\n")
        self.console.push("exec(_script)")
        self.console.new_prompt(sys.ps1)
        for signal in self.signal_names:
            out_var = self.console.locals.get("out_" + signal)
            signal_type = getattr(self.Outputs, signal).type
            if not isinstance(out_var, signal_type) and out_var is not None:
                self.Error.add_message(signal,
                                       "'{}' has to be an instance of '{}'.".
                                       format(signal, signal_type.__name__))
                getattr(self.Error, signal)()
                out_var = None
            getattr(self.Outputs, signal).send(out_var)
Exemplo n.º 43
0
class OWImpute(OWWidget):
    name = "Impute"
    description = "Impute missing values in the data table."
    icon = "icons/Impute.svg"
    priority = 2130
    keywords = ["substitute", "missing"]

    class Inputs:
        data = Input("Data", Orange.data.Table)
        learner = Input("Learner", Learner)

    class Outputs:
        data = Output("Data", Orange.data.Table)

    class Error(OWWidget.Error):
        imputation_failed = Msg("Imputation failed for '{}'")
        model_based_imputer_sparse = Msg(
            "Model based imputer does not work for sparse data")

    settingsHandler = settings.DomainContextHandler()

    _default_method_index = settings.Setting(int(Method.Leave))  # type: int
    # Per-variable imputation state (synced in storeSpecificSettings)
    _variable_imputation_state = settings.ContextSetting(
        {})  # type: VariableState

    autocommit = settings.Setting(True)

    want_main_area = False
    resizing_enabled = False

    def __init__(self):
        super().__init__()
        self.data = None  # type: Optional[Orange.data.Table]
        self.learner = None  # type: Optional[Learner]
        self.default_learner = SimpleTreeLearner()
        self.modified = False
        self.executor = qconcurrent.ThreadExecutor(self)
        self.__task = None

        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(10, 10, 10, 10)
        self.controlArea.layout().addLayout(main_layout)

        box = QGroupBox(title=self.tr("Default Method"), flat=False)
        box_layout = QVBoxLayout(box)
        main_layout.addWidget(box)

        button_group = QButtonGroup()
        button_group.buttonClicked[int].connect(self.set_default_method)

        for method, _ in list(METHODS.items())[1:-1]:
            imputer = self.create_imputer(method)
            button = QRadioButton(imputer.name)
            button.setChecked(method == self.default_method_index)
            button_group.addButton(button, method)
            box_layout.addWidget(button)

        self.default_button_group = button_group

        box = QGroupBox(title=self.tr("Individual Attribute Settings"),
                        flat=False)
        main_layout.addWidget(box)

        horizontal_layout = QHBoxLayout(box)
        main_layout.addWidget(box)

        self.varview = QListView(selectionMode=QListView.ExtendedSelection,
                                 uniformItemSizes=True)
        self.varview.setItemDelegate(DisplayFormatDelegate())
        self.varmodel = itemmodels.VariableListModel()
        self.varview.setModel(self.varmodel)
        self.varview.selectionModel().selectionChanged.connect(
            self._on_var_selection_changed)
        self.selection = self.varview.selectionModel()

        horizontal_layout.addWidget(self.varview)

        method_layout = QVBoxLayout()
        horizontal_layout.addLayout(method_layout)

        button_group = QButtonGroup()
        for method in Method:
            imputer = self.create_imputer(method)
            button = QRadioButton(text=imputer.name)
            button_group.addButton(button, method)
            method_layout.addWidget(button)

        self.value_combo = QComboBox(
            minimumContentsLength=8,
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLength,
            activated=self._on_value_selected)
        self.value_double = QDoubleSpinBox(
            editingFinished=self._on_value_selected,
            minimum=-1000.,
            maximum=1000.,
            singleStep=.1,
            decimals=3,
        )
        self.value_stack = value_stack = QStackedWidget()
        value_stack.addWidget(self.value_combo)
        value_stack.addWidget(self.value_double)
        method_layout.addWidget(value_stack)

        button_group.buttonClicked[int].connect(
            self.set_method_for_current_selection)

        method_layout.addStretch(2)

        reset_button = QPushButton("Restore All to Default",
                                   checked=False,
                                   checkable=False,
                                   clicked=self.reset_variable_state,
                                   default=False,
                                   autoDefault=False)
        method_layout.addWidget(reset_button)

        self.variable_button_group = button_group

        box = gui.auto_commit(self.controlArea,
                              self,
                              "autocommit",
                              "Apply",
                              orientation=Qt.Horizontal,
                              checkbox_label="Apply automatically")
        box.button.setFixedWidth(180)
        box.layout().insertStretch(0)

    def create_imputer(self, method, *args):
        # type: (Method, ...) -> impute.BaseImputeMethod
        if method == Method.Model:
            if self.learner is not None:
                return impute.Model(self.learner)
            else:
                return impute.Model(self.default_learner)
        elif method == Method.AsAboveSoBelow:
            assert self.default_method_index != Method.AsAboveSoBelow
            default = self.create_imputer(Method(self.default_method_index))
            m = AsDefault()
            m.method = default
            return m
        else:
            return METHODS[method](*args)

    @property
    def default_method_index(self):
        return self._default_method_index

    @default_method_index.setter
    def default_method_index(self, index):
        if self._default_method_index != index:
            assert index != Method.AsAboveSoBelow
            self._default_method_index = index
            self.default_button_group.button(index).setChecked(True)
            # update variable view
            self.update_varview()
            self._invalidate()

    def set_default_method(self, index):
        """Set the current selected default imputation method.
        """
        self.default_method_index = index

    @Inputs.data
    @check_sql_input
    def set_data(self, data):
        self.closeContext()
        self.varmodel[:] = []
        self._variable_imputation_state = {}  # type: VariableState
        self.modified = False
        self.data = data

        if data is not None:
            self.varmodel[:] = data.domain.variables
            self.openContext(data.domain)
            # restore per variable imputation state
            self._restore_state(self._variable_imputation_state)

        self.update_varview()
        self.unconditional_commit()

    @Inputs.learner
    def set_learner(self, learner):
        self.learner = learner or self.default_learner
        imputer = self.create_imputer(Method.Model)
        button = self.default_button_group.button(Method.Model)
        button.setText(imputer.name)

        variable_button = self.variable_button_group.button(Method.Model)
        variable_button.setText(imputer.name)

        if learner is not None:
            self.default_method_index = Method.Model

        self.update_varview()
        self.commit()

    def get_method_for_column(self, column_index):
        # type: (int) -> impute.BaseImputeMethod
        """
        Return the imputation method for column by its index.
        """
        assert 0 <= column_index < len(self.varmodel)
        idx = self.varmodel.index(column_index, 0)
        state = idx.data(StateRole)
        if state is None:
            state = (Method.AsAboveSoBelow, ())
        return self.create_imputer(state[0], *state[1])

    def _invalidate(self):
        self.modified = True
        if self.__task is not None:
            self.cancel()
        self.commit()

    def commit(self):
        self.cancel()
        self.warning()
        self.Error.imputation_failed.clear()
        self.Error.model_based_imputer_sparse.clear()

        if self.data is None or len(self.data) == 0 or len(self.varmodel) == 0:
            self.Outputs.data.send(self.data)
            self.modified = False
            return

        data = self.data
        impute_state = [(i, var, self.get_method_for_column(i))
                        for i, var in enumerate(self.varmodel)]
        # normalize to the effective method bypasing AsDefault
        impute_state = [(i, var, m.method if isinstance(m, AsDefault) else m)
                        for i, var, m in impute_state]

        def impute_one(method, var, data):
            # type: (impute.BaseImputeMethod, Variable, Table) -> Any
            if isinstance(method, impute.Model) and data.is_sparse():
                raise SparseNotSupported()
            elif isinstance(method, impute.DropInstances):
                return RowMask(method(data, var))
            elif not method.supports_variable(var):
                raise VariableNotSupported(var)
            else:
                return method(data, var)

        futures = []
        for _, var, method in impute_state:
            f = self.executor.submit(impute_one, copy.deepcopy(method), var,
                                     data)
            futures.append(f)

        w = qconcurrent.FutureSetWatcher(futures)
        w.doneAll.connect(self.__commit_finish)
        w.progressChanged.connect(self.__progress_changed)
        self.__task = Task(futures, w)
        self.progressBarInit(processEvents=False)
        self.setBlocking(True)

    @Slot()
    def __commit_finish(self):
        assert QThread.currentThread() is self.thread()
        assert self.__task is not None
        futures = self.__task.futures
        assert len(futures) == len(self.varmodel)
        assert self.data is not None

        self.__task = None
        self.setBlocking(False)
        self.progressBarFinished()

        data = self.data
        attributes = []
        class_vars = []
        drop_mask = np.zeros(len(self.data), bool)

        for i, (var, fut) in enumerate(zip(self.varmodel, futures)):
            assert fut.done()
            newvar = []
            try:
                res = fut.result()
            except SparseNotSupported:
                self.Error.model_based_imputer_sparse()
                # ?? break
            except VariableNotSupported:
                self.warning("Default method can not handle '{}'".format(
                    var.name))
            except Exception:  # pylint: disable=broad-except
                log = logging.getLogger(__name__)
                log.info("Error for %s", var, exc_info=True)
                self.Error.imputation_failed(var.name)
                attributes = class_vars = None
                break
            else:
                if isinstance(res, RowMask):
                    drop_mask |= res.mask
                    newvar = var
                else:
                    newvar = res

            if isinstance(newvar, Orange.data.Variable):
                newvar = [newvar]

            if i < len(data.domain.attributes):
                attributes.extend(newvar)
            else:
                class_vars.extend(newvar)

        if attributes is None:
            data = None
        else:
            domain = Orange.data.Domain(attributes, class_vars,
                                        data.domain.metas)
            try:
                data = self.data.from_table(domain, data[~drop_mask])
            except Exception:  # pylint: disable=broad-except
                log = logging.getLogger(__name__)
                log.info("Error", exc_info=True)
                self.Error.imputation_failed("Unknown")
                data = None

        self.Outputs.data.send(data)
        self.modified = False

    @Slot(int, int)
    def __progress_changed(self, n, d):
        assert QThread.currentThread() is self.thread()
        assert self.__task is not None
        self.progressBarSet(100. * n / d)

    def cancel(self):
        if self.__task is not None:
            task, self.__task = self.__task, None
            task.cancel()
            task.watcher.doneAll.disconnect(self.__commit_finish)
            task.watcher.progressChanged.disconnect(self.__progress_changed)
            concurrent.futures.wait(task.futures)
            task.watcher.flush()
            self.progressBarFinished()
            self.setBlocking(False)

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

    def send_report(self):
        specific = []
        for i, var in enumerate(self.varmodel):
            method = self.get_method_for_column(i)
            if not isinstance(method, AsDefault):
                specific.append("{} ({})".format(var.name, str(method)))

        default = self.create_imputer(Method.AsAboveSoBelow)
        if specific:
            self.report_items((("Default method", default.name),
                               ("Specific imputers", ", ".join(specific))))
        else:
            self.report_items((("Method", default.name), ))

    def _on_var_selection_changed(self):
        indexes = self.selection.selectedIndexes()
        defmethod = (Method.AsAboveSoBelow, ())
        methods = [index.data(StateRole) for index in indexes]
        methods = [m if m is not None else defmethod for m in methods]
        methods = set(methods)
        selected_vars = [self.varmodel[index.row()] for index in indexes]
        has_discrete = any(var.is_discrete for var in selected_vars)
        fixed_value = None
        value_stack_enabled = False
        current_value_widget = None

        if len(methods) == 1:
            method_type, parameters = methods.pop()
            for m in Method:
                if method_type == m:
                    self.variable_button_group.button(m).setChecked(True)

            if method_type == Method.Default:
                (fixed_value, ) = parameters

        elif self.variable_button_group.checkedButton() is not None:
            # Uncheck the current button
            self.variable_button_group.setExclusive(False)
            self.variable_button_group.checkedButton().setChecked(False)
            self.variable_button_group.setExclusive(True)
            assert self.variable_button_group.checkedButton() is None

        # Update variable methods GUI enabled state based on selection.
        for method in Method:
            # use a default constructed imputer to query support
            imputer = self.create_imputer(method)
            enabled = all(
                imputer.supports_variable(var) for var in selected_vars)
            button = self.variable_button_group.button(method)
            button.setEnabled(enabled)

        # Update the "Value" edit GUI.
        if not has_discrete:
            # no discrete variables -> allow mass edit for all (continuous vars)
            value_stack_enabled = True
            current_value_widget = self.value_double
        elif len(selected_vars) == 1:
            # single discrete var -> enable and fill the values combo
            value_stack_enabled = True
            current_value_widget = self.value_combo
            self.value_combo.clear()
            self.value_combo.addItems(selected_vars[0].values)
        else:
            # mixed type selection -> disable
            value_stack_enabled = False
            current_value_widget = None
            self.variable_button_group.button(Method.Default).setEnabled(False)

        self.value_stack.setEnabled(value_stack_enabled)
        if current_value_widget is not None:
            self.value_stack.setCurrentWidget(current_value_widget)
            if fixed_value is not None:
                # set current value
                if current_value_widget is self.value_combo:
                    self.value_combo.setCurrentIndex(fixed_value)
                elif current_value_widget is self.value_double:
                    self.value_double.setValue(fixed_value)
                else:
                    assert False

    def set_method_for_current_selection(self, method_index):
        # type: (Method) -> None
        indexes = self.selection.selectedIndexes()
        self.set_method_for_indexes(indexes, method_index)

    def set_method_for_indexes(self, indexes, method_index):
        # type: (List[QModelIndex], Method) -> None
        if method_index == Method.AsAboveSoBelow:
            for index in indexes:
                self.varmodel.setData(index, None, StateRole)
        elif method_index == Method.Default:
            current = self.value_stack.currentWidget()
            if current is self.value_combo:
                value = self.value_combo.currentIndex()
            else:
                value = self.value_double.value()
            for index in indexes:
                state = (int(Method.Default), (value, ))
                self.varmodel.setData(index, state, StateRole)
        else:
            state = (int(method_index), ())
            for index in indexes:
                self.varmodel.setData(index, state, StateRole)

        self.update_varview(indexes)
        self._invalidate()

    def update_varview(self, indexes=None):
        if indexes is None:
            indexes = map(self.varmodel.index, range(len(self.varmodel)))

        for index in indexes:
            self.varmodel.setData(index,
                                  self.get_method_for_column(index.row()),
                                  DisplayMethodRole)

    def _on_value_selected(self):
        # The fixed 'Value' in the widget has been changed by the user.
        self.variable_button_group.button(Method.Default).setChecked(True)
        self.set_method_for_current_selection(Method.Default)

    def reset_variable_state(self):
        indexes = list(map(self.varmodel.index, range(len(self.varmodel))))
        self.set_method_for_indexes(indexes, Method.AsAboveSoBelow)
        self.variable_button_group.button(
            Method.AsAboveSoBelow).setChecked(True)

    def _store_state(self):
        # type: () -> VariableState
        """
        Save the current variable imputation state
        """
        state = {}  # type: VariableState
        for i, var in enumerate(self.varmodel):
            index = self.varmodel.index(i)
            m = index.data(StateRole)
            if m is not None:
                state[var_key(var)] = m
        return state

    def _restore_state(self, state):
        # type: (VariableState) -> None
        """
        Restore the variable imputation state from the saved state
        """
        def check(state):
            # check if state is a proper State
            if isinstance(state, tuple) and len(state) == 2:
                m, p = state
                if isinstance(m, int) and isinstance(p, tuple) and \
                        0 <= m < len(Method):
                    return True
            return False

        for i, var in enumerate(self.varmodel):
            m = state.get(var_key(var), None)
            if check(m):
                self.varmodel.setData(self.varmodel.index(i), m, StateRole)

    def storeSpecificSettings(self):
        self._variable_imputation_state = self._store_state()
        super().storeSpecificSettings()
Exemplo n.º 44
0
class OWFeatureConstructor(OWWidget):
    name = "Feature Constructor"
    description = "Construct new features (data columns) from a set of " \
                  "existing features in the input data set."
    icon = "icons/FeatureConstructor.svg"
    inputs = [("Data", Orange.data.Table, "setData")]
    outputs = [("Data", Orange.data.Table)]

    want_main_area = False

    settingsHandler = FeatureConstructorSettingsHandler()
    descriptors = ContextSetting([])
    currentIndex = ContextSetting(-1)

    EDITORS = [
        (ContinuousDescriptor, ContinuousFeatureEditor),
        (DiscreteDescriptor, DiscreteFeatureEditor),
        (StringDescriptor, StringFeatureEditor)
    ]

    class Error(OWWidget.Error):
        more_values_needed = Msg("Discrete feature {} needs more values.")
        invalid_expressions = Msg("Invalid expressions: {}.")

    def __init__(self):
        super().__init__()
        self.data = None
        self.editors = {}

        box = gui.vBox(self.controlArea, "Variable Definitions")

        toplayout = QHBoxLayout()
        toplayout.setContentsMargins(0, 0, 0, 0)
        box.layout().addLayout(toplayout)

        self.editorstack = QStackedWidget(
            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,
                                   QSizePolicy.MinimumExpanding)
        )

        for descclass, editorclass in self.EDITORS:
            editor = editorclass()
            editor.featureChanged.connect(self._on_modified)
            self.editors[descclass] = editor
            self.editorstack.addWidget(editor)

        self.editorstack.setEnabled(False)

        buttonlayout = QVBoxLayout(spacing=10)
        buttonlayout.setContentsMargins(0, 0, 0, 0)

        self.addbutton = QPushButton(
            "New", toolTip="Create a new variable",
            minimumWidth=120,
            shortcut=QKeySequence.New
        )

        def unique_name(fmt, reserved):
            candidates = (fmt.format(i) for i in count(1))
            return next(c for c in candidates if c not in reserved)

        def reserved_names():
            varnames = []
            if self.data is not None:
                varnames = [var.name for var in
                            self.data.domain.variables + self.data.domain.metas]
            varnames += [desc.name for desc in self.featuremodel]
            return set(varnames)

        def generate_newname(fmt):
            return unique_name(fmt, reserved_names())

        menu = QMenu(self.addbutton)
        cont = menu.addAction("Continuous")
        cont.triggered.connect(
            lambda: self.addFeature(
                ContinuousDescriptor(generate_newname("X{}"), "", 3))
        )
        disc = menu.addAction("Discrete")
        disc.triggered.connect(
            lambda: self.addFeature(
                DiscreteDescriptor(generate_newname("D{}"), "",
                                   ("A", "B"), -1, False))
        )
        string = menu.addAction("String")
        string.triggered.connect(
            lambda: self.addFeature(
                StringDescriptor(generate_newname("S{}"), ""))
        )
        menu.addSeparator()
        self.duplicateaction = menu.addAction("Duplicate Selected Variable")
        self.duplicateaction.triggered.connect(self.duplicateFeature)
        self.duplicateaction.setEnabled(False)
        self.addbutton.setMenu(menu)

        self.removebutton = QPushButton(
            "Remove", toolTip="Remove selected variable",
            minimumWidth=120,
            shortcut=QKeySequence.Delete
        )
        self.removebutton.clicked.connect(self.removeSelectedFeature)

        buttonlayout.addWidget(self.addbutton)
        buttonlayout.addWidget(self.removebutton)
        buttonlayout.addStretch(10)

        toplayout.addLayout(buttonlayout, 0)
        toplayout.addWidget(self.editorstack, 10)

        # Layout for the list view
        layout = QVBoxLayout(spacing=1, margin=0)
        self.featuremodel = DescriptorModel(parent=self)

        self.featureview = QListView(
            minimumWidth=200,
            sizePolicy=QSizePolicy(QSizePolicy.Minimum,
                                   QSizePolicy.MinimumExpanding)
        )

        self.featureview.setItemDelegate(FeatureItemDelegate(self))
        self.featureview.setModel(self.featuremodel)
        self.featureview.selectionModel().selectionChanged.connect(
            self._on_selectedVariableChanged
        )

        layout.addWidget(self.featureview)

        box.layout().addLayout(layout, 1)

        box = gui.hBox(self.controlArea)
        box.layout().addWidget(self.report_button)
        self.report_button.setMinimumWidth(180)
        gui.rubber(box)
        commit = gui.button(box, self, "Send", callback=self.apply,
                            default=True)
        commit.setMinimumWidth(180)

    def setCurrentIndex(self, index):
        index = min(index, len(self.featuremodel) - 1)
        self.currentIndex = index
        if index >= 0:
            itemmodels.select_row(self.featureview, index)
            desc = self.featuremodel[min(index, len(self.featuremodel) - 1)]
            editor = self.editors[type(desc)]
            self.editorstack.setCurrentWidget(editor)
            editor.setEditorData(desc, self.data.domain if self.data else None)
        self.editorstack.setEnabled(index >= 0)
        self.duplicateaction.setEnabled(index >= 0)
        self.removebutton.setEnabled(index >= 0)

    def _on_selectedVariableChanged(self, selected, *_):
        index = selected_row(self.featureview)
        if index is not None:
            self.setCurrentIndex(index)
        else:
            self.setCurrentIndex(-1)

    def _on_modified(self):
        if self.currentIndex >= 0:
            editor = self.editorstack.currentWidget()
            self.featuremodel[self.currentIndex] = editor.editorData()
            self.descriptors = list(self.featuremodel)

    def setDescriptors(self, descriptors):
        """
        Set a list of variable descriptors to edit.
        """
        self.descriptors = descriptors
        self.featuremodel[:] = list(self.descriptors)

    @check_sql_input
    def setData(self, data=None):
        """Set the input dataset."""
        self.closeContext()

        self.data = data

        if self.data is not None:
            descriptors = list(self.descriptors)
            currindex = self.currentIndex
            self.descriptors = []
            self.currentIndex = -1
            self.openContext(data)

            if descriptors != self.descriptors or \
                    self.currentIndex != currindex:
                # disconnect from the selection model while reseting the model
                selmodel = self.featureview.selectionModel()
                selmodel.selectionChanged.disconnect(
                    self._on_selectedVariableChanged)

                self.featuremodel[:] = list(self.descriptors)
                self.setCurrentIndex(self.currentIndex)

                selmodel.selectionChanged.connect(
                    self._on_selectedVariableChanged)

        self.editorstack.setEnabled(self.currentIndex >= 0)

    def handleNewSignals(self):
        if self.data is not None:
            self.apply()
        else:
            self.send("Data", None)

    def addFeature(self, descriptor):
        self.featuremodel.append(descriptor)
        self.setCurrentIndex(len(self.featuremodel) - 1)
        editor = self.editorstack.currentWidget()
        editor.nameedit.setFocus()
        editor.nameedit.selectAll()

    def removeFeature(self, index):
        del self.featuremodel[index]
        index = selected_row(self.featureview)
        if index is not None:
            self.setCurrentIndex(index)
        elif index is None and len(self.featuremodel) > 0:
            # Deleting the last item clears selection
            self.setCurrentIndex(len(self.featuremodel) - 1)

    def removeSelectedFeature(self):
        if self.currentIndex >= 0:
            self.removeFeature(self.currentIndex)

    def duplicateFeature(self):
        desc = self.featuremodel[self.currentIndex]
        self.addFeature(copy.deepcopy(desc))

    def check_attrs_values(self, attr, data):
        for i in range(len(data)):
            for var in attr:
                if not math.isnan(data[i, var]) \
                        and int(data[i, var]) >= len(var.values):
                    return var.name
        return None

    def _validate_descriptors(self, desc):

        def validate(source):
            try:
                return validate_exp(ast.parse(source, mode="eval"))
            except Exception:
                return False

        final = []
        invalid = []
        for d in desc:
            if validate(d.expression):
                final.append(d)
            else:
                final.append(d._replace(expression=""))
                invalid.append(d)

        if invalid:
            self.Error.invalid_expressions(", ".join(s.name for s in invalid))

        return final

    def apply(self):
        self.Error.clear()

        if self.data is None:
            return

        desc = list(self.featuremodel)
        desc = self._validate_descriptors(desc)
        source_vars = tuple(self.data.domain) + self.data.domain.metas
        new_variables = construct_variables(desc, source_vars)

        attrs = [var for var in new_variables if var.is_primitive()]
        metas = [var for var in new_variables if not var.is_primitive()]
        new_domain = Orange.data.Domain(
            self.data.domain.attributes + tuple(attrs),
            self.data.domain.class_vars,
            metas=self.data.domain.metas + tuple(metas)
        )

        try:
            data = Orange.data.Table(new_domain, self.data)
        except Exception as err:
            log = logging.getLogger(__name__)
            log.error("", exc_info=True)
            self.error("".join(format_exception_only(type(err), err)).rstrip())
            return
        disc_attrs_not_ok = self.check_attrs_values(
            [var for var in attrs if var.is_discrete], data)
        if disc_attrs_not_ok:
            self.Error.more_values_needed(disc_attrs_not_ok)
            return

        self.send("Data", data)

    def send_report(self):
        items = OrderedDict()
        for feature in self.featuremodel:
            if isinstance(feature, DiscreteDescriptor):
                items[feature.name] = "{} (discrete with values {}{})".format(
                    feature.expression, feature.values,
                    "; ordered" * feature.ordered)
            elif isinstance(feature, ContinuousDescriptor):
                items[feature.name] = "{} (numeric)".format(feature.expression)
            else:
                items[feature.name] = "{} (text)".format(feature.expression)
        self.report_items(
            report.plural("Constructed feature{s}", len(items)), items)
Exemplo n.º 45
0
class OWGenotypeDistances(widget.OWWidget):
    name = "Expression Profile Distances"
    description = ("Compute distances between expression profiles of "
                   "different experimental factors.")
    icon = "../widgets/icons/GenotypeDistances.svg"
    priority = 1050

    inputs = [("Data", Orange.data.Table, "set_data")]
    outputs = [("Distances", Orange.misc.DistMatrix),
               ("Sorted Data", Orange.data.Table)]

    settingsHandler = SetContextHandler()

    separate_keys = settings.ContextSetting({})
    relevant_keys = settings.ContextSetting({})

    distance_measure = settings.Setting(0)
    auto_commit = settings.Setting(False)

    DISTANCE_FUNCTIONS = [
        ("Distance from Pearson correlation", dist_pcorr),
        ("Euclidean distance", dist_eucl),
        ("Distance from Spearman correlation", dist_spearman)
    ]

    def __init__(self, parent=None):
        super().__init__(self, parent)

        self.data = None
        self.partitions = []
        self.matrix = None
        self.split_groups = []
        self._disable_updates = False

        ########
        # GUI
        ########

        box = gui.widgetBox(self.controlArea, "Input")

        self.info_box = gui.widgetLabel(box, "No data on input\n")

        box = gui.widgetBox(self.controlArea, "Separate By",
                            addSpace=True)

        self.separate_view = QListView(
            selectionMode=QListView.MultiSelection
        )
        box.layout().addWidget(self.separate_view)

        box = gui.widgetBox(self.controlArea, "Sort By",
                            addSpace=True)
        self.relevant_view = QListView(
            selectionMode=QListView.MultiSelection)

        box.layout().addWidget(self.relevant_view)

        self.distance_view = gui.comboBox(
            self.controlArea, self, "distance_measure",
            box="Distance Measure",
            items=[name for name, _ in self.DISTANCE_FUNCTIONS])

        gui.rubber(self.controlArea)

        gui.auto_commit(self.controlArea, self, "auto_commit", "Commit")
        self.groups_box = gui.widgetBox(self.mainArea, "Groups")
        self.groups_scroll_area = QScrollArea()
        self.groups_box.layout().addWidget(self.groups_scroll_area)

    def sizeHint(self):
        return QSize(800, 600)

    def clear(self):
        self.data = None
        self.partitions = []
        self.split_groups = []
        self.matrix = None

    def get_suitable_keys(self, data):
        """Return suitable attr label keys from the data where the key has at least
        two unique values in the data.

        """
        attrs = [attr.attributes.items() for attr in data.domain.attributes]
        attrs = reduce(operator.iadd, attrs, [])
        # in case someone put non string values in attributes dict
        attrs = [(str(key), str(value)) for key, value in attrs]
        attrs = set(attrs)
        values = defaultdict(set)
        for key, value in attrs:
            values[key].add(value)
        keys = [key for key in values if len(values[key]) > 1]
        return keys

    def set_data(self, data=None):
        """Set the input data table.
        """
        self.closeContext()
        self.clear()
        self.error(0)
        self.warning(0)
        if data and not self.get_suitable_keys(data):
            self.error(0, "Data has no suitable column labels.")
            data = None

        self.data = data

        if data:
            self.info_box.setText("{0} genes\n{1} experiments"
                                  .format(len(data), len(data.domain)))
            self.update_control()
            self.split_data()
        else:
            self.separate_view.setModel(itemmodels.PyListModel([]))
            self.relevant_view.setModel(itemmodels.PyListModel([]))
            self.groups_scroll_area.setWidget(QWidget())
            self.info_box.setText("No data on input.\n")
        self.commit()

    def update_control(self):
        """Update the control area of the widget. Populate the list
        views with keys from attribute labels.
        """
        keys = self.get_suitable_keys(self.data)

        model = itemmodels.PyListModel(keys)
        self.separate_view.setModel(model)
        self.separate_view.selectionModel().selectionChanged.connect(
            self.on_separate_key_changed)

        model = itemmodels.PyListModel(keys)
        self.relevant_view.setModel(model)
        self.relevant_view.selectionModel().selectionChanged.connect(
            self.on_relevant_key_changed)

        self.openContext(keys)

        # Get the selected keys from the open context
        separate_keys = self.separate_keys
        relevant_keys = self.relevant_keys

        def select(model, selection_model, selected_items):
            all_items = list(model)
            try:
                indices = [all_items.index(item) for item in selected_items]
            except:
                indices = []
            selection = QItemSelection()
            for ind in indices:
                index = model.index(ind)
                selection.select(index, index)

            selection_model.select(selection, QItemSelectionModel.Select)

        self._disable_updates = True
        try:
            select(self.relevant_view.model(),
                   self.relevant_view.selectionModel(),
                   relevant_keys)

            select(self.separate_view.model(),
                   self.separate_view.selectionModel(),
                   separate_keys)
        finally:
            self._disable_updates = False

    def on_separate_key_changed(self, *args):
        if not self._disable_updates:
            self.separate_keys = self.selected_separeate_by_keys()
            self.split_data()

    def on_relevant_key_changed(self, *args):
        if not self._disable_updates:
            self.relevant_keys = self.selected_relevant_keys()
            self.split_data()

    def selected_separeate_by_keys(self):
        """Return the currently selected separate by keys
        """
        rows = self.separate_view.selectionModel().selectedRows()
        rows = sorted([idx.row() for idx in rows])
        keys = [self.separate_view.model()[row] for row in rows]
        return keys

    def selected_relevant_keys(self):
        """Return the currently selected relevant keys
        """
        rows = self.relevant_view.selectionModel().selectedRows()
        rows = sorted([idx.row() for idx in rows])
        keys = [self.relevant_view.model()[row] for row in rows]
        return keys

    def split_data(self):
        """Split the data and update the Groups widget
        """
        separate_keys = self.selected_separeate_by_keys()
        relevant_keys = self.selected_relevant_keys()

        self.warning(0)
        if not separate_keys:
            self.warning(0, "No separate by column selected.")

        partitions, uniquepos = separate_by(
            self.data, separate_keys, consider=relevant_keys)
        partitions = partitions.items()

        all_values = defaultdict(set)
        for a in [at.attributes for at in self.data.domain.attributes]:
            for k, v in a.items():
                all_values[k].add(v)

        # sort groups
        pkeys = [key for key, _ in partitions]
        types = [data_type([a[i] for a in pkeys])
                 for i in range(len(pkeys[0]))]

        partitions = sorted(partitions, key=lambda x:
                    tuple(types[i](v) for i,v in enumerate(x[0])))

        split_groups = []

        # Collect relevant key value pairs for all columns
        relevant_items = None

        for keys, indices in partitions:
            if relevant_items == None:
                relevant_items = [defaultdict(set) for _ in indices]
            for i, ind in enumerate(indices):
                if ind is not None:
                    attr = self.data.domain[ind]
                    for key in relevant_keys:
                        relevant_items[i][key].add(attr.attributes[key])

        #those with different values between rows are not relevant
        for d in relevant_items:
            for k, s in list(d.items()):
                if len(s) > 1:
                    del d[k]
                else:
                    d[k] = s.pop()

        def get_attr(attr_index, i):
            if attr_index is None:
                attr = Orange.data.ContinuousVariable(next(missing_name_gen), 
                    compute_value=lambda x: None)
                attr.attributes.update(relevant_items[i])
                return attr
            else:
                return self.data.domain[attr_index]

        for keys, indices in partitions:
            attrs = [get_attr(attr_index, i)
                     for i, attr_index in enumerate(indices)]
            for attr in attrs:
                attr.attributes.update(zip(separate_keys, keys))
            domain = Orange.data.Domain(attrs, [], self.data.domain.metas)
            split_groups.append((keys, domain))

        self.set_groups(separate_keys, split_groups, relevant_keys,
                        relevant_items, all_values, uniquepos)

        self.partitions = partitions
        self.split_groups = split_groups

        self.commit()

    def set_groups(self, keys, groups, relevant_keys, relevant_items,
                   all_values, uniquepos):
        """Set the current data groups and update the Group widget
        """
        layout = QVBoxLayout()
        header_widths = []
        header_views = []
        palette = self.palette()
        all_values = all_values.keys()

        def for_print(rd):
            attrs = []
            for d in rd:
                attr = Orange.data.ContinuousVariable(next(inactive_name_gen))
                attr.attributes.update(d)
                attrs.append(attr)
            return Orange.data.Domain(attrs, None)

        for separatev, domain in [(None, for_print(relevant_items))] + groups:
            label = None
            if separatev is not None:
                ann_vals = " <b>|</b> ".join(["<b>{0}</b> = {1}".format(key,val) \
                     for key, val in zip(keys, separatev)])
                label = QLabel(ann_vals)

            model = QStandardItemModel()
            for i, attr in enumerate(domain.attributes):
                item = QStandardItem()
                if separatev is not None:
                    isunique = uniquepos[separatev][i]
                else:
                    isunique = all(a[i] for a in uniquepos.values())

                if str(attr.name).startswith("!!missing "):  # TODO: Change this to not depend on name
                    header_text = ["{0}={1}".format(key, attr.attributes.get(key, "?")) \
                                   for key in all_values if key not in relevant_items[i]]
                    header_text = "\n".join(header_text) if header_text else "Empty"
                    item.setData(header_text, Qt.DisplayRole)
                    item.setFlags(Qt.NoItemFlags)
                    item.setData(QColor(Qt.red), Qt.ForegroundRole)
                    item.setData(palette.color(QPalette.Disabled, QPalette.Window), Qt.BackgroundRole)
                    item.setData("Missing feature.", Qt.ToolTipRole)
                elif str(attr.name).startswith("!!inactive "):
                    header_text = ["{0}={1}".format(key, attr.attributes.get(key, "?")) \
                                   for key in all_values if key in relevant_items[i]]
                    header_text = "\n".join(header_text) if header_text else "No descriptor"
                    item.setData(header_text, Qt.DisplayRole)
                    item.setData(palette.color(QPalette.Disabled, QPalette.Window), Qt.BackgroundRole)
                else:
                    header_text = ["{0}={1}".format(key, attr.attributes.get(key, "?")) \
                                   for key in all_values if key not in relevant_items[i]]
                    header_text = "\n".join(header_text) if header_text else "Empty"
                    item.setData(header_text, Qt.DisplayRole)
                    item.setData(attr.name, Qt.ToolTipRole)

                if not isunique:
                    item.setData(QColor(Qt.red), Qt.ForegroundRole)

                model.setHorizontalHeaderItem(i, item)
            attr_count = len(domain.attributes)
            view = MyHeaderView(Qt.Horizontal)
            view.setResizeMode(QHeaderView.Fixed)
            view.setModel(model)
            hint = view.sizeHint()
            view.setMaximumHeight(hint.height())

            widths = [view.sectionSizeHint(i) for i in range(attr_count)]
            header_widths.append(widths)
            header_views.append(view)

            if label:
                layout.addWidget(label)
            layout.addWidget(view)
            layout.addSpacing(8)

        # Make all header sections the same width
        width_sum = 0
        max_header_count = max([h.count() for h in header_views])
        for i in range(max_header_count):
            max_width = max([w[i] for w in header_widths if i < len(w)] or [0])
            for view in header_views:
                if i < view.count():
                    view.resizeSection(i, max_width)
            width_sum += max_width + 2

        for h in header_views:
            h.setMinimumWidth(h.length() + 4)

        widget = QWidget()
        widget.setLayout(layout)
        widget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum)
        layout.activate()

        max_width = max(h.length() for h in header_views) + 20

        left, _, right, _ = self.getContentsMargins()
        widget.setMinimumWidth(width_sum)
        widget.setMinimumWidth(max_width + left + right)
        self.groups_scroll_area.setWidget(widget)

    def compute_distances(self, separate_keys, partitions, data):
        """Compute the distances between genotypes.
        """
        if separate_keys and partitions:
            self.progressBarInit()
#             matrix = Orange.misc.DistMatrix(len(partitions))
            matrix = numpy.zeros((len(partitions), len(partitions)))

            profiles = [linearize(data, indices) for _, indices in partitions]
            dist_func = self.DISTANCE_FUNCTIONS[self.distance_measure][1]
#             from Orange.utils import progress_bar_milestones
            count = (len(profiles) * len(profiles) - 1) / 2
#             milestones = progress_bar_milestones(count)
            iter_count = 0
            for i in range(len(profiles)):
                for j in range(i + 1, len(profiles)):
                    matrix[i, j] = dist_func(profiles[i], profiles[j])
                    matrix[j, i] = matrix[i, j]
                    iter_count += 1
#                     if iter_count in milestones:
                    self.progressBarSet(100.0 * iter_count / count)
            self.progressBarFinished()

            items = [["{0}={1}".format(key, value)
                      for key, value in zip(separate_keys, values)]
                     for values, _ in partitions]
            items = [" | ".join(item) for item in items]
#             matrix.setattr("items", items)
            matrix = Orange.misc.DistMatrix(matrix)
        else:
            matrix = None

        self.matrix = matrix

    def commit(self):
        separate_keys = self.selected_separeate_by_keys()
        self.compute_distances(separate_keys,
                               self.partitions,
                               self.data)
        if self.split_groups:
            all_attrs = []
            for group, domain in self.split_groups:
                attrs = []
                group_name = " | ".join("{0}={1}".format(*item) for item in
                                        zip(separate_keys, group))
                for attr in domain.attributes:
                    newattr = clone_attr(attr)
                    newattr.attributes["<GENOTYPE GROUP>"] = group_name # Need a better way to pass the groups to downstream widgets.
                    attrs.append(newattr)

                all_attrs.extend(attrs)

            domain = Orange.data.Domain(all_attrs, self.data.domain.class_vars,
                                        self.data.domain.metas)
            data = Orange.data.Table(domain, self.data)
        else:
            data = None
        self.send("Sorted Data", data)
        self.send("Distances", self.matrix)

    def send_report(self):
        self.report_items((
            ("Separate By", ", ".join(self.selected_separeate_by_keys())),
            ("Sort By", ", ".join(self.selected_relevant_keys())),
            ("Distance Measure", self.DISTANCE_FUNCTIONS[self.distance_measure][0])
        ))
        layout = self.groups_scroll_area.widget().layout()
        html = "<table>"
        for i in range(layout.count()):
            item = layout.itemAt(i)
            if isinstance(item, QSpacerItem):
                html += "<tr><td></td></tr>"
            elif isinstance(item, QWidgetItem):
                hor = item.widget()
                if isinstance(hor, QLabel):
                    label = hor.text()
                    html += "<tr><td><b>%s</b></td></tr>" % label
                elif isinstance(hor, QHeaderView):
                    model = hor.model()
                    content = (model.horizontalHeaderItem(col) for col in range(model.columnCount()))
                    content = (item.text().replace('\n', "<br/>") for item in content)
                    html += "<tr>" + ''.join("<td>{}</td>".format(item) for item in content) + "</tr>"
        html += "</table>"
        self.report_raw("Groups", html)
Exemplo n.º 46
0
class DiscreteVariableEditor(VariableEditor):
    """An editor widget for editing a discrete variable.

    Extends the :class:`VariableEditor` to enable editing of
    variables values.

    """
    def setup_gui(self):
        layout = QVBoxLayout()
        self.setLayout(layout)

        self.main_form = QFormLayout()
        self.main_form.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
        layout.addLayout(self.main_form)

        self._setup_gui_name()
        self._setup_gui_values()
        self._setup_gui_labels()

    def _setup_gui_values(self):
        self.values_edit = QListView()
        self.values_edit.setEditTriggers(QTreeView.CurrentChanged)
        self.values_model = itemmodels.PyListModel(flags=Qt.ItemIsSelectable | \
                                        Qt.ItemIsEnabled | Qt.ItemIsEditable)
        self.values_edit.setModel(self.values_model)

        self.values_model.dataChanged.connect(self.on_values_changed)
        self.main_form.addRow("Values:", self.values_edit)

    def set_data(self, var):
        """Set the variable to edit
        """
        VariableEditor.set_data(self, var)
        self.values_model[:] = list(var.values) if var is not None else []

    def get_data(self):
        """Retrieve the modified variable
        """
        name = str(self.name_edit.text())
        labels = self.labels_model.get_dict()
        values = map(str, self.values_model)

        if self.var is not None and not self.is_same():
            var = type(self.var)(name, values=values)
            var.attributes.update(labels)
            self.var = var
        else:
            var = self.var

        return var

    def is_same(self):
        """Is the current model state the same as the input.
        """
        values = list(map(str, self.values_model))
        return (VariableEditor.is_same(self) and self.var is not None and
                self.var.values == values)

    def clear(self):
        """Clear the model state.
        """
        VariableEditor.clear(self)
        self.values_model.wrap([])

    @Slot()
    def on_values_changed(self):
        self.maybe_commit()
Exemplo n.º 47
0
class OWPythonScript(widget.OWWidget):
    name = "Python Script"
    description = "Write a Python script and run it on input data or models."
    icon = "icons/PythonScript.svg"
    priority = 3150

    inputs = [("in_data", Orange.data.Table, "setExampleTable",
               widget.Default),
#               ("in_distance", Orange.misc.SymMatrix, "setDistanceMatrix",
#                widget.Default),
              ("in_learner", Learner, "setLearner",
               widget.Default),
              ("in_classifier", Model, "setClassifier",
               widget.Default),
              ("in_object", object, "setObject")]

    outputs = [("out_data", Orange.data.Table, ),
#                ("out_distance", Orange.misc.SymMatrix, ),
               ("out_learner", Learner, ),
               ("out_classifier", Model, widget.Dynamic),
               ("out_object", object, widget.Dynamic)]

    libraryListSource = \
        Setting([Script("Hello world", "print('Hello world')\n")])
    currentScriptIndex = Setting(0)
    splitterState = Setting(None)
    auto_execute = Setting(False)

    def __init__(self):
        super().__init__()

        self.in_data = None
        self.in_distance = None
        self.in_learner = None
        self.in_classifier = None
        self.in_object = None
        self.auto_execute = False

        for s in self.libraryListSource:
            s.flags = 0

        self._cachedDocuments = {}

        self.infoBox = gui.vBox(self.controlArea, 'Info')
        gui.label(
            self.infoBox, self,
            "<p>Execute python script.</p><p>Input variables:<ul><li> " + \
            "<li>".join(t.name for t in self.inputs) + \
            "</ul></p><p>Output variables:<ul><li>" + \
            "<li>".join(t.name for t in self.outputs) + \
            "</ul></p>"
        )

        self.libraryList = itemmodels.PyListModel(
            [], self,
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable)

        self.libraryList.wrap(self.libraryListSource)

        self.controlBox = gui.vBox(self.controlArea, 'Library')
        self.controlBox.layout().setSpacing(1)

        self.libraryView = QListView(
            editTriggers=QListView.DoubleClicked |
                         QListView.EditKeyPressed,
            sizePolicy=QSizePolicy(QSizePolicy.Ignored,
                                   QSizePolicy.Preferred)
        )
        self.libraryView.setItemDelegate(ScriptItemDelegate(self))
        self.libraryView.setModel(self.libraryList)

        self.libraryView.selectionModel().selectionChanged.connect(
            self.onSelectedScriptChanged
        )
        self.controlBox.layout().addWidget(self.libraryView)

        w = itemmodels.ModelActionsWidget()

        self.addNewScriptAction = action = QAction("+", self)
        action.setToolTip("Add a new script to the library")
        action.triggered.connect(self.onAddScript)
        w.addAction(action)

        action = QAction(unicodedata.lookup("MINUS SIGN"), self)
        action.setToolTip("Remove script from library")
        action.triggered.connect(self.onRemoveScript)
        w.addAction(action)

        action = QAction("Update", self)
        action.setToolTip("Save changes in the editor to library")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.triggered.connect(self.commitChangesToLibrary)
        w.addAction(action)

        action = QAction("More", self, toolTip="More actions")

        new_from_file = QAction("Import Script from File", self)
        save_to_file = QAction("Save Selected Script to File", self)
        save_to_file.setShortcut(QKeySequence(QKeySequence.SaveAs))

        new_from_file.triggered.connect(self.onAddScriptFromFile)
        save_to_file.triggered.connect(self.saveScript)

        menu = QMenu(w)
        menu.addAction(new_from_file)
        menu.addAction(save_to_file)
        action.setMenu(menu)
        button = w.addAction(action)
        button.setPopupMode(QToolButton.InstantPopup)

        w.layout().setSpacing(1)

        self.controlBox.layout().addWidget(w)

        self.execute_button = gui.auto_commit(
            self.controlArea, self, "auto_execute", "Execute",
            auto_label="Auto Execute")

        self.splitCanvas = QSplitter(Qt.Vertical, self.mainArea)
        self.mainArea.layout().addWidget(self.splitCanvas)

        self.defaultFont = defaultFont = \
            "Monaco" if sys.platform == "darwin" else "Courier"

        self.textBox = gui.vBox(self, 'Python Script')
        self.splitCanvas.addWidget(self.textBox)
        self.text = PythonScriptEditor(self)
        self.textBox.layout().addWidget(self.text)

        self.textBox.setAlignment(Qt.AlignVCenter)
        self.text.setTabStopWidth(4)

        self.text.modificationChanged[bool].connect(self.onModificationChanged)

        self.saveAction = action = QAction("&Save", self.text)
        action.setToolTip("Save script to file")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        action.triggered.connect(self.saveScript)

        self.consoleBox = gui.vBox(self, 'Console')
        self.splitCanvas.addWidget(self.consoleBox)
        self.console = PythonConsole({}, self)
        self.consoleBox.layout().addWidget(self.console)
        self.console.document().setDefaultFont(QFont(defaultFont))
        self.consoleBox.setAlignment(Qt.AlignBottom)
        self.console.setTabStopWidth(4)

        select_row(self.libraryView, self.currentScriptIndex)

        self.splitCanvas.setSizes([2, 1])
        if self.splitterState is not None:
            self.splitCanvas.restoreState(QByteArray(self.splitterState))

        self.splitCanvas.splitterMoved[int, int].connect(self.onSpliterMoved)
        self.controlArea.layout().addStretch(1)
        self.resize(800, 600)

    def setExampleTable(self, et):
        self.in_data = et

    def setDistanceMatrix(self, dm):
        self.in_distance = dm

    def setLearner(self, learner):
        self.in_learner = learner

    def setClassifier(self, classifier):
        self.in_classifier = classifier

    def setObject(self, obj):
        self.in_object = obj

    def handleNewSignals(self):
        self.unconditional_commit()

    def selectedScriptIndex(self):
        rows = self.libraryView.selectionModel().selectedRows()
        if rows:
            return  [i.row() for i in rows][0]
        else:
            return None

    def setSelectedScript(self, index):
        select_row(self.libraryView, index)

    def onAddScript(self, *args):
        self.libraryList.append(Script("New script", "", 0))
        self.setSelectedScript(len(self.libraryList) - 1)

    def onAddScriptFromFile(self, *args):
        filename, _ = QFileDialog.getOpenFileName(
            self, 'Open Python Script',
            os.path.expanduser("~/"),
            'Python files (*.py)\nAll files(*.*)'
        )
        if filename:
            name = os.path.basename(filename)
            # TODO: use `tokenize.detect_encoding`
            with open(filename, encoding="utf-8") as f:
                contents = f.read()
            self.libraryList.append(Script(name, contents, 0, filename))
            self.setSelectedScript(len(self.libraryList) - 1)

    def onRemoveScript(self, *args):
        index = self.selectedScriptIndex()
        if index is not None:
            del self.libraryList[index]
            select_row(self.libraryView, max(index - 1, 0))

    def onSaveScriptToFile(self, *args):
        index = self.selectedScriptIndex()
        if index is not None:
            self.saveScript()

    def onSelectedScriptChanged(self, selected, deselected):
        index = [i.row() for i in selected.indexes()]
        if index:
            current = index[0]
            if current >= len(self.libraryList):
                self.addNewScriptAction.trigger()
                return

            self.text.setDocument(self.documentForScript(current))
            self.currentScriptIndex = current

    def documentForScript(self, script=0):
        if type(script) != Script:
            script = self.libraryList[script]

        if script not in self._cachedDocuments:
            doc = QTextDocument(self)
            doc.setDocumentLayout(QPlainTextDocumentLayout(doc))
            doc.setPlainText(script.script)
            doc.setDefaultFont(QFont(self.defaultFont))
            doc.highlighter = PythonSyntaxHighlighter(doc)
            doc.modificationChanged[bool].connect(self.onModificationChanged)
            doc.setModified(False)
            self._cachedDocuments[script] = doc
        return self._cachedDocuments[script]

    def commitChangesToLibrary(self, *args):
        index = self.selectedScriptIndex()
        if index is not None:
            self.libraryList[index].script = self.text.toPlainText()
            self.text.document().setModified(False)
            self.libraryList.emitDataChanged(index)

    def onModificationChanged(self, modified):
        index = self.selectedScriptIndex()
        if index is not None:
            self.libraryList[index].flags = Script.Modified if modified else 0
            self.libraryList.emitDataChanged(index)

    def onSpliterMoved(self, pos, ind):
        self.splitterState = bytes(self.splitCanvas.saveState())

    def updateSelecetdScriptState(self):
        index = self.selectedScriptIndex()
        if index is not None:
            script = self.libraryList[index]
            self.libraryList[index] = Script(script.name,
                                             self.text.toPlainText(),
                                             0)

    def saveScript(self):
        index = self.selectedScriptIndex()
        if index is not None:
            script = self.libraryList[index]
            filename = script.filename
        else:
            filename = os.path.expanduser("~/")

        filename, _ = QFileDialog.getSaveFileName(
            self, 'Save Python Script',
            filename,
            'Python files (*.py)\nAll files(*.*)'
        )

        if filename:
            fn = ""
            head, tail = os.path.splitext(filename)
            if not tail:
                fn = head + ".py"
            else:
                fn = filename

            f = open(fn, 'w')
            f.write(self.text.toPlainText())
            f.close()

    def initial_locals_state(self):
        d = dict([(i.name, getattr(self, i.name, None)) for i in self.inputs])
        d.update(dict([(o.name, None) for o in self.outputs]))
        return d

    def commit(self):
        self._script = str(self.text.toPlainText())
        lcls = self.initial_locals_state()
        lcls["_script"] = str(self.text.toPlainText())
        self.console.updateLocals(lcls)
        self.console.write("\nRunning script:\n")
        self.console.push("exec(_script)")
        self.console.new_prompt(sys.ps1)
        for out in self.outputs:
            signal = out.name
            self.send(signal, self.console.locals.get(signal, None))
Exemplo n.º 48
0
class OWPythonScript(OWWidget):
    name = "Python Script"
    description = "Write a Python script and run it on input data or models."
    icon = "icons/PythonScript.svg"
    priority = 3150
    keywords = ["file", "program"]

    class Inputs:
        data = Input("Data",
                     Table,
                     replaces=["in_data"],
                     default=True,
                     multiple=True)
        learner = Input("Learner",
                        Learner,
                        replaces=["in_learner"],
                        default=True,
                        multiple=True)
        classifier = Input("Classifier",
                           Model,
                           replaces=["in_classifier"],
                           default=True,
                           multiple=True)
        object = Input("Object",
                       object,
                       replaces=["in_object"],
                       default=False,
                       multiple=True)

    class Outputs:
        data = Output("Data", Table, replaces=["out_data"])
        learner = Output("Learner", Learner, replaces=["out_learner"])
        classifier = Output("Classifier", Model, replaces=["out_classifier"])
        object = Output("Object", object, replaces=["out_object"])

    signal_names = ("data", "learner", "classifier", "object")

    settings_version = 2
    scriptLibrary: 'List[_ScriptData]' = Setting([{
        "name": "Hello world",
        "script": "print('Hello world')\n",
        "filename": None
    }])
    currentScriptIndex = Setting(0)
    scriptText: Optional[str] = Setting(None, schema_only=True)
    splitterState: Optional[bytes] = Setting(None)

    # Widgets in the same schema share namespace through a dictionary whose
    # key is self.signalManager. ales-erjavec expressed concern (and I fully
    # agree!) about widget being aware of the outside world. I am leaving this
    # anyway. If this causes any problems in the future, replace this with
    # shared_namespaces = {} and thus use a common namespace for all instances
    # of # PythonScript even if they are in different schemata.
    shared_namespaces = weakref.WeakKeyDictionary()

    class Error(OWWidget.Error):
        pass

    def __init__(self):
        super().__init__()
        self.libraryListSource = []

        for name in self.signal_names:
            setattr(self, name, {})

        self._cachedDocuments = {}

        self.infoBox = gui.vBox(self.controlArea, 'Info')
        gui.label(
            self.infoBox, self,
            "<p>Execute python script.</p><p>Input variables:<ul><li> " +
            "<li>".join(map("in_{0}, in_{0}s".format, self.signal_names)) +
            "</ul></p><p>Output variables:<ul><li>" +
            "<li>".join(map("out_{0}".format, self.signal_names)) +
            "</ul></p>")

        self.libraryList = itemmodels.PyListModel(
            [],
            self,
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable)

        self.libraryList.wrap(self.libraryListSource)

        self.controlBox = gui.vBox(self.controlArea, 'Library')
        self.controlBox.layout().setSpacing(1)

        self.libraryView = QListView(
            editTriggers=QListView.DoubleClicked | QListView.EditKeyPressed,
            sizePolicy=QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred))
        self.libraryView.setItemDelegate(ScriptItemDelegate(self))
        self.libraryView.setModel(self.libraryList)

        self.libraryView.selectionModel().selectionChanged.connect(
            self.onSelectedScriptChanged)
        self.controlBox.layout().addWidget(self.libraryView)

        w = itemmodels.ModelActionsWidget()

        self.addNewScriptAction = action = QAction("+", self)
        action.setToolTip("Add a new script to the library")
        action.triggered.connect(self.onAddScript)
        w.addAction(action)

        action = QAction(unicodedata.lookup("MINUS SIGN"), self)
        action.setToolTip("Remove script from library")
        action.triggered.connect(self.onRemoveScript)
        w.addAction(action)

        action = QAction("Update", self)
        action.setToolTip("Save changes in the editor to library")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.triggered.connect(self.commitChangesToLibrary)
        w.addAction(action)

        action = QAction("More", self, toolTip="More actions")

        new_from_file = QAction("Import Script from File", self)
        save_to_file = QAction("Save Selected Script to File", self)
        restore_saved = QAction("Undo Changes to Selected Script", self)
        save_to_file.setShortcut(QKeySequence(QKeySequence.SaveAs))

        new_from_file.triggered.connect(self.onAddScriptFromFile)
        save_to_file.triggered.connect(self.saveScript)
        restore_saved.triggered.connect(self.restoreSaved)

        menu = QMenu(w)
        menu.addAction(new_from_file)
        menu.addAction(save_to_file)
        menu.addAction(restore_saved)
        action.setMenu(menu)
        button = w.addAction(action)
        button.setPopupMode(QToolButton.InstantPopup)

        w.layout().setSpacing(1)

        self.controlBox.layout().addWidget(w)

        self.execute_button = gui.button(self.controlArea,
                                         self,
                                         'Run',
                                         callback=self.commit)

        run = QAction("Run script",
                      self,
                      triggered=self.commit,
                      shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_R))
        self.addAction(run)

        self.splitCanvas = QSplitter(Qt.Vertical, self.mainArea)
        self.mainArea.layout().addWidget(self.splitCanvas)

        self.defaultFont = defaultFont = \
            "Monaco" if sys.platform == "darwin" else "Courier"

        self.textBox = gui.vBox(self, 'Python Script')
        self.splitCanvas.addWidget(self.textBox)
        self.text = PythonScriptEditor(self)
        self.textBox.layout().addWidget(self.text)

        self.textBox.setAlignment(Qt.AlignVCenter)
        self.text.setTabStopWidth(4)

        self.text.modificationChanged[bool].connect(self.onModificationChanged)

        self.saveAction = action = QAction("&Save", self.text)
        action.setToolTip("Save script to file")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        action.triggered.connect(self.saveScript)

        self.consoleBox = gui.vBox(self, 'Console')
        self.splitCanvas.addWidget(self.consoleBox)
        self.console = PythonConsole({}, self)
        self.consoleBox.layout().addWidget(self.console)
        self.console.document().setDefaultFont(QFont(defaultFont))
        self.consoleBox.setAlignment(Qt.AlignBottom)
        self.console.setTabStopWidth(4)
        self.splitCanvas.setSizes([2, 1])
        self.setAcceptDrops(True)
        self.controlArea.layout().addStretch(10)

        self._restoreState()
        self.settingsAboutToBePacked.connect(self._saveState)

    def sizeHint(self) -> QSize:
        return super().sizeHint().expandedTo(QSize(800, 600))

    def _restoreState(self):
        self.libraryListSource = [
            Script.fromdict(s) for s in self.scriptLibrary
        ]
        self.libraryList.wrap(self.libraryListSource)
        select_row(self.libraryView, self.currentScriptIndex)

        if self.scriptText is not None:
            current = self.text.toPlainText()
            # do not mark scripts as modified
            if self.scriptText != current:
                self.text.document().setPlainText(self.scriptText)

        if self.splitterState is not None:
            self.splitCanvas.restoreState(QByteArray(self.splitterState))

    def _saveState(self):
        self.scriptLibrary = [s.asdict() for s in self.libraryListSource]
        self.scriptText = self.text.toPlainText()
        self.splitterState = bytes(self.splitCanvas.saveState())

    def handle_input(self, obj, sig_id, signal):
        dic = getattr(self, signal)
        if obj is None:
            if sig_id in dic.keys():
                del dic[sig_id]
        else:
            dic[sig_id] = obj

    @Inputs.data
    def set_data(self, data, sig_id):
        self.handle_input(data, sig_id, "data")

    @Inputs.learner
    def set_learner(self, data, sig_id):
        self.handle_input(data, sig_id, "learner")

    @Inputs.classifier
    def set_classifier(self, data, sig_id):
        self.handle_input(data, sig_id, "classifier")

    @Inputs.object
    def set_object(self, data, sig_id):
        self.handle_input(data, sig_id, "object")

    def handleNewSignals(self):
        self.commit()

    def selectedScriptIndex(self):
        rows = self.libraryView.selectionModel().selectedRows()
        if rows:
            return [i.row() for i in rows][0]
        else:
            return None

    def setSelectedScript(self, index):
        select_row(self.libraryView, index)

    def onAddScript(self, *_):
        self.libraryList.append(
            Script("New script", self.text.toPlainText(), 0))
        self.setSelectedScript(len(self.libraryList) - 1)

    def onAddScriptFromFile(self, *_):
        filename, _ = QFileDialog.getOpenFileName(
            self, 'Open Python Script', os.path.expanduser("~/"),
            'Python files (*.py)\nAll files(*.*)')
        if filename:
            name = os.path.basename(filename)
            # TODO: use `tokenize.detect_encoding`
            with open(filename, encoding="utf-8") as f:
                contents = f.read()
            self.libraryList.append(Script(name, contents, 0, filename))
            self.setSelectedScript(len(self.libraryList) - 1)

    def onRemoveScript(self, *_):
        index = self.selectedScriptIndex()
        if index is not None:
            del self.libraryList[index]
            select_row(self.libraryView, max(index - 1, 0))

    def onSaveScriptToFile(self, *_):
        index = self.selectedScriptIndex()
        if index is not None:
            self.saveScript()

    def onSelectedScriptChanged(self, selected, _deselected):
        index = [i.row() for i in selected.indexes()]
        if index:
            current = index[0]
            if current >= len(self.libraryList):
                self.addNewScriptAction.trigger()
                return

            self.text.setDocument(self.documentForScript(current))
            self.currentScriptIndex = current

    def documentForScript(self, script=0):
        if not isinstance(script, Script):
            script = self.libraryList[script]
        if script not in self._cachedDocuments:
            doc = QTextDocument(self)
            doc.setDocumentLayout(QPlainTextDocumentLayout(doc))
            doc.setPlainText(script.script)
            doc.setDefaultFont(QFont(self.defaultFont))
            doc.highlighter = PythonSyntaxHighlighter(doc)
            doc.modificationChanged[bool].connect(self.onModificationChanged)
            doc.setModified(False)
            self._cachedDocuments[script] = doc
        return self._cachedDocuments[script]

    def commitChangesToLibrary(self, *_):
        index = self.selectedScriptIndex()
        if index is not None:
            self.libraryList[index].script = self.text.toPlainText()
            self.text.document().setModified(False)
            self.libraryList.emitDataChanged(index)

    def onModificationChanged(self, modified):
        index = self.selectedScriptIndex()
        if index is not None:
            self.libraryList[index].flags = Script.Modified if modified else 0
            self.libraryList.emitDataChanged(index)

    def restoreSaved(self):
        index = self.selectedScriptIndex()
        if index is not None:
            self.text.document().setPlainText(self.libraryList[index].script)
            self.text.document().setModified(False)

    def saveScript(self):
        index = self.selectedScriptIndex()
        if index is not None:
            script = self.libraryList[index]
            filename = script.filename
        else:
            filename = os.path.expanduser("~/")

        filename, _ = QFileDialog.getSaveFileName(
            self, 'Save Python Script', filename,
            'Python files (*.py)\nAll files(*.*)')

        if filename:
            fn = ""
            head, tail = os.path.splitext(filename)
            if not tail:
                fn = head + ".py"
            else:
                fn = filename

            f = open(fn, 'w')
            f.write(self.text.toPlainText())
            f.close()

    def initial_locals_state(self):
        d = self.shared_namespaces.setdefault(self.signalManager, {}).copy()
        for name in self.signal_names:
            value = getattr(self, name)
            all_values = list(value.values())
            one_value = all_values[0] if len(all_values) == 1 else None
            d["in_" + name + "s"] = all_values
            d["in_" + name] = one_value
        return d

    def update_namespace(self, namespace):
        not_saved = reduce(set.union,
                           ({f"in_{name}s", f"in_{name}", f"out_{name}"}
                            for name in self.signal_names))
        self.shared_namespaces.setdefault(self.signalManager, {}).update({
            name: value
            for name, value in namespace.items() if name not in not_saved
        })

    def commit(self):
        self.Error.clear()
        lcls = self.initial_locals_state()
        lcls["_script"] = str(self.text.toPlainText())
        self.console.updateLocals(lcls)
        self.console.write("\nRunning script:\n")
        self.console.push("exec(_script)")
        self.console.new_prompt(sys.ps1)
        self.update_namespace(self.console.locals)
        for signal in self.signal_names:
            out_var = self.console.locals.get("out_" + signal)
            signal_type = getattr(self.Outputs, signal).type
            if not isinstance(out_var, signal_type) and out_var is not None:
                self.Error.add_message(
                    signal, "'{}' has to be an instance of '{}'.".format(
                        signal, signal_type.__name__))
                getattr(self.Error, signal)()
                out_var = None
            getattr(self.Outputs, signal).send(out_var)

    def dragEnterEvent(self, event):  # pylint: disable=no-self-use
        urls = event.mimeData().urls()
        if urls:
            # try reading the file as text
            c = read_file_content(urls[0].toLocalFile(), limit=1000)
            if c is not None:
                event.acceptProposedAction()

    def dropEvent(self, event):
        """Handle file drops"""
        urls = event.mimeData().urls()
        if urls:
            self.text.pasteFile(urls[0])

    @classmethod
    def migrate_settings(cls, settings, version):
        if version is not None and version < 2:
            scripts = settings.pop("libraryListSource")  # type: List[Script]
            library = [
                dict(name=s.name, script=s.script, filename=s.filename)
                for s in scripts
            ]  # type: List[_ScriptData]
            settings["scriptLibrary"] = library
Exemplo n.º 49
0
class OWImpute(OWWidget):
    name = "Impute"
    description = "Impute missing values in the data table."
    icon = "icons/Impute.svg"
    priority = 2130

    class Inputs:
        data = Input("Data", Orange.data.Table)
        learner = Input("Learner", Learner)

    class Outputs:
        data = Output("Data", Orange.data.Table)

    class Error(OWWidget.Error):
        imputation_failed = Msg("Imputation failed for '{}'")
        model_based_imputer_sparse = Msg("Model based imputer does not work for sparse data")

    settingsHandler = settings.DomainContextHandler()

    _default_method_index = settings.Setting(int(Method.Leave))  # type: int
    # Per-variable imputation state (synced in storeSpecificSettings)
    _variable_imputation_state = settings.ContextSetting({})  # type: VariableState

    autocommit = settings.Setting(True)

    want_main_area = False
    resizing_enabled = False

    def __init__(self):
        super().__init__()
        self.data = None  # type: Optional[Orange.data.Table]
        self.learner = None  # type: Optional[Learner]
        self.default_learner = SimpleTreeLearner()
        self.modified = False
        self.executor = qconcurrent.ThreadExecutor(self)
        self.__task = None

        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(10, 10, 10, 10)
        self.controlArea.layout().addLayout(main_layout)

        box = QGroupBox(title=self.tr("Default Method"), flat=False)
        box_layout = QVBoxLayout(box)
        main_layout.addWidget(box)

        button_group = QButtonGroup()
        button_group.buttonClicked[int].connect(self.set_default_method)

        for method, _ in list(METHODS.items())[1:-1]:
            imputer = self.create_imputer(method)
            button = QRadioButton(imputer.name)
            button.setChecked(method == self.default_method_index)
            button_group.addButton(button, method)
            box_layout.addWidget(button)

        self.default_button_group = button_group

        box = QGroupBox(title=self.tr("Individual Attribute Settings"),
                        flat=False)
        main_layout.addWidget(box)

        horizontal_layout = QHBoxLayout(box)
        main_layout.addWidget(box)

        self.varview = QListView(
            selectionMode=QListView.ExtendedSelection,
            uniformItemSizes=True
        )
        self.varview.setItemDelegate(DisplayFormatDelegate())
        self.varmodel = itemmodels.VariableListModel()
        self.varview.setModel(self.varmodel)
        self.varview.selectionModel().selectionChanged.connect(
            self._on_var_selection_changed
        )
        self.selection = self.varview.selectionModel()

        horizontal_layout.addWidget(self.varview)

        method_layout = QVBoxLayout()
        horizontal_layout.addLayout(method_layout)

        button_group = QButtonGroup()
        for method in Method:
            imputer = self.create_imputer(method)
            button = QRadioButton(text=imputer.name)
            button_group.addButton(button, method)
            method_layout.addWidget(button)

        self.value_combo = QComboBox(
            minimumContentsLength=8,
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLength,
            activated=self._on_value_selected
            )
        self.value_double = QDoubleSpinBox(
            editingFinished=self._on_value_selected,
            minimum=-1000., maximum=1000., singleStep=.1, decimals=3,
            )
        self.value_stack = value_stack = QStackedWidget()
        value_stack.addWidget(self.value_combo)
        value_stack.addWidget(self.value_double)
        method_layout.addWidget(value_stack)

        button_group.buttonClicked[int].connect(
            self.set_method_for_current_selection
        )

        method_layout.addStretch(2)

        reset_button = QPushButton(
                "Restore All to Default", checked=False, checkable=False,
                clicked=self.reset_variable_state, default=False,
                autoDefault=False)
        method_layout.addWidget(reset_button)

        self.variable_button_group = button_group

        box = gui.auto_commit(
            self.controlArea, self, "autocommit", "Apply",
            orientation=Qt.Horizontal,
            checkbox_label="Apply automatically")
        box.button.setFixedWidth(180)
        box.layout().insertStretch(0)

    def create_imputer(self, method, *args):
        # type: (Method, ...) -> impute.BaseImputeMethod
        if method == Method.Model:
            if self.learner is not None:
                return impute.Model(self.learner)
            else:
                return impute.Model(self.default_learner)
        elif method == Method.AsAboveSoBelow:
            assert self.default_method_index != Method.AsAboveSoBelow
            default = self.create_imputer(Method(self.default_method_index))
            m = AsDefault()
            m.method = default
            return m
        else:
            return METHODS[method](*args)

    @property
    def default_method_index(self):
        return self._default_method_index

    @default_method_index.setter
    def default_method_index(self, index):
        if self._default_method_index != index:
            assert index != Method.AsAboveSoBelow
            self._default_method_index = index
            self.default_button_group.button(index).setChecked(True)
            # update variable view
            self.update_varview()
            self._invalidate()

    def set_default_method(self, index):
        """Set the current selected default imputation method.
        """
        self.default_method_index = index

    @Inputs.data
    @check_sql_input
    def set_data(self, data):
        self.closeContext()
        self.varmodel[:] = []
        self._variable_imputation_state = {}  # type: VariableState
        self.modified = False
        self.data = data

        if data is not None:
            self.varmodel[:] = data.domain.variables
            self.openContext(data.domain)
            # restore per variable imputation state
            self._restore_state(self._variable_imputation_state)

        self.update_varview()
        self.unconditional_commit()

    @Inputs.learner
    def set_learner(self, learner):
        self.learner = learner or self.default_learner
        imputer = self.create_imputer(Method.Model)
        button = self.default_button_group.button(Method.Model)
        button.setText(imputer.name)

        variable_button = self.variable_button_group.button(Method.Model)
        variable_button.setText(imputer.name)

        if learner is not None:
            self.default_method_index = Method.Model

        self.update_varview()
        self.commit()

    def get_method_for_column(self, column_index):
        # type: (int) -> impute.BaseImputeMethod
        """
        Return the imputation method for column by its index.
        """
        assert 0 <= column_index < len(self.varmodel)
        idx = self.varmodel.index(column_index, 0)
        state = idx.data(StateRole)
        if state is None:
            state = (Method.AsAboveSoBelow, ())
        return self.create_imputer(state[0], *state[1])

    def _invalidate(self):
        self.modified = True
        if self.__task is not None:
            self.cancel()
        self.commit()

    def commit(self):
        self.cancel()
        self.warning()
        self.Error.imputation_failed.clear()
        self.Error.model_based_imputer_sparse.clear()

        if self.data is None or len(self.data) == 0 or len(self.varmodel) == 0:
            self.Outputs.data.send(self.data)
            self.modified = False
            return

        data = self.data
        impute_state = [
            (i, var, self.get_method_for_column(i))
            for i, var in enumerate(self.varmodel)
        ]
        # normalize to the effective method bypasing AsDefault
        impute_state = [
            (i, var, m.method if isinstance(m, AsDefault) else m)
            for i, var, m in impute_state
        ]

        def impute_one(method, var, data):
            # type: (impute.BaseImputeMethod, Variable, Table) -> Any
            if isinstance(method, impute.Model) and data.is_sparse():
                raise SparseNotSupported()
            elif isinstance(method, impute.DropInstances):
                return RowMask(method(data, var))
            elif not method.supports_variable(var):
                raise VariableNotSupported(var)
            else:
                return method(data, var)

        futures = []
        for _, var, method in impute_state:
            f = self.executor.submit(
                impute_one, copy.deepcopy(method), var, data)
            futures.append(f)

        w = qconcurrent.FutureSetWatcher(futures)
        w.doneAll.connect(self.__commit_finish)
        w.progressChanged.connect(self.__progress_changed)
        self.__task = Task(futures, w)
        self.progressBarInit(processEvents=False)
        self.setBlocking(True)

    @Slot()
    def __commit_finish(self):
        assert QThread.currentThread() is self.thread()
        assert self.__task is not None
        futures = self.__task.futures
        assert len(futures) == len(self.varmodel)
        assert self.data is not None

        self.__task = None
        self.setBlocking(False)
        self.progressBarFinished()

        data = self.data
        attributes = []
        class_vars = []
        drop_mask = np.zeros(len(self.data), bool)

        for i, (var, fut) in enumerate(zip(self.varmodel, futures)):
            assert fut.done()
            newvar = []
            try:
                res = fut.result()
            except SparseNotSupported:
                self.Error.model_based_imputer_sparse()
                # ?? break
            except VariableNotSupported:
                self.warning("Default method can not handle '{}'".
                             format(var.name))
            except Exception:  # pylint: disable=broad-except
                log = logging.getLogger(__name__)
                log.info("Error for %s", var, exc_info=True)
                self.Error.imputation_failed(var.name)
                attributes = class_vars = None
                break
            else:
                if isinstance(res, RowMask):
                    drop_mask |= res.mask
                    newvar = var
                else:
                    newvar = res

            if isinstance(newvar, Orange.data.Variable):
                newvar = [newvar]

            if i < len(data.domain.attributes):
                attributes.extend(newvar)
            else:
                class_vars.extend(newvar)

        if attributes is None:
            data = None
        else:
            domain = Orange.data.Domain(attributes, class_vars,
                                        data.domain.metas)
            try:
                data = self.data.from_table(domain, data[~drop_mask])
            except Exception:  # pylint: disable=broad-except
                log = logging.getLogger(__name__)
                log.info("Error", exc_info=True)
                self.Error.imputation_failed("Unknown")
                data = None

        self.Outputs.data.send(data)
        self.modified = False

    @Slot(int, int)
    def __progress_changed(self, n, d):
        assert QThread.currentThread() is self.thread()
        assert self.__task is not None
        self.progressBarSet(100. * n / d)

    def cancel(self):
        if self.__task is not None:
            task, self.__task = self.__task, None
            task.cancel()
            task.watcher.doneAll.disconnect(self.__commit_finish)
            task.watcher.progressChanged.disconnect(self.__progress_changed)
            concurrent.futures.wait(task.futures)
            task.watcher.flush()
            self.progressBarFinished()
            self.setBlocking(False)

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

    def send_report(self):
        specific = []
        for i, var in enumerate(self.varmodel):
            method = self.get_method_for_column(i)
            if not isinstance(method, AsDefault):
                specific.append("{} ({})".format(var.name, str(method)))

        default = self.create_imputer(Method.AsAboveSoBelow)
        if specific:
            self.report_items((
                ("Default method", default.name),
                ("Specific imputers", ", ".join(specific))
            ))
        else:
            self.report_items((("Method", default.name),))

    def _on_var_selection_changed(self):
        indexes = self.selection.selectedIndexes()
        defmethod = (Method.AsAboveSoBelow, ())
        methods = [index.data(StateRole) for index in indexes]
        methods = [m if m is not None else defmethod for m in methods]
        methods = set(methods)
        selected_vars = [self.varmodel[index.row()] for index in indexes]
        has_discrete = any(var.is_discrete for var in selected_vars)
        fixed_value = None
        value_stack_enabled = False
        current_value_widget = None

        if len(methods) == 1:
            method_type, parameters = methods.pop()
            for m in Method:
                if method_type == m:
                    self.variable_button_group.button(m).setChecked(True)

            if method_type == Method.Default:
                (fixed_value,) = parameters

        elif self.variable_button_group.checkedButton() is not None:
            # Uncheck the current button
            self.variable_button_group.setExclusive(False)
            self.variable_button_group.checkedButton().setChecked(False)
            self.variable_button_group.setExclusive(True)
            assert self.variable_button_group.checkedButton() is None

        # Update variable methods GUI enabled state based on selection.
        for method in Method:
            # use a default constructed imputer to query support
            imputer = self.create_imputer(method)
            enabled = all(imputer.supports_variable(var) for var in
                          selected_vars)
            button = self.variable_button_group.button(method)
            button.setEnabled(enabled)

        # Update the "Value" edit GUI.
        if not has_discrete:
            # no discrete variables -> allow mass edit for all (continuous vars)
            value_stack_enabled = True
            current_value_widget = self.value_double
        elif len(selected_vars) == 1:
            # single discrete var -> enable and fill the values combo
            value_stack_enabled = True
            current_value_widget = self.value_combo
            self.value_combo.clear()
            self.value_combo.addItems(selected_vars[0].values)
        else:
            # mixed type selection -> disable
            value_stack_enabled = False
            current_value_widget = None
            self.variable_button_group.button(Method.Default).setEnabled(False)

        self.value_stack.setEnabled(value_stack_enabled)
        if current_value_widget is not None:
            self.value_stack.setCurrentWidget(current_value_widget)
            if fixed_value is not None:
                # set current value
                if current_value_widget is self.value_combo:
                    self.value_combo.setCurrentIndex(fixed_value)
                elif current_value_widget is self.value_double:
                    self.value_double.setValue(fixed_value)
                else:
                    assert False

    def set_method_for_current_selection(self, method_index):
        # type: (Method) -> None
        indexes = self.selection.selectedIndexes()
        self.set_method_for_indexes(indexes, method_index)

    def set_method_for_indexes(self, indexes, method_index):
        # type: (List[QModelIndex], Method) -> None
        if method_index == Method.AsAboveSoBelow:
            for index in indexes:
                self.varmodel.setData(index, None, StateRole)
        elif method_index == Method.Default:
            current = self.value_stack.currentWidget()
            if current is self.value_combo:
                value = self.value_combo.currentIndex()
            else:
                value = self.value_double.value()
            for index in indexes:
                state = (int(Method.Default), (value,))
                self.varmodel.setData(index, state, StateRole)
        else:
            state = (int(method_index), ())
            for index in indexes:
                self.varmodel.setData(index, state, StateRole)

        self.update_varview(indexes)
        self._invalidate()

    def update_varview(self, indexes=None):
        if indexes is None:
            indexes = map(self.varmodel.index, range(len(self.varmodel)))

        for index in indexes:
            self.varmodel.setData(
                index, self.get_method_for_column(index.row()),
                DisplayMethodRole)

    def _on_value_selected(self):
        # The fixed 'Value' in the widget has been changed by the user.
        self.variable_button_group.button(Method.Default).setChecked(True)
        self.set_method_for_current_selection(Method.Default)

    def reset_variable_state(self):
        indexes = list(map(self.varmodel.index, range(len(self.varmodel))))
        self.set_method_for_indexes(indexes, Method.AsAboveSoBelow)
        self.variable_button_group.button(Method.AsAboveSoBelow).setChecked(True)

    def _store_state(self):
        # type: () -> VariableState
        """
        Save the current variable imputation state
        """
        state = {}  # type: VariableState
        for i, var in enumerate(self.varmodel):
            index = self.varmodel.index(i)
            m = index.data(StateRole)
            if m is not None:
                state[var_key(var)] = m
        return state

    def _restore_state(self, state):
        # type: (VariableState) -> None
        """
        Restore the variable imputation state from the saved state
        """
        def check(state):
            # check if state is a proper State
            if isinstance(state, tuple) and len(state) == 2:
                m, p = state
                if isinstance(m, int) and isinstance(p, tuple) and \
                        0 <= m < len(Method):
                    return True
            return False

        for i, var in enumerate(self.varmodel):
            m = state.get(var_key(var), None)
            if check(m):
                self.varmodel.setData(self.varmodel.index(i), m, StateRole)

    def storeSpecificSettings(self):
        self._variable_imputation_state = self._store_state()
        super().storeSpecificSettings()