示例#1
0
    def test_read_only(self):
        model = DomainModel()
        domain = Domain([ContinuousVariable(x) for x in "abc"])
        model.set_domain(domain)
        index = model.index(0, 0)

        self.assertRaises(TypeError, model.append, 42)
        self.assertRaises(TypeError, model.extend, [42])
        self.assertRaises(TypeError, model.insert, 0, 42)
        self.assertRaises(TypeError, model.remove, 0)
        self.assertRaises(TypeError, model.pop)
        self.assertRaises(TypeError, model.clear)
        self.assertRaises(TypeError, model.reverse)
        self.assertRaises(TypeError, model.sort)
        with self.assertRaises(TypeError):
            model[0] = 1
        with self.assertRaises(TypeError):
            del model[0]

        self.assertFalse(model.setData(index, domain[0], Qt.EditRole))
        self.assertTrue(model.setData(index, "foo", Qt.ToolTipRole))

        self.assertFalse(
            model.setItemData(index, {
                Qt.EditRole: domain[0],
                Qt.ToolTipRole: "foo"
            }))
        self.assertTrue(model.setItemData(index, {Qt.ToolTipRole: "foo"}))

        self.assertFalse(model.insertRows(0, 1))
        self.assertSequenceEqual(model, domain)
        self.assertFalse(model.removeRows(0, 1))
        self.assertSequenceEqual(model, domain)
示例#2
0
    def test_read_only(self):
        model = DomainModel()
        domain = Domain([ContinuousVariable(x) for x in "abc"])
        model.set_domain(domain)
        index = model.index(0, 0)

        self.assertRaises(TypeError, model.append, 42)
        self.assertRaises(TypeError, model.extend, [42])
        self.assertRaises(TypeError, model.insert, 0, 42)
        self.assertRaises(TypeError, model.remove, 0)
        self.assertRaises(TypeError, model.pop)
        self.assertRaises(TypeError, model.clear)
        self.assertRaises(TypeError, model.reverse)
        self.assertRaises(TypeError, model.sort)
        with self.assertRaises(TypeError):
            model[0] = 1
        with self.assertRaises(TypeError):
            del model[0]

        self.assertRaises(TypeError, model.setData, index, domain[0])
        self.assertTrue(model.setData(index, "foo", Qt.ToolTipRole))

        self.assertRaises(TypeError, model.setItemData, index,
                          {Qt.EditRole: domain[0], Qt.ToolTipRole: "foo"})
        self.assertTrue(model.setItemData(index, {Qt.ToolTipRole: "foo"}))

        self.assertRaises(TypeError, model.insertRows, 0, 0)
        self.assertRaises(TypeError, model.removeRows, 0, 0)
示例#3
0
class OWGroupBy(OWWidget, ConcurrentWidgetMixin):
    name = "Group by"
    description = ""
    category = "Transform"
    icon = "icons/GroupBy.svg"
    keywords = ["aggregate", "group by"]

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

    class Outputs:
        data = Output("Data", Table, doc="Aggregated data")

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

    settingsHandler = DomainContextHandler()

    gb_attrs: List[Variable] = ContextSetting([])
    aggregations: Dict[Variable, Set[str]] = ContextSetting({})
    auto_commit: bool = Setting(True)

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

        self.data = None
        self.result = None

        self.gb_attrs_model = DomainModel(separators=False, )
        self.agg_table_model = VarTableModel(self)
        self.agg_checkboxes = {}

        self.__init_control_area()
        self.__init_main_area()

    def __init_control_area(self) -> None:
        """Init all controls in the control area"""
        box = gui.vBox(self.controlArea, "Group by")
        self.gb_attrs_view = AggregateListViewSearch(
            selectionMode=QListView.ExtendedSelection)
        self.gb_attrs_view.setModel(self.gb_attrs_model)
        self.gb_attrs_view.selectionModel().selectionChanged.connect(
            self.__gb_changed)
        box.layout().addWidget(self.gb_attrs_view)

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

    def __init_main_area(self) -> None:
        """Init all controls in the main area"""
        # aggregation table
        self.agg_table_view = tableview = QTableView()
        tableview.setModel(self.agg_table_model)
        tableview.setSelectionBehavior(QAbstractItemView.SelectRows)
        tableview.selectionModel().selectionChanged.connect(
            self.__rows_selected)
        tableview.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

        vbox = gui.vBox(self.mainArea, " ")
        vbox.layout().addWidget(tableview)

        # aggregations checkboxes
        grid_layout = QGridLayout()
        gui.widgetBox(self.mainArea,
                      orientation=grid_layout,
                      box="Aggregations")

        col = 0
        row = 0
        break_rows = (5, 5, 99)
        for agg in AGGREGATIONS:
            self.agg_checkboxes[agg] = cb = CheckBox(agg, self)
            cb.setDisabled(True)
            cb.stateChanged.connect(partial(self.__aggregation_changed, agg))
            grid_layout.addWidget(cb, row, col)
            row += 1
            if row == break_rows[col]:
                row = 0
                col += 1

    ############
    # Callbacks

    def __rows_selected(self) -> None:
        """Callback for table selection change; update checkboxes"""
        selected_attrs = self.get_selected_attributes()

        types = {type(attr) for attr in selected_attrs}
        active_aggregations = [
            self.aggregations[attr] for attr in selected_attrs
        ]
        for agg, cb in self.agg_checkboxes.items():
            cb.setDisabled(not types & AGGREGATIONS[agg].types)

            activated = {agg in a for a in active_aggregations}
            with block_signals(cb):
                # check if aggregation active for all selected attributes,
                # partially check if active for some else uncheck
                cb.setCheckState(Qt.Checked if activated == {True} else (
                    Qt.Unchecked if activated ==
                    {False} else Qt.PartiallyChecked))

    def __gb_changed(self) -> None:
        """
        Callback for Group-by attributes selection change; update attribute
        and call commit
        """
        rows = self.gb_attrs_view.selectionModel().selectedRows()
        values = self.gb_attrs_view.model()[:]
        self.gb_attrs = [values[row.row()] for row in sorted(rows)]
        # everything cached in result should be recomputed on gb change
        self.result = Result()
        self.commit.deferred()

    def __aggregation_changed(self, agg: str) -> None:
        """
        Callback for aggregation change; update aggregations dictionary and call
        commit
        """
        selected_attrs = self.get_selected_attributes()
        for attr in selected_attrs:
            if self.agg_checkboxes[agg].isChecked(
            ) and self.__aggregation_compatible(agg, attr):
                self.aggregations[attr].add(agg)
            else:
                self.aggregations[attr].discard(agg)
            self.agg_table_model.update_aggregation(attr)
        self.commit.deferred()

    @Inputs.data
    def set_data(self, data: Table) -> None:
        self.closeContext()
        self.data = data

        # reset states
        self.cancel()
        self.result = Result()
        self.Outputs.data.send(None)
        self.gb_attrs_model.set_domain(data.domain if data else None)
        self.gb_attrs = data.domain[:1] if data else []
        self.aggregations = ({
            attr: DEFAULT_AGGREGATIONS[type(attr)].copy()
            for attr in data.domain.variables + data.domain.metas
        } if data else {})
        default_aggregations = self.aggregations.copy()

        self.openContext(self.data)

        # restore aggregations
        self.aggregations.update({
            k: v
            for k, v in default_aggregations.items()
            if k not in self.aggregations
        })

        # update selections in widgets and re-plot
        self.agg_table_model.set_domain(data.domain if data else None)
        self._set_gb_selection()

        self.commit.now()

    #########################
    # Task connected methods

    @gui.deferred
    def commit(self) -> None:
        self.Error.clear()
        self.Warning.clear()
        if self.data:
            self.start(_run, self.data, self.gb_attrs, self.aggregations,
                       self.result)

    def on_done(self, result: Result) -> None:
        self.result = result
        self.Outputs.data.send(result.result_table)

    def on_partial_result(self, result: Result) -> None:
        # store result in case the task is canceled and on_done is not called
        self.result = result

    def on_exception(self, ex: Exception):
        self.Error.unexpected_error(str(ex))

    ###################
    # Helper methods

    def get_selected_attributes(self):
        """Get select attributes in the table"""
        selection_model = self.agg_table_view.selectionModel()
        sel_rows = selection_model.selectedRows()
        vars_ = self.data.domain.variables + self.data.domain.metas
        return [vars_[index.row()] for index in sel_rows]

    def _set_gb_selection(self) -> None:
        """Set selection in groupby list according to self.gb_attrs"""
        sm = self.gb_attrs_view.selectionModel()
        values = self.gb_attrs_model[:]
        with disconnected(sm.selectionChanged, self.__gb_changed):
            for val in self.gb_attrs:
                index = values.index(val)
                model_index = self.gb_attrs_model.index(index, 0)
                sm.select(model_index, QItemSelectionModel.Select)

    @staticmethod
    def __aggregation_compatible(agg, attr):
        """Check a compatibility of aggregation with the variable"""
        return type(attr) in AGGREGATIONS[agg].types