Ejemplo n.º 1
0
    def __init__(self):
        super().__init__()

        self.results = None
        self.classifier_names = []
        self.colors = []
        self._curve_data = {}

        box = gui.vBox(self.controlArea, "Plot")
        tbox = gui.vBox(box, "Target Class")
        tbox.setFlat(True)

        self.target_cb = gui.comboBox(tbox,
                                      self,
                                      "target_index",
                                      callback=self._on_target_changed,
                                      contentsLength=8)

        cbox = gui.vBox(box, "Classifiers")
        cbox.setFlat(True)
        self.classifiers_list_box = gui.listBox(
            cbox,
            self,
            "selected_classifiers",
            "classifier_names",
            selectionMode=QtWidgets.QListView.MultiSelection,
            callback=self._on_classifiers_changed)

        gui.checkBox(box,
                     self,
                     "display_convex_hull",
                     "Show lift convex hull",
                     callback=self._replot)

        self.plotview = pg.GraphicsView(background="w")
        self.plotview.setFrameStyle(QtWidgets.QFrame.StyledPanel)

        self.plot = pg.PlotItem()
        self.plot.getViewBox().setMenuEnabled(False)

        pen = QPen(self.palette().color(QPalette.Text))

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel("P Rate")

        axis = self.plot.getAxis("left")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel("TP Rate")

        self.plot.showGrid(True, True, alpha=0.1)
        self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0))

        self.plotview.setCentralItem(self.plot)
        self.mainArea.layout().addWidget(self.plotview)
    def render_plot_area(self, i, x_label, y_label, invert_y=False):
        self.plot_areas[i] = pg.GraphicsView(background="w")
        self.plot_areas[i].setFrameStyle(QFrame.StyledPanel)

        self.plot_items[i] = pg.PlotItem(enableMenu=True)
        self.plot_items[i].setMouseEnabled(False, False)
        self.plot_items[i].hideButtons()
        self.plot_items[i].enableAutoScale()
        self.plot_items[i].enableAutoRange(x=True, y=True)
        self.plot_items[i].showGrid(x=True, y=True, alpha=0.1)

        if invert_y:
            self.plot_items[i].invertY()

        pen = QPen(self.palette().color(QPalette.Text))

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot_items[i].getAxis("bottom")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel(x_label)

        axis = self.plot_items[i].getAxis("left")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel(y_label)

        self.plot_areas[i].setCentralItem(self.plot_items[i])
Ejemplo n.º 3
0
    def __init__(self):
        super().__init__()

        self.results = None
        self.classifier_names = []
        self.colors = []
        self._points_hull: Dict[Tuple[int, int], PointsAndHull] = {}

        box = gui.vBox(self.controlArea, box="Curve")
        self.target_cb = gui.comboBox(
            box, self, "target_index",
            label="Target: ", orientation=Qt.Horizontal,
            callback=self._on_target_changed,
            contentsLength=8, searchable=True
        )
        gui.radioButtons(
            box, self, "curve_type", ("Lift Curve", "Cumulative Gains"),
            callback=self._on_curve_type_changed
        )

        self.classifiers_list_box = gui.listBox(
            self.controlArea, self, "selected_classifiers", "classifier_names",
            box="Models",
            selectionMode=QListView.MultiSelection,
            callback=self._on_classifiers_changed
        )
        self.classifiers_list_box.setMaximumHeight(100)

        gui.checkBox(self.controlArea, self, "display_convex_hull",
                     "Show convex hull", box="Settings", callback=self._replot)

        gui.rubber(self.controlArea)

        self.plotview = GraphicsView()
        self.plotview.setFrameStyle(QFrame.StyledPanel)
        self.plot = PlotItem(enableMenu=False)
        self.plot.parameter_setter = ParameterSetter(self.plot)
        self.plot.curve_items = []
        self.plot.hull_items = []
        self.plot.default_line_item = None
        self.plot.display_convex_hull = self.display_convex_hull
        self.plot.setMouseEnabled(False, False)
        self.plot.hideButtons()

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        for pos, label in (("bottom", "P Rate"), ("left", "")):
            axis = self.plot.getAxis(pos)
            axis.setTickFont(tickfont)
            axis.setLabel(label)
        self._set_left_label()

        self.plot.showGrid(True, True, alpha=0.1)

        self.plotview.setCentralItem(self.plot)
        self.mainArea.layout().addWidget(self.plotview)

        VisualSettingsDialog(self, self.plot.parameter_setter.initial_settings)
    def __init__(self):
        super().__init__()

        self.classifier_names = []
        self.results = None
        self.colors = []
        self._curve_data = {}

        box = gui.vBox(self.controlArea, "Plot")
        tbox = gui.vBox(box, "Target Class")
        tbox.setFlat(True)

        self.target_cb = gui.comboBox(tbox,
                                      self,
                                      "target_index",
                                      callback=self._on_target_changed,
                                      contentsLength=8)

        cbox = gui.vBox(box, "Classifiers")
        cbox.setFlat(True)
        self.classifiers_list_box = gui.listBox(
            cbox,
            self,
            "selected_classifiers",
            "classifier_names",
            selectionMode=QtWidgets.QListView.MultiSelection,
            callback=self._on_classifiers_changed)
        self.show_average_precisions_cb = gui.checkBox(
            cbox,
            self,
            "show_average_precisions",
            "Show average precisions",
            callback=self._replot)
        self.plotview = pg.GraphicsView(background="w")

        self.plot = pg.PlotItem(enableMenu=False)
        self.plot.hideButtons()

        pen = QPen(self.palette().color(QPalette.Text))

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel("Recall")

        axis = self.plot.getAxis("left")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel("Precision")

        self.plot.showGrid(True, True, alpha=0.1)
        self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.05), padding=0.05)

        self.plotview.setCentralItem(self.plot)
        self.mainArea.layout().addWidget(self.plotview)
Ejemplo n.º 5
0
    def __init__(self):
        super().__init__()

        self.results = None
        self.classifier_names = []
        self.colors = []
        self._curve_data = {}

        box = gui.vBox(self.controlArea, "Plot")
        tbox = gui.vBox(box, "Target Class")
        tbox.setFlat(True)

        self.target_cb = gui.comboBox(
            tbox, self, "target_index", callback=self._on_target_changed,
            contentsLength=8)

        cbox = gui.vBox(box, "Classifiers")
        cbox.setFlat(True)
        self.classifiers_list_box = gui.listBox(
            cbox, self, "selected_classifiers", "classifier_names",
            selectionMode=QtWidgets.QListView.MultiSelection,
            callback=self._on_classifiers_changed)

        gui.checkBox(box, self, "display_convex_hull",
                     "Show lift convex hull", callback=self._replot)

        self.plotview = pg.GraphicsView(background="w")
        self.plotview.setFrameStyle(QtWidgets.QFrame.StyledPanel)

        self.plot = pg.PlotItem(enableMenu=False)
        self.plot.setMouseEnabled(False, False)
        self.plot.hideButtons()

        pen = QPen(self.palette().color(QPalette.Text))

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel("P Rate")

        axis = self.plot.getAxis("left")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel("TP Rate")

        self.plot.showGrid(True, True, alpha=0.1)
        self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0), padding=0.05)

        self.plotview.setCentralItem(self.plot)
        self.mainArea.layout().addWidget(self.plotview)
Ejemplo n.º 6
0
    def _init_ui(self):
        namesBox = gui.vBox(self.controlArea, "Names")

        hbox = gui.hBox(namesBox, margin=0, spacing=0)
        gui.lineEdit(hbox,
                     self,
                     "attr1",
                     "Variable X: ",
                     controlWidth=80,
                     orientation=Qt.Horizontal,
                     callback=self._attr_name_changed)
        gui.separator(hbox, 21)
        hbox = gui.hBox(namesBox, margin=0, spacing=0)
        attr2 = gui.lineEdit(hbox,
                             self,
                             "attr2",
                             "Variable Y: ",
                             controlWidth=80,
                             orientation=Qt.Horizontal,
                             callback=self._attr_name_changed)
        gui.separator(hbox)
        gui.checkBox(hbox,
                     self,
                     "hasAttr2",
                     '',
                     disables=attr2,
                     labelWidth=0,
                     callback=self.set_dimensions)

        gui.widgetLabel(namesBox, "Labels")
        self.classValuesView = listView = gui.ListViewWithSizeHint(
            preferred_size=(-1, 30))
        listView.setModel(self.class_model)
        itemmodels.select_row(listView, 0)
        namesBox.layout().addWidget(listView)

        self.addClassLabel = QAction("+",
                                     self,
                                     toolTip="Add new class label",
                                     triggered=self.add_new_class_label)

        self.removeClassLabel = QAction(
            unicodedata.lookup("MINUS SIGN"),
            self,
            toolTip="Remove selected class label",
            triggered=self.remove_selected_class_label)

        actionsWidget = itemmodels.ModelActionsWidget(
            [self.addClassLabel, self.removeClassLabel], self)
        actionsWidget.layout().addStretch(10)
        actionsWidget.layout().setSpacing(1)
        namesBox.layout().addWidget(actionsWidget)

        tBox = gui.vBox(self.buttonsArea, "Tools")
        toolsBox = gui.widgetBox(tBox, orientation=QGridLayout())

        self.toolActions = QActionGroup(self)
        self.toolActions.setExclusive(True)
        self.toolButtons = []

        for i, (name, tooltip, tool, icon) in enumerate(self.TOOLS):
            action = QAction(
                name,
                self,
                toolTip=tooltip,
                checkable=tool.checkable,
                icon=QIcon(icon),
            )
            action.triggered.connect(partial(self.set_current_tool, tool))

            button = QToolButton(iconSize=QSize(24, 24),
                                 toolButtonStyle=Qt.ToolButtonTextUnderIcon,
                                 sizePolicy=QSizePolicy(
                                     QSizePolicy.MinimumExpanding,
                                     QSizePolicy.Fixed))
            button.setDefaultAction(action)
            self.toolButtons.append((button, tool))

            toolsBox.layout().addWidget(button, i / 3, i % 3)
            self.toolActions.addAction(action)

        for column in range(3):
            toolsBox.layout().setColumnMinimumWidth(column, 10)
            toolsBox.layout().setColumnStretch(column, 1)

        undo = self.undo_stack.createUndoAction(self)
        redo = self.undo_stack.createRedoAction(self)

        undo.setShortcut(QKeySequence.Undo)
        redo.setShortcut(QKeySequence.Redo)

        self.addActions([undo, redo])
        self.undo_stack.indexChanged.connect(self.invalidate)

        indBox = gui.indentedBox(tBox, sep=8)
        form = QFormLayout(formAlignment=Qt.AlignLeft,
                           labelAlignment=Qt.AlignLeft,
                           fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow)
        indBox.layout().addLayout(form)
        slider = gui.hSlider(indBox,
                             self,
                             "brushRadius",
                             minValue=1,
                             maxValue=100,
                             createLabel=False,
                             addToLayout=False)
        form.addRow("Radius:", slider)

        slider = gui.hSlider(indBox,
                             self,
                             "density",
                             None,
                             minValue=1,
                             maxValue=100,
                             createLabel=False,
                             addToLayout=False)

        form.addRow("Intensity:", slider)

        slider = gui.hSlider(indBox,
                             self,
                             "symbol_size",
                             None,
                             minValue=1,
                             maxValue=100,
                             createLabel=False,
                             callback=self.set_symbol_size,
                             addToLayout=False)

        form.addRow("Symbol:", slider)

        self.btResetToInput = gui.button(tBox, self, "Reset to Input Data",
                                         self.reset_to_input)
        self.btResetToInput.setDisabled(True)

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

        # main area GUI
        viewbox = PaintViewBox(enableMouse=False)
        self.plotview = pg.PlotWidget(background="w", viewBox=viewbox)
        self.plotview.sizeHint = lambda: QSize(
            200, 100)  # Minimum size for 1-d painting
        self.plot = self.plotview.getPlotItem()

        axis_color = self.palette().color(QPalette.Text)
        axis_pen = QPen(axis_color)

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setLabel(self.attr1)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)

        axis = self.plot.getAxis("left")
        axis.setLabel(self.attr2)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)
        if not self.hasAttr2:
            self.plot.hideAxis('left')

        self.plot.hideButtons()
        self.plot.setXRange(0, 1, padding=0.01)

        self.mainArea.layout().addWidget(self.plotview)

        # enable brush tool
        self.toolActions.actions()[0].setChecked(True)
        self.set_current_tool(self.TOOLS[0][2])

        self.set_dimensions()
Ejemplo n.º 7
0
    def __init__(self):
        super().__init__()

        self.results = None
        self.classifier_names = []
        self.perf_line = None
        self.colors = []
        self._curve_data = {}
        self._plot_curves = {}
        self._rocch = None
        self._perf_line = None

        box = gui.vBox(self.controlArea, "Plot")
        tbox = gui.vBox(box, "Target Class")
        tbox.setFlat(True)

        self.target_cb = gui.comboBox(
            tbox, self, "target_index", callback=self._on_target_changed,
            contentsLength=8)

        cbox = gui.vBox(box, "Classifiers")
        cbox.setFlat(True)
        self.classifiers_list_box = gui.listBox(
            cbox, self, "selected_classifiers", "classifier_names",
            selectionMode=QListView.MultiSelection,
            callback=self._on_classifiers_changed)

        abox = gui.vBox(box, "Combine ROC Curves From Folds")
        abox.setFlat(True)
        gui.comboBox(abox, self, "roc_averaging",
                     items=["Merge Predictions from Folds", "Mean TP Rate",
                            "Mean TP and FP at Threshold", "Show Individual Curves"],
                     callback=self._replot)

        hbox = gui.vBox(box, "ROC Convex Hull")
        hbox.setFlat(True)
        gui.checkBox(hbox, self, "display_convex_curve",
                     "Show convex ROC curves", callback=self._replot)
        gui.checkBox(hbox, self, "display_convex_hull",
                     "Show ROC convex hull", callback=self._replot)

        box = gui.vBox(self.controlArea, "Analysis")

        gui.checkBox(box, self, "display_def_threshold",
                     "Default threshold (0.5) point",
                     callback=self._on_display_def_threshold_changed)

        gui.checkBox(box, self, "display_perf_line", "Show performance line",
                     callback=self._on_display_perf_line_changed)
        grid = QGridLayout()
        ibox = gui.indentedBox(box, orientation=grid)

        sp = gui.spin(box, self, "fp_cost", 1, 1000, 10,
                      callback=self._on_display_perf_line_changed)
        grid.addWidget(QLabel("FP Cost:"), 0, 0)
        grid.addWidget(sp, 0, 1)

        sp = gui.spin(box, self, "fn_cost", 1, 1000, 10,
                      callback=self._on_display_perf_line_changed)
        grid.addWidget(QLabel("FN Cost:"))
        grid.addWidget(sp, 1, 1)
        sp = gui.spin(box, self, "target_prior", 1, 99,
                      callback=self._on_display_perf_line_changed)
        sp.setSuffix("%")
        sp.addAction(QAction("Auto", sp))
        grid.addWidget(QLabel("Prior target class probability:"))
        grid.addWidget(sp, 2, 1)

        self.plotview = pg.GraphicsView(background="w")
        self.plotview.setFrameStyle(QFrame.StyledPanel)

        self.plot = pg.PlotItem(enableMenu=False)
        self.plot.setMouseEnabled(False, False)
        self.plot.hideButtons()

        pen = QPen(self.palette().color(QPalette.Text))

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel("FP Rate (1-Specificity)")

        axis = self.plot.getAxis("left")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel("TP Rate (Sensitivity)")

        self.plot.showGrid(True, True, alpha=0.1)
        self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0), padding=0.05)

        self.plotview.setCentralItem(self.plot)
        self.mainArea.layout().addWidget(self.plotview)
Ejemplo n.º 8
0
    def add_main_layout(self):

        self.data = None
        self.preprocessors = None
        self.learner = None

        self.scatterplot_item = None
        self.plot_item = None

        self.x_label = 'x'
        self.y_label = 'y'

        box = gui.vBox(self.controlArea, "Variables")

        self.x_var_model = itemmodels.VariableListModel()
        self.comboBoxAttributesX = gui.comboBox(box,
                                                self,
                                                value='x_var_index',
                                                label="Input: ",
                                                orientation=Qt.Horizontal,
                                                callback=self.apply,
                                                contentsLength=12)
        self.comboBoxAttributesX.setSizePolicy(QSizePolicy.MinimumExpanding,
                                               QSizePolicy.Fixed)
        self.comboBoxAttributesX.setModel(self.x_var_model)
        gui.doubleSpin(gui.indentedBox(box),
                       self,
                       "polynomialexpansion",
                       0,
                       10,
                       label="Polynomial expansion:",
                       callback=self.apply)

        gui.separator(box, height=8)
        self.y_var_model = itemmodels.VariableListModel()
        self.comboBoxAttributesY = gui.comboBox(box,
                                                self,
                                                value="y_var_index",
                                                label="Target: ",
                                                orientation=Qt.Horizontal,
                                                callback=self.apply,
                                                contentsLength=12)
        self.comboBoxAttributesY.setSizePolicy(QSizePolicy.MinimumExpanding,
                                               QSizePolicy.Fixed)
        self.comboBoxAttributesY.setModel(self.y_var_model)

        gui.rubber(self.controlArea)

        # main area GUI
        self.plotview = pg.PlotWidget(background="w")
        self.plot = self.plotview.getPlotItem()

        axis_color = self.palette().color(QPalette.Text)
        axis_pen = QPen(axis_color)

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setLabel(self.x_label)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)

        axis = self.plot.getAxis("left")
        axis.setLabel(self.y_label)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)

        self.plot.setRange(xRange=(0.0, 1.0),
                           yRange=(0.0, 1.0),
                           disableAutoRange=True)

        self.mainArea.layout().addWidget(self.plotview)
Ejemplo n.º 9
0
    def add_main_layout(self):

        self.data = None
        self.learner = None

        self.scatterplot_item = None
        self.plot_item = None

        self.x_label = 'x'
        self.y_label = 'y'

        self.rmse = ""
        self.mae = ""
        self.regressor_name = self.default_learner_name

        # info box
        info_box = gui.vBox(self.controlArea, "Info")
        self.regressor_label = gui.label(
            widget=info_box,
            master=self,
            label="Regressor: %(regressor_name).30s")
        gui.label(widget=info_box,
                  master=self,
                  label="Mean absolute error: %(mae).6s")
        gui.label(widget=info_box,
                  master=self,
                  label="Root mean square error: %(rmse).6s")

        box = gui.vBox(self.controlArea, "Variables")

        self.x_var_model = itemmodels.VariableListModel()
        self.comboBoxAttributesX = gui.comboBox(box,
                                                self,
                                                value='x_var_index',
                                                label="Input: ",
                                                orientation=Qt.Horizontal,
                                                callback=self.apply,
                                                maximumContentsLength=15)
        self.comboBoxAttributesX.setModel(self.x_var_model)
        self.expansion_spin = gui.doubleSpin(gui.indentedBox(box),
                                             self,
                                             "polynomialexpansion",
                                             0,
                                             10,
                                             label="Polynomial expansion:",
                                             callback=self.apply)

        gui.separator(box, height=8)
        self.y_var_model = itemmodels.VariableListModel()
        self.comboBoxAttributesY = gui.comboBox(box,
                                                self,
                                                value="y_var_index",
                                                label="Target: ",
                                                orientation=Qt.Horizontal,
                                                callback=self.apply,
                                                maximumContentsLength=15)
        self.comboBoxAttributesY.setModel(self.y_var_model)

        properties_box = gui.vBox(self.controlArea, "Properties")
        self.error_bars_checkbox = gui.checkBox(widget=properties_box,
                                                master=self,
                                                value='error_bars_enabled',
                                                label="Show error bars",
                                                callback=self.apply)

        gui.rubber(self.controlArea)

        # main area GUI
        self.plotview = pg.PlotWidget(background="w")
        self.plot = self.plotview.getPlotItem()

        axis_color = self.palette().color(QPalette.Text)
        axis_pen = QPen(axis_color)

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setLabel(self.x_label)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)

        axis = self.plot.getAxis("left")
        axis.setLabel(self.y_label)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)

        self.plot.setRange(xRange=(0.0, 1.0),
                           yRange=(0.0, 1.0),
                           disableAutoRange=True)

        self.mainArea.layout().addWidget(self.plotview)
Ejemplo n.º 10
0
    def _init_ui(self):
        namesBox = gui.vBox(self.controlArea, "Names")

        hbox = gui.hBox(namesBox, margin=0, spacing=0)
        gui.lineEdit(hbox, self, "attr1", "Variable X: ",
                     controlWidth=80, orientation=Qt.Horizontal,
                     callback=self._attr_name_changed)
        gui.separator(hbox, 21)
        hbox = gui.hBox(namesBox, margin=0, spacing=0)
        attr2 = gui.lineEdit(hbox, self, "attr2", "Variable Y: ",
                             controlWidth=80, orientation=Qt.Horizontal,
                             callback=self._attr_name_changed)
        gui.separator(hbox)
        gui.checkBox(hbox, self, "hasAttr2", '', disables=attr2,
                     labelWidth=0,
                     callback=self.set_dimensions)
        gui.separator(namesBox)

        gui.widgetLabel(namesBox, "Labels")
        self.classValuesView = listView = QListView(
            selectionMode=QListView.SingleSelection,
            sizePolicy=QSizePolicy(QSizePolicy.Ignored,
                                   QSizePolicy.Maximum)
        )
        listView.setModel(self.class_model)
        itemmodels.select_row(listView, 0)
        namesBox.layout().addWidget(listView)

        self.addClassLabel = QAction(
            "+", self,
            toolTip="Add new class label",
            triggered=self.add_new_class_label
        )

        self.removeClassLabel = QAction(
            unicodedata.lookup("MINUS SIGN"), self,
            toolTip="Remove selected class label",
            triggered=self.remove_selected_class_label
        )

        actionsWidget = itemmodels.ModelActionsWidget(
            [self.addClassLabel, self.removeClassLabel], self
        )
        actionsWidget.layout().addStretch(10)
        actionsWidget.layout().setSpacing(1)
        namesBox.layout().addWidget(actionsWidget)

        tBox = gui.vBox(self.controlArea, "Tools", addSpace=True)
        buttonBox = gui.hBox(tBox)
        toolsBox = gui.widgetBox(buttonBox, orientation=QGridLayout())

        self.toolActions = QActionGroup(self)
        self.toolActions.setExclusive(True)
        self.toolButtons = []

        for i, (name, tooltip, tool, icon) in enumerate(self.TOOLS):
            action = QAction(
                name, self,
                toolTip=tooltip,
                checkable=tool.checkable,
                icon=QIcon(icon),
            )
            action.triggered.connect(partial(self.set_current_tool, tool))

            button = QToolButton(
                iconSize=QSize(24, 24),
                toolButtonStyle=Qt.ToolButtonTextUnderIcon,
                sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,
                                       QSizePolicy.Fixed)
            )
            button.setDefaultAction(action)
            self.toolButtons.append((button, tool))

            toolsBox.layout().addWidget(button, i / 3, i % 3)
            self.toolActions.addAction(action)

        for column in range(3):
            toolsBox.layout().setColumnMinimumWidth(column, 10)
            toolsBox.layout().setColumnStretch(column, 1)

        undo = self.undo_stack.createUndoAction(self)
        redo = self.undo_stack.createRedoAction(self)

        undo.setShortcut(QKeySequence.Undo)
        redo.setShortcut(QKeySequence.Redo)

        self.addActions([undo, redo])
        self.undo_stack.indexChanged.connect(lambda _: self.invalidate())

        gui.separator(tBox)
        indBox = gui.indentedBox(tBox, sep=8)
        form = QFormLayout(
            formAlignment=Qt.AlignLeft,
            labelAlignment=Qt.AlignLeft,
            fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow
        )
        indBox.layout().addLayout(form)
        slider = gui.hSlider(
            indBox, self, "brushRadius", minValue=1, maxValue=100,
            createLabel=False
        )
        form.addRow("Radius:", slider)

        slider = gui.hSlider(
            indBox, self, "density", None, minValue=1, maxValue=100,
            createLabel=False
        )

        form.addRow("Intensity:", slider)
        self.btResetToInput = gui.button(
            tBox, self, "Reset to Input Data", self.reset_to_input)
        self.btResetToInput.setDisabled(True)

        gui.rubber(self.controlArea)
        gui.auto_commit(self.left_side, self, "autocommit",
                        "Send")

        # main area GUI
        viewbox = PaintViewBox(enableMouse=False)
        self.plotview = pg.PlotWidget(background="w", viewBox=viewbox)
        self.plotview.sizeHint = lambda: QSize(200, 100)  # Minimum size for 1-d painting
        self.plot = self.plotview.getPlotItem()

        axis_color = self.palette().color(QPalette.Text)
        axis_pen = QPen(axis_color)

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setLabel(self.attr1)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)

        axis = self.plot.getAxis("left")
        axis.setLabel(self.attr2)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)
        if not self.hasAttr2:
            self.plot.hideAxis('left')

        self.plot.hideButtons()
        self.plot.setXRange(0, 1, padding=0.01)

        self.mainArea.layout().addWidget(self.plotview)

        # enable brush tool
        self.toolActions.actions()[0].setChecked(True)
        self.set_current_tool(self.TOOLS[0][2])

        self.set_dimensions()
Ejemplo n.º 11
0
    def __init__(self):
        super().__init__()

        self.results = None
        self.classifier_names = []
        self.perf_line = None
        self.colors = []
        self._curve_data = {}
        self._plot_curves = {}
        self._rocch = None
        self._perf_line = None
        self._tooltip_cache = None

        box = gui.vBox(self.controlArea, "绘制")
        tbox = gui.vBox(box, "目标类别")
        tbox.setFlat(True)

        self.target_cb = gui.comboBox(tbox,
                                      self,
                                      "target_index",
                                      callback=self._on_target_changed,
                                      contentsLength=8)

        cbox = gui.vBox(box, "分类器")
        cbox.setFlat(True)
        self.classifiers_list_box = gui.listBox(
            cbox,
            self,
            "selected_classifiers",
            "classifier_names",
            selectionMode=QListView.MultiSelection,
            callback=self._on_classifiers_changed)

        abox = gui.vBox(box, "从折叠(Folds)合并ROC曲线")
        abox.setFlat(True)
        gui.comboBox(abox,
                     self,
                     "roc_averaging",
                     items=["从折叠合并预测", "平均真阳性率", "阈值处的平均真阳性率和假阳性率", "显示单个曲线"],
                     callback=self._replot)

        hbox = gui.vBox(box, "ROC凸包")
        hbox.setFlat(True)
        gui.checkBox(hbox,
                     self,
                     "display_convex_curve",
                     "显示凸ROC曲线",
                     callback=self._replot)
        gui.checkBox(hbox,
                     self,
                     "display_convex_hull",
                     "显示ROC凸包",
                     callback=self._replot)

        box = gui.vBox(self.controlArea, "分析(Analysis)")

        gui.checkBox(box,
                     self,
                     "display_def_threshold",
                     "默认阈值(0.5)点",
                     callback=self._on_display_def_threshold_changed)

        gui.checkBox(box,
                     self,
                     "display_perf_line",
                     "显示性能行",
                     callback=self._on_display_perf_line_changed)
        grid = QGridLayout()
        gui.indentedBox(box, orientation=grid)

        sp = gui.spin(box,
                      self,
                      "fp_cost",
                      1,
                      1000,
                      10,
                      alignment=Qt.AlignRight,
                      callback=self._on_display_perf_line_changed)
        grid.addWidget(QLabel("假阳性率损失:"), 0, 0)
        grid.addWidget(sp, 0, 1)

        sp = gui.spin(box,
                      self,
                      "fn_cost",
                      1,
                      1000,
                      10,
                      alignment=Qt.AlignRight,
                      callback=self._on_display_perf_line_changed)
        grid.addWidget(QLabel("假阴性率损失:"))
        grid.addWidget(sp, 1, 1)
        self.target_prior_sp = gui.spin(box,
                                        self,
                                        "target_prior",
                                        1,
                                        99,
                                        alignment=Qt.AlignRight,
                                        callback=self._on_target_prior_changed)
        self.target_prior_sp.setSuffix(" %")
        self.target_prior_sp.addAction(QAction("Auto", sp))
        grid.addWidget(QLabel("目标类先验概率:"))
        grid.addWidget(self.target_prior_sp, 2, 1)

        self.plotview = pg.GraphicsView(background="w")
        self.plotview.setFrameStyle(QFrame.StyledPanel)
        self.plotview.scene().sigMouseMoved.connect(self._on_mouse_moved)

        self.plot = pg.PlotItem(enableMenu=False)
        self.plot.setMouseEnabled(False, False)
        self.plot.hideButtons()

        pen = QPen(self.palette().color(QPalette.Text))

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel("假阳性率损失 (1-特异度)")

        axis = self.plot.getAxis("left")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel("真阳性率 (灵敏度)")

        self.plot.showGrid(True, True, alpha=0.1)
        self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0), padding=0.05)

        self.plotview.setCentralItem(self.plot)
        self.mainArea.layout().addWidget(self.plotview)
Ejemplo n.º 12
0
    def __init__(self):
        super().__init__()

        self.results = None
        self.classifier_names = []
        self.perf_line = None
        self.colors = []
        self._curve_data = {}
        self._plot_curves = {}
        self._rocch = None
        self._perf_line = None
        self._tooltip_cache = None

        box = gui.vBox(self.controlArea, "绘制")
        self.target_cb = gui.comboBox(box,
                                      self,
                                      "target_index",
                                      label="目标",
                                      orientation=Qt.Horizontal,
                                      callback=self._on_target_changed,
                                      contentsLength=8,
                                      searchable=True)

        gui.widgetLabel(box, "分类器")
        line_height = 4 * QFontMetrics(self.font()).lineSpacing()
        self.classifiers_list_box = gui.listBox(
            box,
            self,
            "selected_classifiers",
            "classifier_names",
            selectionMode=QListView.MultiSelection,
            callback=self._on_classifiers_changed,
            sizeHint=QSize(0, line_height))

        abox = gui.vBox(self.controlArea, "曲线")
        gui.comboBox(abox,
                     self,
                     "roc_averaging",
                     items=["合并折叠预测", "平均真阳性率", "阈值处的平均真阳性率和假阳性率", "显示单个曲线"],
                     callback=self._replot)

        gui.checkBox(abox,
                     self,
                     "display_convex_curve",
                     "显示凸ROC曲线",
                     callback=self._replot)
        gui.checkBox(abox,
                     self,
                     "display_convex_hull",
                     "显示ROC凸包",
                     callback=self._replot)

        box = gui.vBox(self.controlArea, "分析")

        gui.checkBox(box,
                     self,
                     "display_def_threshold",
                     "默认阈值(0.5)点",
                     callback=self._on_display_def_threshold_changed)

        gui.checkBox(box,
                     self,
                     "display_perf_line",
                     "显示性能线",
                     callback=self._on_display_perf_line_changed)
        grid = QGridLayout()
        gui.indentedBox(box, orientation=grid)

        sp = gui.spin(box,
                      self,
                      "fp_cost",
                      1,
                      1000,
                      10,
                      alignment=Qt.AlignRight,
                      callback=self._on_display_perf_line_changed)
        grid.addWidget(QLabel("假阳性率损失:"), 0, 0)
        grid.addWidget(sp, 0, 1)

        sp = gui.spin(box,
                      self,
                      "fn_cost",
                      1,
                      1000,
                      10,
                      alignment=Qt.AlignRight,
                      callback=self._on_display_perf_line_changed)
        grid.addWidget(QLabel("假阴性率损失:"))
        grid.addWidget(sp, 1, 1)
        self.target_prior_sp = gui.spin(box,
                                        self,
                                        "target_prior",
                                        1,
                                        99,
                                        alignment=Qt.AlignRight,
                                        spinType=float,
                                        callback=self._on_target_prior_changed)
        self.target_prior_sp.setSuffix(" %")
        self.target_prior_sp.addAction(QAction("Auto", sp))
        grid.addWidget(QLabel("先验概率:"))
        grid.addWidget(self.target_prior_sp, 2, 1)

        self.plotview = GraphicsView(background=None)
        self.plotview.setFrameStyle(QFrame.StyledPanel)
        self.plotview.scene().sigMouseMoved.connect(self._on_mouse_moved)

        self.plot = PlotItem(enableMenu=False)
        self.plot.setMouseEnabled(False, False)
        self.plot.hideButtons()

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setTickFont(tickfont)
        axis.setLabel("假阳性率 (1-特异度)")
        axis.setGrid(16)

        axis = self.plot.getAxis("left")
        axis.setTickFont(tickfont)
        axis.setLabel("真阳性率 (灵敏度)")
        axis.setGrid(16)

        self.plot.showGrid(True, True, alpha=0.2)
        self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0), padding=0.05)

        self.plotview.setCentralItem(self.plot)
        self.mainArea.layout().addWidget(self.plotview)
Ejemplo n.º 13
0
    def add_main_layout(self):

        self.data = None
        self.preprocessors = None
        self.learner = None

        self.scatterplot_item = None
        self.plot_item = None

        self.x_label = 'x'
        self.y_label = 'y'

        box = gui.vBox(self.controlArea, "Variables")

        self.x_var_model = itemmodels.VariableListModel()
        self.comboBoxAttributesX = gui.comboBox(
            box, self, value='x_var_index', label="Input: ",
            orientation=Qt.Horizontal, callback=self.apply, contentsLength=12)
        self.comboBoxAttributesX.setSizePolicy(
            QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
        self.comboBoxAttributesX.setModel(self.x_var_model)
        gui.doubleSpin(
            gui.indentedBox(box),
            self, "polynomialexpansion", 0, 10,
            label="Polynomial expansion:", callback=self.apply)

        gui.separator(box, height=8)
        self.y_var_model = itemmodels.VariableListModel()
        self.comboBoxAttributesY = gui.comboBox(
            box, self, value="y_var_index", label="Target: ",
            orientation=Qt.Horizontal, callback=self.apply, contentsLength=12)
        self.comboBoxAttributesY.setSizePolicy(
            QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
        self.comboBoxAttributesY.setModel(self.y_var_model)

        gui.rubber(self.controlArea)

        # main area GUI
        self.plotview = pg.PlotWidget(background="w")
        self.plot = self.plotview.getPlotItem()

        axis_color = self.palette().color(QPalette.Text)
        axis_pen = QPen(axis_color)

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setLabel(self.x_label)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)

        axis = self.plot.getAxis("left")
        axis.setLabel(self.y_label)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)

        self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0),
                           disableAutoRange=True)

        self.mainArea.layout().addWidget(self.plotview)
    def add_main_layout(self):

        self.data = None
        self.learner = None

        self.scatterplot_item = None
        self.plot_item = None

        self.x_label = 'x'
        self.y_label = 'y'

        self.rmse = ""
        self.mae = ""
        self.regressor_name = self.default_learner_name

        # info box
        info_box = gui.vBox(self.controlArea, "Info")
        self.regressor_label = gui.label(
            widget=info_box, master=self,
            label="Regressor: %(regressor_name).30s")
        gui.label(widget=info_box, master=self,
            label="Mean absolute error: %(mae).6s")
        gui.label(widget=info_box, master=self,
                  label="Root mean square error: %(rmse).6s")

        box = gui.vBox(self.controlArea, "Variables")

        self.x_var_model = itemmodels.VariableListModel()
        self.comboBoxAttributesX = gui.comboBox(
            box, self, value='x_var_index', label="Input: ",
            orientation=Qt.Horizontal, callback=self.apply,
            maximumContentsLength=15)
        self.comboBoxAttributesX.setModel(self.x_var_model)
        self.expansion_spin = gui.doubleSpin(
            gui.indentedBox(box),
            self, "polynomialexpansion", 0, 10,
            label="Polynomial expansion:", callback=self.apply)

        gui.separator(box, height=8)
        self.y_var_model = itemmodels.VariableListModel()
        self.comboBoxAttributesY = gui.comboBox(
            box, self, value="y_var_index", label="Target: ",
            orientation=Qt.Horizontal, callback=self.apply,
            maximumContentsLength=15)
        self.comboBoxAttributesY.setModel(self.y_var_model)

        properties_box = gui.vBox(self.controlArea, "Properties")
        self.error_bars_checkbox = gui.checkBox(
            widget=properties_box, master=self, value='error_bars_enabled',
            label="Show error bars", callback=self.apply)

        gui.rubber(self.controlArea)

        # main area GUI
        self.plotview = pg.PlotWidget(background="w")
        self.plot = self.plotview.getPlotItem()

        axis_color = self.palette().color(QPalette.Text)
        axis_pen = QPen(axis_color)

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setLabel(self.x_label)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)

        axis = self.plot.getAxis("left")
        axis.setLabel(self.y_label)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)

        self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0),
                           disableAutoRange=True)

        self.mainArea.layout().addWidget(self.plotview)
Ejemplo n.º 15
0
    def __init__(self):
        super().__init__()

        self.results = None
        self.classifier_names = []
        self.perf_line = None
        self.colors = []
        self._curve_data = {}
        self._plot_curves = {}
        self._rocch = None
        self._perf_line = None
        self._tooltip_cache = None

        box = gui.vBox(self.controlArea, "Plot")
        self.target_cb = gui.comboBox(box,
                                      self,
                                      "target_index",
                                      label="Target",
                                      orientation=Qt.Horizontal,
                                      callback=self._on_target_changed,
                                      contentsLength=8,
                                      searchable=True)

        gui.widgetLabel(box, "Classifiers")
        line_height = 4 * QFontMetrics(self.font()).lineSpacing()
        self.classifiers_list_box = gui.listBox(
            box,
            self,
            "selected_classifiers",
            "classifier_names",
            selectionMode=QListView.MultiSelection,
            callback=self._on_classifiers_changed,
            sizeHint=QSize(0, line_height))

        abox = gui.vBox(self.controlArea, "Curves")
        gui.comboBox(abox,
                     self,
                     "roc_averaging",
                     items=[
                         "Merge Predictions from Folds", "Mean TP Rate",
                         "Mean TP and FP at Threshold",
                         "Show Individual Curves"
                     ],
                     callback=self._replot)

        gui.checkBox(abox,
                     self,
                     "display_convex_curve",
                     "Show convex ROC curves",
                     callback=self._replot)
        gui.checkBox(abox,
                     self,
                     "display_convex_hull",
                     "Show ROC convex hull",
                     callback=self._replot)

        box = gui.vBox(self.controlArea, "Analysis")

        gui.checkBox(box,
                     self,
                     "display_def_threshold",
                     "Default threshold (0.5) point",
                     callback=self._on_display_def_threshold_changed)

        gui.checkBox(box,
                     self,
                     "display_perf_line",
                     "Show performance line",
                     callback=self._on_display_perf_line_changed)
        grid = QGridLayout()
        gui.indentedBox(box, orientation=grid)

        sp = gui.spin(box,
                      self,
                      "fp_cost",
                      1,
                      1000,
                      10,
                      alignment=Qt.AlignRight,
                      callback=self._on_display_perf_line_changed)
        grid.addWidget(QLabel("FP Cost:"), 0, 0)
        grid.addWidget(sp, 0, 1)

        sp = gui.spin(box,
                      self,
                      "fn_cost",
                      1,
                      1000,
                      10,
                      alignment=Qt.AlignRight,
                      callback=self._on_display_perf_line_changed)
        grid.addWidget(QLabel("FN Cost:"))
        grid.addWidget(sp, 1, 1)
        self.target_prior_sp = gui.spin(box,
                                        self,
                                        "target_prior",
                                        1,
                                        99,
                                        alignment=Qt.AlignRight,
                                        callback=self._on_target_prior_changed)
        self.target_prior_sp.setSuffix(" %")
        self.target_prior_sp.addAction(QAction("Auto", sp))
        grid.addWidget(QLabel("Prior probability:"))
        grid.addWidget(self.target_prior_sp, 2, 1)

        self.plotview = pg.GraphicsView(background="w")
        self.plotview.setFrameStyle(QFrame.StyledPanel)
        self.plotview.scene().sigMouseMoved.connect(self._on_mouse_moved)

        self.plot = pg.PlotItem(enableMenu=False)
        self.plot.setMouseEnabled(False, False)
        self.plot.hideButtons()

        pen = QPen(self.palette().color(QPalette.Text))

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel("FP Rate (1-Specificity)")

        axis = self.plot.getAxis("left")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel("TP Rate (Sensitivity)")

        self.plot.showGrid(True, True, alpha=0.1)
        self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0), padding=0.05)

        self.plotview.setCentralItem(self.plot)
        self.mainArea.layout().addWidget(self.plotview)
Ejemplo n.º 16
0
class OWBoxPlot(widget.OWWidget):
    name = "Box Plot"
    description = "Visualize the distribution of feature values in a box plot."
    icon = "icons/BoxPlot.svg"
    priority = 100
    keywords = ["whisker"]

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

    class Outputs:
        selected_data = Output("Selected Data",
                               Orange.data.Table,
                               default=True)
        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table)

    class Warning(widget.OWWidget.Warning):
        no_vars = widget.Msg(
            "Data contains no categorical or numeric variables")

    buttons_area_orientation = None

    #: Comparison types for continuous variables
    CompareNone, CompareMedians, CompareMeans = 0, 1, 2

    settingsHandler = DomainContextHandler()
    # If this was a list, context handler would try to match its elements to
    # variable names!
    selection = ContextSetting((), schema_only=True)

    attribute = ContextSetting(None)
    order_by_importance = Setting(False)
    order_grouping_by_importance = Setting(False)
    group_var = ContextSetting(None)
    show_annotations = Setting(True)
    compare = Setting(CompareMeans)
    stattest = Setting(0)
    sig_threshold = Setting(0.05)
    stretched = Setting(True)
    show_labels = Setting(True)
    sort_freqs = Setting(False)

    _sorting_criteria_attrs = {
        CompareNone: "",
        CompareMedians: "median",
        CompareMeans: "mean"
    }

    _pen_axis_tick = QPen(Qt.white, 5)
    _pen_axis = QPen(Qt.darkGray, 3)
    _pen_median = QPen(QBrush(QColor(0xff, 0xff, 0x00)), 2)
    _pen_paramet = QPen(QBrush(QColor(0x33, 0x00, 0xff)), 2)
    _pen_dotted = QPen(QBrush(QColor(0x33, 0x00, 0xff)), 1)
    _pen_dotted.setStyle(Qt.DotLine)
    _post_line_pen = QPen(Qt.lightGray, 2)
    _post_grp_pen = QPen(Qt.lightGray, 4)
    for pen in (_pen_paramet, _pen_median, _pen_dotted, _pen_axis,
                _pen_axis_tick, _post_line_pen, _post_grp_pen):
        pen.setCosmetic(True)
        pen.setCapStyle(Qt.RoundCap)
        pen.setJoinStyle(Qt.RoundJoin)
    _pen_axis_tick.setCapStyle(Qt.FlatCap)

    _box_brush = QBrush(QColor(0x33, 0x88, 0xff, 0xc0))
    _attr_brush = QBrush(QColor(0x33, 0x00, 0xff))

    graph_name = "box_scene"

    def __init__(self):
        super().__init__()
        self._axis_font = QFont()
        self._axis_font.setPixelSize(12)
        self._label_font = QFont()
        self._label_font.setPixelSize(11)
        self.dataset = None
        self.stats = []
        self.dist = self.conts = None

        self.posthoc_lines = []

        self.label_txts = self.mean_labels = self.boxes = self.labels = \
            self.label_txts_all = self.attr_labels = self.order = []
        self.scale_x = 1
        self.scene_min_x = self.scene_max_x = self.scene_width = 0
        self.label_width = 0

        self.attrs = VariableListModel()
        sorted_model = SortProxyModel(sortRole=Qt.UserRole)
        sorted_model.setSourceModel(self.attrs)
        sorted_model.sort(0)
        box = gui.vBox(self.controlArea, "Variable")
        view = self.attr_list = ListViewSearch()
        view.setModel(sorted_model)
        view.setSelectionMode(view.SingleSelection)
        view.selectionModel().selectionChanged.connect(self.attr_changed)
        view.setMinimumSize(QSize(30, 30))
        # Any other policy than Ignored will let the QListBox's scrollbar
        # set the minimal height (see the penultimate paragraph of
        # http://doc.qt.io/qt-4.8/qabstractscrollarea.html#addScrollBarWidget)
        view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored)
        box.layout().addWidget(view)
        gui.checkBox(box,
                     self,
                     "order_by_importance",
                     "Order by relevance to subgroups",
                     tooltip="Order by 𝜒² or ANOVA over the subgroups",
                     callback=self.apply_attr_sorting)

        self.group_vars = VariableListModel(placeholder="None")
        sorted_model = SortProxyModel(sortRole=Qt.UserRole)
        sorted_model.setSourceModel(self.group_vars)
        sorted_model.sort(0)

        box = gui.vBox(self.controlArea, "Subgroups")
        view = self.group_list = ListViewSearch()
        view.setModel(sorted_model)
        view.selectionModel().selectionChanged.connect(self.grouping_changed)
        view.setMinimumSize(QSize(30, 30))
        # See the comment above
        view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored)
        box.layout().addWidget(view)
        gui.checkBox(box,
                     self,
                     "order_grouping_by_importance",
                     "Order by relevance to variable",
                     tooltip="Order by 𝜒² or ANOVA over the variable values",
                     callback=self.apply_group_sorting)

        # TODO: move Compare median/mean to grouping box
        # The vertical size policy is needed to let only the list views expand
        self.display_box = gui.vBox(self.controlArea,
                                    "Display",
                                    sizePolicy=(QSizePolicy.Minimum,
                                                QSizePolicy.Maximum))

        gui.checkBox(self.display_box,
                     self,
                     "show_annotations",
                     "Annotate",
                     callback=self.update_graph)
        self.compare_rb = gui.radioButtonsInBox(
            self.display_box,
            self,
            'compare',
            btnLabels=["No comparison", "Compare medians", "Compare means"],
            callback=self.update_graph)

        # The vertical size policy is needed to let only the list views expand
        self.stretching_box = box = gui.vBox(self.controlArea,
                                             box="Display",
                                             sizePolicy=(QSizePolicy.Minimum,
                                                         QSizePolicy.Fixed))
        self.stretching_box.sizeHint = self.display_box.sizeHint
        gui.checkBox(box,
                     self,
                     'stretched',
                     "Stretch bars",
                     callback=self.update_graph,
                     stateWhenDisabled=False)
        gui.checkBox(box,
                     self,
                     'show_labels',
                     "Show box labels",
                     callback=self.update_graph)
        self.sort_cb = gui.checkBox(box,
                                    self,
                                    'sort_freqs',
                                    "Sort by subgroup frequencies",
                                    callback=self.update_graph,
                                    stateWhenDisabled=False)

        gui.vBox(self.mainArea)
        self.box_scene = QGraphicsScene(self)
        self.box_scene.selectionChanged.connect(self.on_selection_changed)
        self.box_view = QGraphicsView(self.box_scene)
        self.box_view.setRenderHints(QPainter.Antialiasing
                                     | QPainter.TextAntialiasing
                                     | QPainter.SmoothPixmapTransform)
        self.box_view.viewport().installEventFilter(self)

        self.mainArea.layout().addWidget(self.box_view)

        self.stat_test = ""
        self.mainArea.setMinimumWidth(300)
        self.update_box_visibilities()

    def sizeHint(self):
        return QSize(900, 500)

    def eventFilter(self, obj, event):
        if obj is self.box_view.viewport() and \
                event.type() == QEvent.Resize:
            self.update_graph()
        return super().eventFilter(obj, event)

    @property
    def show_stretched(self):
        return self.stretched and self.group_var is not self.attribute

    def reset_attrs(self):
        domain = self.dataset.domain
        self.attrs[:] = [
            var for var in chain(domain.class_vars, domain.metas,
                                 domain.attributes)
            if var.is_primitive() and not var.attributes.get("hidden", False)
        ]

    def reset_groups(self):
        domain = self.dataset.domain
        self.group_vars[:] = [None] + [
            var for var in chain(domain.class_vars, domain.metas,
                                 domain.attributes)
            if var.is_discrete and not var.attributes.get("hidden", False)
        ]

    @Inputs.data
    def set_data(self, dataset):
        self.closeContext()
        self._reset_all_data()
        if dataset and not (len(dataset.domain.variables)
                            or any(var.is_primitive()
                                   for var in dataset.domain.metas)):
            self.Warning.no_vars()
            dataset = None

        self.dataset = dataset
        if dataset:
            self.reset_attrs()
            self.reset_groups()
            self._select_default_variables()
            self.openContext(self.dataset)
            self._set_list_view_selections()
            self.compute_box_data()
            self.apply_attr_sorting()
            self.apply_group_sorting()
            self.update_graph()
            self.select_box_items()

        self.update_box_visibilities()
        self.commit()

    def _reset_all_data(self):
        self.clear_scene()
        self.Warning.no_vars.clear()

        self.stats = []
        self.dist = self.conts = None
        self.group_var = None
        self.attribute = None
        self.stat_test = ""
        self.attrs[:] = []
        self.group_vars[:] = [None]
        self.selection = ()

    def _select_default_variables(self):
        # visualize first non-class variable, group by class (if present)
        domain = self.dataset.domain
        if len(self.attrs) > len(domain.class_vars):
            self.attribute = self.attrs[len(domain.class_vars)]
        elif self.attrs:
            self.attribute = self.attrs[0]

        if domain.class_var and domain.class_var.is_discrete:
            self.group_var = domain.class_var

    def _set_list_view_selections(self):
        for view, var, callback in ((self.attr_list, self.attribute,
                                     self.attr_changed),
                                    (self.group_list, self.group_var,
                                     self.grouping_changed)):
            src_model = view.model().sourceModel()
            if var not in src_model:
                continue
            sel_model = view.selectionModel()
            sel_model.selectionChanged.disconnect(callback)
            row = src_model.indexOf(var)
            index = view.model().index(row, 0)
            sel_model.select(index, sel_model.ClearAndSelect)
            self._ensure_selection_visible(view)
            sel_model.selectionChanged.connect(callback)

    def apply_attr_sorting(self):
        def compute_score(attr):
            # This function and the one in apply_group_sorting are similar, but
            # different in too many details, so they are kept as separate
            # functions.
            # If you discover a bug in this function, check the other one, too.
            if attr is group_var:
                return 3
            if attr.is_continuous:
                # One-way ANOVA
                col = data.get_column_view(attr)[0].astype(float)
                groups = (col[group_col == i] for i in range(n_groups))
                groups = (col[~np.isnan(col)] for col in groups)
                groups = [group for group in groups if len(group)]
                p = f_oneway(*groups)[1] if len(groups) > 1 else 2
            else:
                p = self._chi_square(group_var, attr)[1]
            if math.isnan(p):
                return 2
            return p

        data = self.dataset
        if data is None:
            return
        domain = data.domain
        group_var = self.group_var
        if self.order_by_importance and group_var is not None:
            n_groups = len(group_var.values)
            group_col = data.get_column_view(group_var)[0] if \
                domain.has_continuous_attributes(
                    include_class=True, include_metas=True) else None
            self._sort_list(self.attrs, self.attr_list, compute_score)
        else:
            self._sort_list(self.attrs, self.attr_list, None)

    def apply_group_sorting(self):
        def compute_stat(group):
            # This function and the one in apply_attr_sorting are similar, but
            # different in too many details, so they are kept as separate
            # functions.
            # If you discover a bug in this function, check the other one, too.
            if group is attr:
                return 3
            if group is None:
                return -1
            if attr.is_continuous:
                group_col = data.get_column_view(group)[0].astype(float)
                groups = (attr_col[group_col == i]
                          for i in range(len(group.values)))
                groups = (col[~np.isnan(col)] for col in groups)
                groups = [group for group in groups if len(group)]
                p = f_oneway(*groups)[1] if len(groups) > 1 else 2
            else:
                p = self._chi_square(group, attr)[1]
            if math.isnan(p):
                return 2
            return p

        data = self.dataset
        if data is None:
            return
        attr = self.attribute
        if self.order_grouping_by_importance:
            if attr.is_continuous:
                attr_col = data.get_column_view(attr)[0].astype(float)
            self._sort_list(self.group_vars, self.group_list, compute_stat)
        else:
            self._sort_list(self.group_vars, self.group_list, None)

    def _sort_list(self, source_model, view, key=None):
        if key is None:
            c = count()

            def key(_):  # pylint: disable=function-redefined
                return next(c)

        for i, attr in enumerate(source_model):
            source_model.setData(source_model.index(i), key(attr), Qt.UserRole)
        self._ensure_selection_visible(view)

    @staticmethod
    def _ensure_selection_visible(view):
        selection = view.selectedIndexes()
        if len(selection) == 1:
            view.scrollTo(selection[0])

    def _chi_square(self, group_var, attr):
        # Chi-square with the given distribution into groups
        if not attr.values or not group_var.values:
            return 0, 2, 0
        observed = np.array(
            contingency.get_contingency(self.dataset, group_var, attr))
        observed = observed[observed.sum(axis=1) != 0, :]
        observed = observed[:, observed.sum(axis=0) != 0]
        if min(observed.shape) < 2:
            return 0, 2, 0
        return chi2_contingency(observed)[:3]

    def grouping_changed(self, selected):
        if not selected:
            return  # should never come here
        self.group_var = selected.indexes()[0].data(gui.TableVariable)
        self._variables_changed(self.apply_attr_sorting)

    def attr_changed(self, selected):
        if not selected:
            return  # should never come here
        self.attribute = selected.indexes()[0].data(gui.TableVariable)
        self._variables_changed(self.apply_group_sorting)

    def _variables_changed(self, sorting):
        self.selection = ()
        self.compute_box_data()
        sorting()
        self.update_graph()
        self.update_box_visibilities()
        self.commit()

    def update_graph(self):
        pending_selection = self.selection
        self.box_scene.selectionChanged.disconnect(self.on_selection_changed)
        try:  # not for exceptions, just to reconnect after all possible paths
            self.clear_scene()

            if self.dataset is None or self.attribute is None:
                return

            if self.attribute.is_continuous:
                self._display_changed_cont()
            else:
                self._display_changed_disc()
            self.selection = pending_selection
            self.draw_stat()
            self.select_box_items()

            if self.attribute.is_continuous:
                heights = 90 if self.show_annotations else 60
                self.box_view.centerOn(
                    self.scene_min_x + self.scene_width / 2,
                    -30 - len(self.stats) * heights / 2 + 45)
            else:
                self.box_view.centerOn(self.scene_width / 2,
                                       -30 - len(self.boxes) * 40 / 2 + 45)
        finally:
            self.box_scene.selectionChanged.connect(self.on_selection_changed)

    def select_box_items(self):
        selection = set(self.selection)
        for box in self.box_scene.items():
            if isinstance(box, FilterGraphicsRectItem):
                box.setSelected(box.data_range in selection)

    def compute_box_data(self):
        attr = self.attribute
        if not attr:
            return
        dataset = self.dataset
        if dataset is None \
                or not attr.is_continuous and not attr.values \
                or self.group_var and not self.group_var.values:
            self.stats = []
            self.dist = self.conts = None
            return
        if self.group_var:
            self.dist = None
            self.conts = contingency.get_contingency(dataset, attr,
                                                     self.group_var)
            missing_val_str = f"missing '{self.group_var.name}'"
            group_var_labels = self.group_var.values + ("", )
            if self.attribute.is_continuous:
                stats, label_texts = [], []
                for cont, value in zip(self.conts.array_with_unknowns,
                                       group_var_labels):
                    if np.sum(cont[1]):
                        stats.append(BoxData(cont, value))
                        label_texts.append(value or missing_val_str)
                self.stats = stats
                self.label_txts_all = label_texts
            else:
                self.label_txts_all = [
                    v or missing_val_str for v, c in zip(
                        group_var_labels, self.conts.array_with_unknowns)
                    if np.sum(c) > 0
                ]
        else:
            self.dist = distribution.get_distribution(dataset, attr)
            self.conts = None
            if self.attribute.is_continuous:
                self.stats = [BoxData(self.dist, None)]
            self.label_txts_all = [""]
        self.label_txts = [
            txts for stat, txts in zip(self.stats, self.label_txts_all)
            if stat.n > 0
        ]
        self.stats = [stat for stat in self.stats if stat.n > 0]

    def update_box_visibilities(self):
        self.controls.stretched.setDisabled(self.group_var is self.attribute)

        if not self.attribute:
            self.stretching_box.hide()
            self.display_box.hide()
        elif self.attribute.is_continuous:
            self.stretching_box.hide()
            self.display_box.show()
            self.compare_rb.setEnabled(self.group_var is not None)
        else:
            self.stretching_box.show()
            self.display_box.hide()
            self.sort_cb.setEnabled(self.group_var is not None)

    def clear_scene(self):
        self.box_scene.clear()
        self.box_view.viewport().update()
        self.attr_labels = []
        self.labels = []
        self.boxes = []
        self.mean_labels = []
        self.posthoc_lines = []

    def _display_changed_cont(self):
        self.mean_labels = [
            self.mean_label(stat, self.attribute, lab)
            for stat, lab in zip(self.stats, self.label_txts)
        ]
        self.draw_axis()
        self.boxes = [self.box_group(stat) for stat in self.stats]
        self.labels = [
            self.label_group(stat, self.attribute, mean_lab)
            for stat, mean_lab in zip(self.stats, self.mean_labels)
        ]
        self.attr_labels = [
            QGraphicsSimpleTextItem(lab) for lab in self.label_txts
        ]
        for it in chain(self.labels, self.attr_labels):
            self.box_scene.addItem(it)

        self.order = list(range(len(self.stats)))
        criterion = self._sorting_criteria_attrs[self.compare]
        if criterion:
            vals = [getattr(stat, criterion) for stat in self.stats]
            overmax = max((val for val in vals if val is not None), default=0) \
                      + 1
            vals = [val if val is not None else overmax for val in vals]
            self.order = sorted(self.order, key=vals.__getitem__)

        heights = 90 if self.show_annotations else 60

        for row, box_index in enumerate(self.order):
            y = (-len(self.stats) + row) * heights + 10
            for item in self.boxes[box_index]:
                self.box_scene.addItem(item)
                item.setY(y)
            labels = self.labels[box_index]

            if self.show_annotations:
                labels.show()
                labels.setY(y)
            else:
                labels.hide()

            label = self.attr_labels[box_index]
            label.setY(y - 15 - label.boundingRect().height())
            if self.show_annotations:
                label.hide()
            else:
                stat = self.stats[box_index]

                if self.compare == OWBoxPlot.CompareMedians and \
                        stat.median is not None:
                    pos = stat.median + 5 / self.scale_x
                elif self.compare == OWBoxPlot.CompareMeans or stat.q25 is None:
                    pos = stat.mean + 5 / self.scale_x
                else:
                    pos = stat.q25
                label.setX(pos * self.scale_x)
                label.show()

        r = QRectF(self.scene_min_x, -30 - len(self.stats) * heights,
                   self.scene_width,
                   len(self.stats) * heights + 90)
        self.box_scene.setSceneRect(r)

        self._compute_tests_cont()
        self._show_posthoc()

    def _display_changed_disc(self):
        self.clear_scene()
        self.attr_labels = [
            QGraphicsSimpleTextItem(lab) for lab in self.label_txts_all
        ]

        if not self.show_stretched:
            if self.group_var:
                self.labels = [
                    QGraphicsTextItem("{}".format(int(sum(cont))))
                    for cont in self.conts.array_with_unknowns
                    if np.sum(cont) > 0
                ]
            else:
                self.labels = [QGraphicsTextItem(str(int(sum(self.dist))))]

        self.order = list(range(len(self.attr_labels)))

        self.draw_axis_disc()
        if self.group_var:
            conts = self.conts.array_with_unknowns
            self.boxes = [
                self.strudel(cont, val)
                for cont, val in zip(conts, self.group_var.values + ("", ))
                if np.sum(cont) > 0
            ]
            sums_ = np.sum(conts, axis=1)
            sums_ = sums_[sums_ > 0]  # only bars with sum > 0 are shown

            if self.sort_freqs:
                # pylint: disable=invalid-unary-operand-type
                self.order = sorted(self.order, key=(-sums_).__getitem__)
        else:
            conts = self.dist.array_with_unknowns
            self.boxes = [self.strudel(conts)]
            sums_ = [np.sum(conts)]

        for row, box_index in enumerate(self.order):
            y = (-len(self.boxes) + row) * 40 + 10
            box = self.boxes[box_index]
            bars, labels = box[::2], box[1::2]

            self.__draw_group_labels(y, box_index)
            if not self.show_stretched:
                self.__draw_row_counts(y, self.labels[box_index],
                                       sums_[box_index])
            if self.show_labels and self.attribute is not self.group_var:
                self.__draw_bar_labels(y, bars, labels)
            self.__draw_bars(y, bars)

        self.box_scene.setSceneRect(-self.label_width - 5,
                                    -30 - len(self.boxes) * 40,
                                    self.scene_width,
                                    len(self.boxes * 40) + 90)
        self._compute_tests_disc()

    def __draw_group_labels(self, y, row):
        """Draw group labels

        Parameters
        ----------
        y: int
            vertical offset of bars
        row: int
            row index
        """
        label = self.attr_labels[row]
        b = label.boundingRect()
        label.setPos(-b.width() - 10, y - b.height() / 2)
        self.box_scene.addItem(label)

    def __draw_row_counts(self, y, label, row_sum_):
        """Draw row counts

        Parameters
        ----------
        y: int
            vertical offset of bars
        label: QGraphicsSimpleTextItem
            Label for group
        row_sum_: int
            Sum for the group
        """
        assert not self.attribute.is_continuous
        b = label.boundingRect()
        right = self.scale_x * row_sum_
        label.setPos(right + 10, y - b.height() / 2)
        self.box_scene.addItem(label)

    def __draw_bar_labels(self, y, bars, labels):
        """Draw bar labels

        Parameters
        ----------
        y: int
            vertical offset of bars
        bars: List[FilterGraphicsRectItem]
            list of bars being drawn
        labels: List[QGraphicsTextItem]
            list of labels for corresponding bars
        """
        for text_item, bar_part in zip(labels, bars):
            label = self.Label(text_item.toPlainText())
            label.setPos(bar_part.boundingRect().x(),
                         y - label.boundingRect().height() - 8)
            label.setMaxWidth(bar_part.boundingRect().width())
            self.box_scene.addItem(label)

    def __draw_bars(self, y, bars):
        """Draw bars

        Parameters
        ----------
        y: int
            vertical offset of bars

        bars: List[FilterGraphicsRectItem]
            list of bars to draw
        """
        for item in bars:
            item.setPos(0, y)
            self.box_scene.addItem(item)

    # noinspection PyPep8Naming
    def _compute_tests_cont(self):
        # The t-test and ANOVA are implemented here since they efficiently use
        # the widget-specific data in self.stats.
        # The non-parametric tests can't do this, so we use statistics.tests

        # pylint: disable=comparison-with-itself
        def stat_ttest():
            d1, d2 = self.stats
            if d1.n < 2 or d2.n < 2:
                return np.nan, np.nan
            pooled_var = d1.var / d1.n + d2.var / d2.n
            # pylint: disable=comparison-with-itself
            if pooled_var == 0 or np.isnan(pooled_var):
                return np.nan, np.nan
            df = pooled_var ** 2 / \
                ((d1.var / d1.n) ** 2 / (d1.n - 1) +
                 (d2.var / d2.n) ** 2 / (d2.n - 1))
            t = abs(d1.mean - d2.mean) / math.sqrt(pooled_var)
            p = 2 * (1 - scipy.special.stdtr(df, t))
            return t, p

        # TODO: Check this function
        # noinspection PyPep8Naming
        def stat_ANOVA():
            if any(stat.n == 0 for stat in self.stats):
                return np.nan, np.nan
            n = sum(stat.n for stat in self.stats)
            grand_avg = sum(stat.n * stat.mean for stat in self.stats) / n
            var_between = sum(stat.n * (stat.mean - grand_avg)**2
                              for stat in self.stats)
            df_between = len(self.stats) - 1

            var_within = sum(stat.n * stat.var for stat in self.stats)
            df_within = n - len(self.stats)
            if var_within == 0 or df_within == 0 or df_between == 0:
                return np.nan, np.nan
            F = (var_between / df_between) / (var_within / df_within)
            p = 1 - scipy.special.fdtr(df_between, df_within, F)
            return F, p

        n = len(self.dataset)
        if self.compare == OWBoxPlot.CompareNone or len(self.stats) < 2:
            t = ""
        elif any(s.n <= 1 for s in self.stats):
            t = "At least one group has just one instance, " \
                "cannot compute significance"
        elif len(self.stats) == 2:
            if self.compare == OWBoxPlot.CompareMedians:
                t = ""
                # z, p = tests.wilcoxon_rank_sum(
                #    self.stats[0].dist, self.stats[1].dist)
                # t = "Mann-Whitney's z: %.1f (p=%.3f)" % (z, p)
            else:
                t, p = stat_ttest()
                t = "" if np.isnan(
                    t) else f"Student's t: {t:.3f} (p={p:.3f}, N={n})"
        else:
            if self.compare == OWBoxPlot.CompareMedians:
                t = ""
                # U, p = -1, -1
                # t = "Kruskal Wallis's U: %.1f (p=%.3f)" % (U, p)
            else:
                F, p = stat_ANOVA()
                t = "" if np.isnan(F) else f"ANOVA: {F:.3f} (p={p:.3f}, N={n})"
        self.stat_test = t

    def _compute_tests_disc(self):
        if self.group_var is None or self.attribute is None:
            self.stat_test = ""
        else:
            chi, p, dof = self._chi_square(self.group_var, self.attribute)
            if np.isnan(p):
                self.stat_test = ""
            else:
                self.stat_test = f"χ²: {chi:.2f} (p={p:.3f}, dof={dof})"

    def mean_label(self, stat, attr, val_name):
        label = QGraphicsItemGroup()
        t = QGraphicsSimpleTextItem(attr.str_val(stat.mean), label)
        t.setFont(self._label_font)
        bbox = t.boundingRect()
        w2, h = bbox.width() / 2, bbox.height()
        t.setPos(-w2, -h)
        tpm = QGraphicsSimpleTextItem(
            " \u00b1 " + "%.*f" % (attr.number_of_decimals + 1, stat.dev),
            label)
        tpm.setFont(self._label_font)
        tpm.setPos(w2, -h)
        if val_name:
            vnm = QGraphicsSimpleTextItem(val_name + ": ", label)
            vnm.setFont(self._label_font)
            vnm.setBrush(self._attr_brush)
            vb = vnm.boundingRect()
            label.min_x = -w2 - vb.width()
            vnm.setPos(label.min_x, -h)
        else:
            label.min_x = -w2
        return label

    def draw_axis(self):
        """Draw the horizontal axis and sets self.scale_x"""
        misssing_stats = not self.stats
        stats = self.stats or [BoxData(np.array([[0.], [1.]]), self.attribute)]
        mean_labels = self.mean_labels or [
            self.mean_label(stats[0], self.attribute, "")
        ]
        bottom = min(stat.a_min for stat in stats)
        top = max(stat.a_max for stat in stats)

        first_val, step = compute_scale(bottom, top)
        while bottom <= first_val:
            first_val -= step
        bottom = first_val
        no_ticks = math.ceil((top - first_val) / step) + 1
        top = max(top, first_val + no_ticks * step)

        gbottom = min(bottom, min(stat.mean - stat.dev for stat in stats))
        gtop = max(top, max(stat.mean + stat.dev for stat in stats))

        bv = self.box_view
        viewrect = bv.viewport().rect().adjusted(15, 15, -15, -30)
        self.scale_x = scale_x = viewrect.width() / (gtop - gbottom)

        # In principle we should repeat this until convergence since the new
        # scaling is too conservative. (No chance am I doing this.)
        mlb = min(stat.mean + mean_lab.min_x / scale_x
                  for stat, mean_lab in zip(stats, mean_labels))
        if mlb < gbottom:
            gbottom = mlb
            self.scale_x = scale_x = viewrect.width() / (gtop - gbottom)

        self.scene_min_x = gbottom * scale_x
        self.scene_max_x = gtop * scale_x
        self.scene_width = self.scene_max_x - self.scene_min_x

        val = first_val
        last_text = self.scene_min_x
        while True:
            l = self.box_scene.addLine(val * scale_x, -1, val * scale_x, 1,
                                       self._pen_axis_tick)
            l.setZValue(100)
            t = QGraphicsSimpleTextItem(
                self.attribute.str_val(val) if not misssing_stats else "?")
            t.setFont(self._axis_font)
            t.setFlag(QGraphicsItem.ItemIgnoresTransformations)
            r = t.boundingRect()
            x_start = val * scale_x - r.width() / 2
            x_finish = x_start + r.width()
            if x_start > last_text + 10 and x_finish < self.scene_max_x:
                t.setPos(x_start, 8)
                self.box_scene.addItem(t)
                last_text = x_finish
            if val >= top:
                break
            val += step
        self.box_scene.addLine(bottom * scale_x - 4, 0, top * scale_x + 4, 0,
                               self._pen_axis)

    def draw_stat(self):
        if self.stat_test:
            label = QGraphicsSimpleTextItem(self.stat_test)
            brect = self.box_scene.sceneRect()
            label.setPos(brect.center().x() - label.boundingRect().width() / 2,
                         8 + self._axis_font.pixelSize() * 2)
            label.setFlag(QGraphicsItem.ItemIgnoresTransformations)
            self.box_scene.addItem(label)

    def draw_axis_disc(self):
        """
        Draw the horizontal axis and sets self.scale_x for discrete attributes
        """
        assert not self.attribute.is_continuous
        if self.show_stretched:
            if not self.attr_labels:
                return
            step = steps = 10
        else:
            if self.group_var:
                max_box = max(
                    float(np.sum(dist))
                    for dist in self.conts.array_with_unknowns)
            else:
                max_box = float(np.sum(self.dist.array_with_unknowns))
            if max_box == 0:
                self.scale_x = 1
                return
            _, step = compute_scale(0, max_box)
            step = int(step) if step > 1 else 1
            steps = int(math.ceil(max_box / step))
        max_box = step * steps

        bv = self.box_view
        viewrect = bv.viewport().rect().adjusted(15, 15, -15, -30)
        self.scene_width = viewrect.width()

        lab_width = max(lab.boundingRect().width() for lab in self.attr_labels)
        lab_width = max(lab_width, 40)
        lab_width = min(lab_width, self.scene_width / 3)
        self.label_width = lab_width

        right_offset = 0  # offset for the right label
        if not self.show_stretched and self.labels:
            if self.group_var:
                rows = list(zip(self.conts.array_with_unknowns, self.labels))
            else:
                rows = [(self.dist, self.labels[0])]
            # available space left of the 'group labels'
            available = self.scene_width - lab_width - 10
            scale_x = (available - right_offset) / max_box
            max_right = max(
                sum(dist) * scale_x + 10 + lbl.boundingRect().width()
                for dist, lbl in rows)
            right_offset = max(0, max_right - max_box * scale_x)

        self.scale_x = scale_x = \
            (self.scene_width - lab_width - 10 - right_offset) / max_box

        self.box_scene.addLine(0, 0, max_box * scale_x, 0, self._pen_axis)
        for val in range(0, step * steps + 1, step):
            l = self.box_scene.addLine(val * scale_x, -1, val * scale_x, 1,
                                       self._pen_axis_tick)
            l.setZValue(100)
            t = self.box_scene.addSimpleText(str(val), self._axis_font)
            t.setPos(val * scale_x - t.boundingRect().width() / 2, 8)
        if self.show_stretched:
            self.scale_x *= 100

    def label_group(self, stat, attr, mean_lab):
        def centered_text(val, pos):
            t = QGraphicsSimpleTextItem(attr.str_val(val), labels)
            t.setFont(self._label_font)
            bbox = t.boundingRect()
            t.setPos(pos - bbox.width() / 2, 22)
            return t

        def line(x, down=1):
            QGraphicsLineItem(x, 12 * down, x, 20 * down, labels)

        def move_label(label, frm, to):
            label.setX(to)
            to += t_box.width() / 2
            path = QPainterPath()
            path.lineTo(0, 4)
            path.lineTo(to - frm, 4)
            path.lineTo(to - frm, 8)
            p = QGraphicsPathItem(path)
            p.setPos(frm, 12)
            labels.addToGroup(p)

        labels = QGraphicsItemGroup()

        labels.addToGroup(mean_lab)
        m = stat.mean * self.scale_x
        mean_lab.setPos(m, -22)
        line(m, -1)

        if stat.median is not None:
            msc = stat.median * self.scale_x
            med_t = centered_text(stat.median, msc)
            med_box_width2 = med_t.boundingRect().width() / 2
            line(msc)

        if stat.q25 is not None:
            x = stat.q25 * self.scale_x
            t = centered_text(stat.q25, x)
            t_box = t.boundingRect()
            med_left = msc - med_box_width2
            if x + t_box.width() / 2 >= med_left - 5:
                move_label(t, x, med_left - t_box.width() - 5)
            else:
                line(x)

        if stat.q75 is not None:
            x = stat.q75 * self.scale_x
            t = centered_text(stat.q75, x)
            t_box = t.boundingRect()
            med_right = msc + med_box_width2
            if x - t_box.width() / 2 <= med_right + 5:
                move_label(t, x, med_right + 5)
            else:
                line(x)

        return labels

    def box_group(self, stat, height=20):
        def line(x0, y0, x1, y1, *args):
            return QGraphicsLineItem(x0 * scale_x, y0, x1 * scale_x, y1, *args)

        scale_x = self.scale_x
        box = []
        whisker1 = line(stat.a_min, -1.5, stat.a_min, 1.5)
        whisker2 = line(stat.a_max, -1.5, stat.a_max, 1.5)
        vert_line = line(stat.a_min, 0, stat.a_max, 0)
        mean_line = line(stat.mean, -height / 3, stat.mean, height / 3)
        for it in (whisker1, whisker2, mean_line):
            it.setPen(self._pen_paramet)
        vert_line.setPen(self._pen_dotted)
        var_line = line(stat.mean - stat.dev, 0, stat.mean + stat.dev, 0)
        var_line.setPen(self._pen_paramet)
        box.extend([whisker1, whisker2, vert_line, mean_line, var_line])
        if stat.q25 is not None or stat.q75 is not None:
            # if any of them is None it means that its value is equal to median
            box_from = stat.q25 or stat.median
            box_to = stat.q75 or stat.median
            mbox = FilterGraphicsRectItem(stat.data_range, box_from * scale_x,
                                          -height / 2,
                                          (box_to - box_from) * scale_x,
                                          height)
            mbox.setBrush(self._box_brush)
            mbox.setPen(QPen(Qt.NoPen))
            mbox.setZValue(-200)
            box.append(mbox)

        if stat.median is not None:
            median_line = line(stat.median, -height / 2, stat.median,
                               height / 2)
            median_line.setPen(self._pen_median)
            median_line.setZValue(-150)
            box.append(median_line)

        return box

    def strudel(self, dist, group_val=None):
        attr = self.attribute
        ss = np.sum(dist)
        box = []
        if ss < 1e-6:
            cond = DiscDataRange(None, group_val)
            box.append(FilterGraphicsRectItem(cond, 0, -10, 1, 10))
        cum = 0
        missing_val_str = f"missing '{attr.name}'"
        values = attr.values + ("", )
        colors = attr.palette.qcolors_w_nan
        total = sum(dist)
        for freq, value, color in zip(dist, values, colors):
            if freq < 1e-6:
                continue
            v = freq
            if self.show_stretched:
                v /= ss
            v *= self.scale_x
            cond = DiscDataRange(value, group_val)
            rect = FilterGraphicsRectItem(cond, cum + 1, -6, v - 2, 12)
            rect.setBrush(QBrush(color))
            rect.setPen(QPen(Qt.NoPen))
            value = value or missing_val_str
            if self.show_stretched:
                tooltip = f"{value}: {100 * freq / total:.2f}%"
            else:
                tooltip = f"{value}: ({int(freq)})"
            rect.setToolTip(tooltip)
            text = QGraphicsTextItem(value)
            box.append(rect)
            box.append(text)
            cum += v
        return box

    def on_selection_changed(self):
        self.selection = tuple(item.data_range
                               for item in self.box_scene.selectedItems()
                               if item.data_range)
        self.commit()

    def commit(self):
        conditions = self._gather_conditions()
        if conditions:
            selected = Values(conditions, conjunction=False)(self.dataset)
            selection = np.in1d(self.dataset.ids,
                                selected.ids,
                                assume_unique=True).nonzero()[0]
        else:
            selected, selection = None, []
        self.Outputs.selected_data.send(selected)
        self.Outputs.annotated_data.send(
            create_annotated_table(self.dataset, selection))

    def _gather_conditions(self):
        conditions = []
        attr = self.attribute
        group_attr = self.group_var
        for data_range in self.selection:
            if attr.is_discrete:
                # If some value was removed from the data (in case settings are
                # loaded from a scheme), do not include the corresponding
                # filter; this is appropriate since data with such value does
                # not exist anyway
                if not data_range.value:
                    condition = IsDefined([attr], negate=True)
                elif data_range.value not in attr.values:
                    continue
                else:
                    condition = FilterDiscrete(attr, [data_range.value])
            else:
                condition = FilterContinuous(attr, FilterContinuous.Between,
                                             data_range.low, data_range.high)
            if data_range.group_value:
                if not data_range.group_value:
                    grp_filter = IsDefined([group_attr], negate=True)
                elif data_range.group_value not in group_attr.values:
                    continue
                else:
                    grp_filter = FilterDiscrete(group_attr,
                                                [data_range.group_value])
                condition = Values([condition, grp_filter], conjunction=True)
            conditions.append(condition)
        return conditions

    def _show_posthoc(self):
        def line(y0, y1):
            it = self.box_scene.addLine(x, y0, x, y1, self._post_line_pen)
            it.setZValue(-100)
            self.posthoc_lines.append(it)

        while self.posthoc_lines:
            self.box_scene.removeItem(self.posthoc_lines.pop())

        if self.compare == OWBoxPlot.CompareNone or len(self.stats) < 2:
            return

        if self.compare == OWBoxPlot.CompareMedians:
            crit_line = "median"
        else:
            crit_line = "mean"

        xs = []

        height = 90 if self.show_annotations else 60

        y_up = -len(self.stats) * height + 10
        for pos, box_index in enumerate(self.order):
            stat = self.stats[box_index]
            x = getattr(stat, crit_line)
            if x is None:
                continue
            x *= self.scale_x
            xs.append(x * self.scale_x)
            by = y_up + pos * height
            line(by + 12, 0)

        used_to = []
        last_to = to = 0
        for frm, frm_x in enumerate(xs[:-1]):
            for to in range(frm + 1, len(xs)):
                if xs[to] - frm_x > 1.5:
                    to -= 1
                    break
            if to in (last_to, frm):
                continue
            for rowi, used in enumerate(used_to):
                if used < frm:
                    used_to[rowi] = to
                    break
            else:
                rowi = len(used_to)
                used_to.append(to)
            y = -6 - rowi * 6
            it = self.box_scene.addLine(frm_x - 2, y, xs[to] + 2, y,
                                        self._post_grp_pen)
            self.posthoc_lines.append(it)
            last_to = to

    def get_widget_name_extension(self):
        return self.attribute.name if self.attribute else None

    def send_report(self):
        self.report_plot()
        text = ""
        if self.attribute:
            text += "Box plot for attribute '{}' ".format(self.attribute.name)
        if self.group_var:
            text += "grouped by '{}'".format(self.group_var.name)
        if text:
            self.report_caption(text)

    class Label(QGraphicsSimpleTextItem):
        """Boxplot Label with settable maxWidth"""
        # Minimum width to display label text
        MIN_LABEL_WIDTH = 25

        # padding bellow the text
        PADDING = 3

        __max_width = None

        def maxWidth(self):
            return self.__max_width

        def setMaxWidth(self, max_width):
            self.__max_width = max_width

        def paint(self, painter, option, widget):
            """Overrides QGraphicsSimpleTextItem.paint

            If label text is too long, it is elided
            to fit into the allowed region
            """
            if self.__max_width is None:
                width = option.rect.width()
            else:
                width = self.__max_width

            if width < self.MIN_LABEL_WIDTH:
                # if space is too narrow, no label
                return

            fm = painter.fontMetrics()
            text = fm.elidedText(self.text(), Qt.ElideRight, width)
            painter.drawText(
                option.rect.x(),
                option.rect.y() + self.boundingRect().height() - self.PADDING,
                text)
Ejemplo n.º 17
0
    def __init__(self):
        super().__init__()

        self.results = None
        self.classifier_names = []
        self.perf_line = None
        self.colors = []
        self._curve_data = {}
        self._plot_curves = {}
        self._rocch = None
        self._perf_line = None

        box = gui.vBox(self.controlArea, "Plot")
        tbox = gui.vBox(box, "Target Class")
        tbox.setFlat(True)

        self.target_cb = gui.comboBox(tbox,
                                      self,
                                      "target_index",
                                      callback=self._on_target_changed,
                                      contentsLength=8)

        cbox = gui.vBox(box, "Classifiers")
        cbox.setFlat(True)
        self.classifiers_list_box = gui.listBox(
            cbox,
            self,
            "selected_classifiers",
            "classifier_names",
            selectionMode=QListView.MultiSelection,
            callback=self._on_classifiers_changed)

        abox = gui.vBox(box, "Combine ROC Curves From Folds")
        abox.setFlat(True)
        gui.comboBox(abox,
                     self,
                     "roc_averaging",
                     items=[
                         "Merge Predictions from Folds", "Mean TP Rate",
                         "Mean TP and FP at Threshold",
                         "Show Individual Curves"
                     ],
                     callback=self._replot)

        hbox = gui.vBox(box, "ROC Convex Hull")
        hbox.setFlat(True)
        gui.checkBox(hbox,
                     self,
                     "display_convex_curve",
                     "Show convex ROC curves",
                     callback=self._replot)
        gui.checkBox(hbox,
                     self,
                     "display_convex_hull",
                     "Show ROC convex hull",
                     callback=self._replot)

        box = gui.vBox(self.controlArea, "Analysis")

        gui.checkBox(box,
                     self,
                     "display_def_threshold",
                     "Default threshold (0.5) point",
                     callback=self._on_display_def_threshold_changed)

        gui.checkBox(box,
                     self,
                     "display_perf_line",
                     "Show performance line",
                     callback=self._on_display_perf_line_changed)
        grid = QGridLayout()
        ibox = gui.indentedBox(box, orientation=grid)

        sp = gui.spin(box,
                      self,
                      "fp_cost",
                      1,
                      1000,
                      10,
                      callback=self._on_display_perf_line_changed)
        grid.addWidget(QLabel("FP Cost:"), 0, 0)
        grid.addWidget(sp, 0, 1)

        sp = gui.spin(box,
                      self,
                      "fn_cost",
                      1,
                      1000,
                      10,
                      callback=self._on_display_perf_line_changed)
        grid.addWidget(QLabel("FN Cost:"))
        grid.addWidget(sp, 1, 1)
        sp = gui.spin(box,
                      self,
                      "target_prior",
                      1,
                      99,
                      callback=self._on_display_perf_line_changed)
        sp.setSuffix("%")
        sp.addAction(QAction("Auto", sp))
        grid.addWidget(QLabel("Prior target class probability:"))
        grid.addWidget(sp, 2, 1)

        self.plotview = pg.GraphicsView(background="w")
        self.plotview.setFrameStyle(QFrame.StyledPanel)

        self.plot = pg.PlotItem()
        self.plot.getViewBox().setMenuEnabled(False)
        self.plot.getViewBox().setMouseEnabled(False, False)

        pen = QPen(self.palette().color(QPalette.Text))

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel("FP Rate (1-Specificity)")

        axis = self.plot.getAxis("left")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel("TP Rate (Sensitivity)")

        self.plot.showGrid(True, True, alpha=0.1)
        self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0))

        self.plotview.setCentralItem(self.plot)
        self.mainArea.layout().addWidget(self.plotview)