{
    "file_diffs": [
        {
            "old_file_content": "from collections import OrderedDict\nimport pkg_resources\n\nimport numpy\n\nfrom AnyQt.QtWidgets import (\n    QWidget, QButtonGroup, QGroupBox, QRadioButton, QSlider,\n    QDoubleSpinBox, QComboBox, QSpinBox, QListView, QLabel,\n    QScrollArea, QVBoxLayout, QHBoxLayout, QFormLayout,\n    QSizePolicy, QApplication, QCheckBox\n)\n\nfrom AnyQt.QtGui import (\n    QIcon, QStandardItemModel, QStandardItem\n)\n\nfrom AnyQt.QtCore import (\n    Qt, QEvent, QSize, QMimeData, QTimer\n)\n\nfrom AnyQt.QtCore import pyqtSignal as Signal, pyqtSlot as Slot\n\nimport Orange.data\nfrom Orange import preprocess\nfrom Orange.preprocess import Continuize, ProjectPCA, RemoveNaNRows, \\\n    ProjectCUR, Scale as _Scale, Randomize as _Randomize, RemoveSparse\nfrom Orange.widgets import widget, gui\nfrom Orange.widgets.settings import Setting\nfrom Orange.widgets.utils.overlay import OverlayWidget\nfrom Orange.widgets.utils.sql import check_sql_input\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.widget import Input, Output\nfrom Orange.preprocess import Normalize\nfrom Orange.widgets.data.utils.preprocess import (\n    BaseEditor, blocked, StandardItemModel, DescriptionRole,\n    ParametersRole, Controller, SequenceFlow\n)\n\nclass _NoneDisc(preprocess.discretize.Discretization):\n    \"\"\"Discretize all variables into None.\n\n    Used in combination with preprocess.Discretize to remove\n    all discrete features from the domain.\n\n    \"\"\"\n    _reprable_module = True\n\n    def __call__(self, data, variable):\n        return None\n\n\nclass DiscretizeEditor(BaseEditor):\n    \"\"\"\n    Editor for preprocess.Discretize.\n    \"\"\"\n    #: Discretize methods\n    NoDisc, EqualWidth, EqualFreq, Drop, EntropyMDL = 0, 1, 2, 3, 4\n    Discretizers = {\n        NoDisc: (None, {}),\n        EqualWidth: (preprocess.discretize.EqualWidth, {\"n\": 4}),\n        EqualFreq:  (preprocess.discretize.EqualFreq, {\"n\": 4}),\n        Drop: (_NoneDisc, {}),\n        EntropyMDL: (preprocess.discretize.EntropyMDL, {\"force\": False})\n    }\n    Names = {\n        NoDisc: \"None\",\n        EqualWidth: \"Equal width discretization\",\n        EqualFreq: \"Equal frequency discretization\",\n        Drop: \"Remove numeric features\",\n        EntropyMDL: \"Entropy-MDL discretization\"\n    }\n\n    def __init__(self, parent=None, **kwargs):\n        BaseEditor.__init__(self, parent, **kwargs)\n        self.__method = DiscretizeEditor.EqualFreq\n        self.__nintervals = 4\n\n        layout = QVBoxLayout()\n        self.setLayout(layout)\n        self.__group = group = QButtonGroup(self, exclusive=True)\n\n        for method in [self.EntropyMDL, self.EqualFreq, self.EqualWidth,\n                       self.Drop]:\n            rb = QRadioButton(\n                self, text=self.Names[method],\n                checked=self.__method == method\n            )\n            layout.addWidget(rb)\n            group.addButton(rb, method)\n\n        group.buttonClicked.connect(self.__on_buttonClicked)\n\n        self.__slbox = slbox = QGroupBox(\n            title=\"Number of intervals (for equal width/frequency)\",\n            flat=True\n        )\n        slbox.setLayout(QHBoxLayout())\n        self.__slider = slider = QSlider(\n            orientation=Qt.Horizontal,\n            minimum=2, maximum=10, value=self.__nintervals,\n            enabled=self.__method in [self.EqualFreq, self.EqualWidth],\n            pageStep=1, tickPosition=QSlider.TicksBelow\n        )\n        slider.valueChanged.connect(self.__on_valueChanged)\n        slbox.layout().addWidget(slider)\n        self.__slabel = slabel = QLabel()\n        slbox.layout().addWidget(slabel)\n\n        container = QHBoxLayout()\n        container.setContentsMargins(13, 0, 0, 0)\n        container.addWidget(slbox)\n        self.layout().insertLayout(3, container)\n        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)\n\n    def setMethod(self, method):\n        if self.__method != method:\n            self.__method = method\n            b = self.__group.button(method)\n            b.setChecked(True)\n            self.__slider.setEnabled(\n                method in [self.EqualFreq, self.EqualWidth]\n            )\n            self.changed.emit()\n\n    def method(self):\n        return self.__method\n\n    def intervals(self):\n        return self.__nintervals\n\n    def setIntervals(self, n):\n        n = numpy.clip(n, self.__slider.minimum(), self.__slider.maximum())\n        n = int(n)\n        if self.__nintervals != n:\n            self.__nintervals = n\n            # blocking signals in order to differentiate between\n            # changed by user (notified through __on_valueChanged) or\n            # changed programmatically (this)\n            with blocked(self.__slider):\n                self.__slider.setValue(n)\n                self.__slabel.setText(str(self.__slider.value()))\n            self.changed.emit()\n\n    def setParameters(self, params):\n        method = params.get(\"method\", self.EqualFreq)\n        nintervals = params.get(\"n\", 5)\n        self.setMethod(method)\n        if method in [self.EqualFreq, self.EqualWidth]:\n            self.setIntervals(nintervals)\n\n    def parameters(self):\n        if self.__method in [self.EqualFreq, self.EqualWidth]:\n            return {\"method\": self.__method, \"n\": self.__nintervals}\n        else:\n            return {\"method\": self.__method}\n\n    def __on_buttonClicked(self):\n        # on user 'method' button click\n        method = self.__group.checkedId()\n        if method != self.__method:\n            self.setMethod(self.__group.checkedId())\n            self.edited.emit()\n\n    def __on_valueChanged(self):\n        # on user n intervals slider change.\n        self.__nintervals = self.__slider.value()\n        self.changed.emit()\n        self.edited.emit()\n        self.__slabel.setText(str(self.__slider.value()))\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        method = params.pop(\"method\", DiscretizeEditor.EqualFreq)\n        method, defaults = DiscretizeEditor.Discretizers[method]\n\n        if method is None:\n            return None\n\n        resolved = dict(defaults)\n        # update only keys in defaults?\n        resolved.update(params)\n        return preprocess.Discretize(method(**params), remove_const=False)\n\n    def __repr__(self):\n        n_int = \", Number of intervals: {}\".format(self.__nintervals) \\\n            if self.__method in [self.EqualFreq, self.EqualWidth] else \"\"\n        return \"{}{}\".format(self.Names[self.__method], n_int)\n\n\nclass ContinuizeEditor(BaseEditor):\n    _Type = type(Continuize.FirstAsBase)\n\n    Continuizers = OrderedDict([\n        (Continuize.FrequentAsBase, \"Most frequent is base\"),\n        (Continuize.Indicators, \"One feature per value\"),\n        (Continuize.RemoveMultinomial, \"Remove non-binary features\"),\n        (Continuize.Remove, \"Remove categorical features\"),\n        (Continuize.AsOrdinal, \"Treat as ordinal\"),\n        (Continuize.AsNormalizedOrdinal, \"Divide by number of values\")\n    ])\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n        self.__treatment = Continuize.Indicators\n        self.__group = group = QButtonGroup(exclusive=True)\n        group.buttonClicked.connect(self.__on_buttonClicked)\n\n        for treatment, text in ContinuizeEditor.Continuizers.items():\n            rb = QRadioButton(\n                text=text,\n                checked=self.__treatment == treatment)\n            group.addButton(rb, enum_to_index(ContinuizeEditor._Type,\n                                              treatment))\n            self.layout().addWidget(rb)\n\n        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)\n\n    def setTreatment(self, treatment):\n        buttonid = enum_to_index(ContinuizeEditor._Type, treatment)\n        b = self.__group.button(buttonid)\n        if b is not None:\n            b.setChecked(True)\n            self.__treatment = treatment\n            self.changed.emit()\n\n    def treatment(self):\n        return self.__treatment\n\n    def setParameters(self, params):\n        treatment = params.get(\"multinomial_treatment\", Continuize.Indicators)\n        self.setTreatment(treatment)\n\n    def parameters(self):\n        return {\"multinomial_treatment\": self.__treatment}\n\n    def __on_buttonClicked(self):\n        self.__treatment = index_to_enum(\n            ContinuizeEditor._Type, self.__group.checkedId())\n        self.changed.emit()\n        self.edited.emit()\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        treatment = params.pop(\"multinomial_treatment\", Continuize.Indicators)\n        return Continuize(multinomial_treatment=treatment)\n\n    def __repr__(self):\n        return self.Continuizers[self.__treatment]\n\nclass RemoveSparseEditor(BaseEditor):\n\n    options = [\"missing\", \"zeros\"]\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.fixedThresh = 50\n        self.percThresh = 5\n        self.useFixedThreshold = False\n        self.filter0 = True\n        self.setLayout(QVBoxLayout())\n\n        self.layout().addWidget(QLabel(\"Remove features with too many\"))\n        options = [\"missing values\",\n                   \"zeros\"]\n        self.filter_buttons = QButtonGroup(exclusive=True)\n        self.filter_buttons.buttonClicked.connect(self.filterByClicked)\n        for idx, option, in enumerate(options):\n            btn = QRadioButton(self, text=option, checked=idx == 0)\n            self.filter_buttons.addButton(btn, id=idx)\n            self.layout().addWidget(btn)\n\n        self.layout().addSpacing(20)\n\n        filter_settings = QGroupBox(title='Threshold:', flat=True)\n        filter_settings.setLayout(QFormLayout())\n        self.settings_buttons = QButtonGroup(exclusive=True)\n        self.settings_buttons.buttonClicked.connect(self.filterSettingsClicked)\n\n        btn_perc = QRadioButton(self, text='Percentage', checked=not self.useFixedThreshold)\n        self.settings_buttons.addButton(btn_perc, id=0)\n        self.percSpin = QSpinBox(minimum=0, maximum=100, value=self.percThresh,\n                                 enabled=not self.useFixedThreshold)\n        self.percSpin.valueChanged[int].connect(self.setPercThresh)\n        self.percSpin.editingFinished.connect(self.edited)\n\n        btn_fix = QRadioButton(self, text='Fixed', checked=self.useFixedThreshold)\n        self.settings_buttons.addButton(btn_fix, id=1)\n        self.fixedSpin = QSpinBox(minimum=0, maximum=1000000, value=self.fixedThresh,\n                                  enabled=self.useFixedThreshold)\n        self.fixedSpin.valueChanged[int].connect(self.setFixedThresh)\n        self.fixedSpin.editingFinished.connect(self.edited)\n        filter_settings.layout().addRow(btn_fix, self.fixedSpin)\n        filter_settings.layout().addRow(btn_perc, self.percSpin)\n\n        self.layout().addWidget(filter_settings)\n\n    def filterSettingsClicked(self):\n        self.setUseFixedThreshold(self.settings_buttons.checkedId())\n        self.percSpin.setEnabled(not self.useFixedThreshold)\n        self.fixedSpin.setEnabled(self.useFixedThreshold)\n        self.edited.emit()\n\n    def filterByClicked(self):\n        self.setFilter0(self.filter_buttons.checkedId())\n\n    def setFilter0(self, id_):\n        if self.filter0 != id_:\n            self.filter0 = id_\n            self.edited.emit()\n\n    def setFixedThresh(self, thresh):\n        if self.fixedThresh != thresh:\n            self.fixedThresh = thresh\n            self.fixedSpin.setValue(thresh)\n            self.edited.emit()\n\n    def setPercThresh(self, thresh):\n        if self.percThresh != thresh:\n            self.percThresh = thresh\n            self.percSpin.setValue(thresh)\n            self.edited.emit()\n\n    def setUseFixedThreshold(self, val):\n        if self.useFixedThreshold != val:\n            self.useFixedThreshold = val\n            self.edited.emit()\n\n    def parameters(self):\n        return {'fixedThresh': self.fixedThresh,\n                'percThresh' : self.percThresh,\n                'useFixedThreshold' : self.useFixedThreshold,\n                'filter0' : self.filter0}\n\n    def setParameters(self, params):\n        self.setPercThresh(params.get('percThresh', 5))\n        self.setFixedThresh(params.get('fixedThresh', 50))\n        self.setUseFixedThreshold(params.get('useFixedThreshold', False))\n        self.setFilter0(params.get('filter0', True))\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        filter0 = params.pop('filter0', True)\n        useFixedThreshold = params.pop('useFixedThreshold', True)\n        if useFixedThreshold:\n            threshold = params.pop('fixedThresh', 50)\n        else:\n            threshold = params.pop('percThresh', 5) / 100\n        return RemoveSparse(threshold, filter0)\n\nclass ImputeEditor(BaseEditor):\n    (NoImputation, Constant, Average,\n     Model, Random, DropRows, DropColumns) = 0, 1, 2, 3, 4, 5, 6\n\n    Imputers = {\n        NoImputation: (None, {}),\n        #         Constant: (None, {\"value\": 0})\n        Average: (preprocess.impute.Average(), {}),\n        #         Model: (preprocess.impute.Model, {}),\n        Random: (preprocess.impute.Random(), {}),\n        DropRows: (None, {})\n    }\n    Names = {\n        NoImputation: \"Don't impute.\",\n        Constant: \"Replace with constant\",\n        Average: \"Average/Most frequent\",\n        Model: \"Model based imputer\",\n        Random: \"Replace with random value\",\n        DropRows: \"Remove rows with missing values.\",\n    }\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.__method = ImputeEditor.Average\n        self.__group = group = QButtonGroup(self, exclusive=True)\n        group.buttonClicked.connect(self.__on_buttonClicked)\n\n        for methodid in [self.Average, self.Random, self.DropRows]:\n            text = self.Names[methodid]\n            rb = QRadioButton(text=text, checked=self.__method == methodid)\n            group.addButton(rb, methodid)\n            self.layout().addWidget(rb)\n\n    def setMethod(self, method):\n        b = self.__group.button(method)\n        if b is not None:\n            b.setChecked(True)\n            self.__method = method\n            self.changed.emit()\n\n    def setParameters(self, params):\n        method = params.get(\"method\", ImputeEditor.Average)\n        self.setMethod(method)\n\n    def parameters(self):\n        return {\"method\": self.__method}\n\n    def __on_buttonClicked(self):\n        self.__method = self.__group.checkedId()\n        self.changed.emit()\n        self.edited.emit()\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        method = params.pop(\"method\", ImputeEditor.Average)\n        if method == ImputeEditor.NoImputation:\n            return None\n        elif method == ImputeEditor.Average:\n            return preprocess.Impute()\n        elif method == ImputeEditor.Model:\n            return preprocess.Impute(method=preprocess.impute.Model())\n        elif method == ImputeEditor.DropRows:\n            return RemoveNaNRows()\n        elif method == ImputeEditor.DropColumns:\n            return preprocess.RemoveNaNColumns()\n        else:\n            method, defaults = ImputeEditor.Imputers[method]\n            defaults = dict(defaults)\n            defaults.update(params)\n            return preprocess.Impute(method=method)\n\n    def __repr__(self):\n        return self.Names[self.__method]\n\n\nclass UnivariateFeatureSelect(QWidget):\n    changed = Signal()\n    edited = Signal()\n\n    #: Strategy\n    Fixed, Proportion, FDR, FPR, FWE = 1, 2, 3, 4, 5\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.__scoreidx = 0\n        self.__strategy = UnivariateFeatureSelect.Fixed\n        self.__k = 10\n        self.__p = 75.0\n\n        box = QGroupBox(title=\"Score\", flat=True)\n        box.setLayout(QVBoxLayout())\n        self.__cb = cb = QComboBox(self, )\n        self.__cb.currentIndexChanged.connect(self.setScoreIndex)\n        self.__cb.activated.connect(self.edited)\n        box.layout().addWidget(cb)\n\n        self.layout().addWidget(box)\n\n        box = QGroupBox(title=\"Number of features\", flat=True)\n        self.__group = group = QButtonGroup(self, exclusive=True)\n        self.__spins = {}\n\n        form = QFormLayout()\n        fixedrb = QRadioButton(\"Fixed:\", checked=True)\n        group.addButton(fixedrb, UnivariateFeatureSelect.Fixed)\n        kspin = QSpinBox(\n            minimum=1, maximum=1000000, value=self.__k,\n            enabled=self.__strategy == UnivariateFeatureSelect.Fixed\n        )\n        kspin.valueChanged[int].connect(self.setK)\n        kspin.editingFinished.connect(self.edited)\n        self.__spins[UnivariateFeatureSelect.Fixed] = kspin\n        form.addRow(fixedrb, kspin)\n\n        percrb = QRadioButton(\"Proportion:\")\n        group.addButton(percrb, UnivariateFeatureSelect.Proportion)\n        pspin = QDoubleSpinBox(\n            minimum=1.0, maximum=100.0, singleStep=0.5,\n            value=self.__p, suffix=\"%\",\n            enabled=self.__strategy == UnivariateFeatureSelect.Proportion\n        )\n\n        pspin.valueChanged[float].connect(self.setP)\n        pspin.editingFinished.connect(self.edited)\n        self.__spins[UnivariateFeatureSelect.Proportion] = pspin\n        form.addRow(percrb, pspin)\n\n#         form.addRow(QRadioButton(\"FDR\"), QDoubleSpinBox())\n#         form.addRow(QRadioButton(\"FPR\"), QDoubleSpinBox())\n#         form.addRow(QRadioButton(\"FWE\"), QDoubleSpinBox())\n\n        self.__group.buttonClicked.connect(self.__on_buttonClicked)\n        box.setLayout(form)\n        self.layout().addWidget(box)\n\n    def setScoreIndex(self, scoreindex):\n        if self.__scoreidx != scoreindex:\n            self.__scoreidx = scoreindex\n            self.__cb.setCurrentIndex(scoreindex)\n            self.changed.emit()\n\n    def scoreIndex(self):\n        return self.__scoreidx\n\n    def setStrategy(self, strategy):\n        if self.__strategy != strategy:\n            self.__strategy = strategy\n            b = self.__group.button(strategy)\n            b.setChecked(True)\n            for st, rb in self.__spins.items():\n                rb.setEnabled(st == strategy)\n            self.changed.emit()\n\n    def setK(self, k):\n        if self.__k != k:\n            self.__k = k\n            spin = self.__spins[UnivariateFeatureSelect.Fixed]\n            spin.setValue(k)\n            if self.__strategy == UnivariateFeatureSelect.Fixed:\n                self.changed.emit()\n\n    def setP(self, p):\n        if self.__p != p:\n            self.__p = p\n            spin = self.__spins[UnivariateFeatureSelect.Proportion]\n            spin.setValue(p)\n            if self.__strategy == UnivariateFeatureSelect.Proportion:\n                self.changed.emit()\n\n    def setItems(self, itemlist):\n        for item in itemlist:\n            self.__cb.addItem(item[\"text\"])\n\n    def __on_buttonClicked(self):\n        strategy = self.__group.checkedId()\n        self.setStrategy(strategy)\n        self.edited.emit()\n\n    def setParameters(self, params):\n        score = params.get(\"score\", 0)\n        strategy = params.get(\"strategy\", UnivariateFeatureSelect.Fixed)\n        self.setScoreIndex(score)\n        self.setStrategy(strategy)\n        if strategy == UnivariateFeatureSelect.Fixed:\n            self.setK(params.get(\"k\", 10))\n        else:\n            self.setP(params.get(\"p\", 75))\n\n    def parameters(self):\n        score = self.__scoreidx\n        strategy = self.__strategy\n        p = self.__p\n        k = self.__k\n\n        return {\"score\": score, \"strategy\": strategy, \"p\": p, \"k\": k}\n\n\nclass FeatureSelectEditor(BaseEditor):\n\n    MEASURES = [\n        (\"Information Gain\", preprocess.score.InfoGain),\n        (\"Gain ratio\", preprocess.score.GainRatio),\n        (\"Gini index\", preprocess.score.Gini),\n        (\"ReliefF\", preprocess.score.ReliefF),\n        (\"Fast Correlation Based Filter\", preprocess.score.FCBF),\n        (\"ANOVA\", preprocess.score.ANOVA),\n        (\"Chi2\", preprocess.score.Chi2),\n        (\"RReliefF\", preprocess.score.RReliefF),\n        (\"Univariate Linear Regression\", preprocess.score.UnivariateLinearRegression)\n    ]\n\n    def __init__(self, parent=None):\n        super().__init__(parent)\n        self.setLayout(QVBoxLayout())\n        self.layout().setContentsMargins(0, 0, 0, 0)\n\n        self.__score = 0\n        self.__selecionidx = 0\n\n        self.__uni_fs = UnivariateFeatureSelect()\n        self.__uni_fs.setItems(\n            [{\"text\": \"Information Gain\", \"tooltip\": \"\"},\n             {\"text\": \"Gain Ratio\"},\n             {\"text\": \"Gini Index\"},\n             {\"text\": \"ReliefF\"},\n             {\"text\": \"Fast Correlation Based Filter\"},\n             {\"text\": \"ANOVA\"},\n             {\"text\": \"Chi2\"},\n             {\"text\": \"RReliefF\"},\n             {\"text\": \"Univariate Linear Regression\"}\n            ]\n        )\n        self.layout().addWidget(self.__uni_fs)\n        self.__uni_fs.changed.connect(self.changed)\n        self.__uni_fs.edited.connect(self.edited)\n\n    def setParameters(self, params):\n        self.__uni_fs.setParameters(params)\n\n    def parameters(self):\n        return self.__uni_fs.parameters()\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        score = params.pop(\"score\", 0)\n        score = FeatureSelectEditor.MEASURES[score][1]\n        strategy = params.get(\"strategy\", UnivariateFeatureSelect.Fixed)\n        k = params.get(\"k\", 10)\n        p = params.get(\"p\", 75.0)\n        if strategy == UnivariateFeatureSelect.Fixed:\n            return preprocess.fss.SelectBestFeatures(score(), k=k)\n        elif strategy == UnivariateFeatureSelect.Proportion:\n            return preprocess.fss.SelectBestFeatures(score(), k=p / 100)\n        else:\n            raise NotImplementedError\n\n    def __repr__(self):\n        params = self.__uni_fs.parameters()\n        return \"Score: {}, Strategy (Fixed): {}\".format(\n            self.MEASURES[params[\"score\"]][0], params[\"k\"])\n\n# TODO: Model based FS (random forest variable importance, ...), RFE\n# Unsupervised (min variance, constant, ...)??\n\nclass RandomFeatureSelectEditor(BaseEditor):\n    #: Strategy\n    Fixed, Percentage = 1, 2\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.__strategy = RandomFeatureSelectEditor.Fixed\n        self.__k = 10\n        self.__p = 75.0\n\n        box = QGroupBox(title=\"Number of features\", flat=True)\n        self.__group = group = QButtonGroup(self, exclusive=True)\n        self.__spins = {}\n\n        form = QFormLayout()\n        fixedrb = QRadioButton(\"Fixed\", checked=True)\n        group.addButton(fixedrb, RandomFeatureSelectEditor.Fixed)\n        kspin = QSpinBox(\n            minimum=1, maximum=1000000, value=self.__k,\n            enabled=self.__strategy == RandomFeatureSelectEditor.Fixed\n        )\n        kspin.valueChanged[int].connect(self.setK)\n        kspin.editingFinished.connect(self.edited)\n        self.__spins[RandomFeatureSelectEditor.Fixed] = kspin\n        form.addRow(fixedrb, kspin)\n\n        percrb = QRadioButton(\"Percentage\")\n        group.addButton(percrb, RandomFeatureSelectEditor.Percentage)\n        pspin = QDoubleSpinBox(\n            minimum=0.0, maximum=100.0, singleStep=0.5,\n            value=self.__p, suffix=\"%\",\n            enabled=self.__strategy == RandomFeatureSelectEditor.Percentage\n        )\n        pspin.valueChanged[float].connect(self.setP)\n        pspin.editingFinished.connect(self.edited)\n        self.__spins[RandomFeatureSelectEditor.Percentage] = pspin\n        form.addRow(percrb, pspin)\n\n        self.__group.buttonClicked.connect(self.__on_buttonClicked)\n        box.setLayout(form)\n        self.layout().addWidget(box)\n\n    def setStrategy(self, strategy):\n        if self.__strategy != strategy:\n            self.__strategy = strategy\n            b = self.__group.button(strategy)\n            b.setChecked(True)\n            for st, rb in self.__spins.items():\n                rb.setEnabled(st == strategy)\n            self.changed.emit()\n\n    def setK(self, k):\n        if self.__k != k:\n            self.__k = k\n            spin = self.__spins[RandomFeatureSelectEditor.Fixed]\n            spin.setValue(k)\n            if self.__strategy == RandomFeatureSelectEditor.Fixed:\n                self.changed.emit()\n\n    def setP(self, p):\n        if self.__p != p:\n            self.__p = p\n            spin = self.__spins[RandomFeatureSelectEditor.Percentage]\n            spin.setValue(p)\n            if self.__strategy == RandomFeatureSelectEditor.Percentage:\n                self.changed.emit()\n\n    def __on_buttonClicked(self):\n        strategy = self.__group.checkedId()\n        self.setStrategy(strategy)\n        self.edited.emit()\n\n    def setParameters(self, params):\n        strategy = params.get(\"strategy\", RandomFeatureSelectEditor.Fixed)\n        self.setStrategy(strategy)\n        if strategy == RandomFeatureSelectEditor.Fixed:\n            self.setK(params.get(\"k\", 10))\n        else:\n            self.setP(params.get(\"p\", 75.0))\n\n    def parameters(self):\n        strategy = self.__strategy\n        p = self.__p\n        k = self.__k\n\n        return {\"strategy\": strategy, \"p\": p, \"k\": k}\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        strategy = params.get(\"strategy\", RandomFeatureSelectEditor.Fixed)\n        k = params.get(\"k\", 10)\n        p = params.get(\"p\", 75.0)\n        if strategy == RandomFeatureSelectEditor.Fixed:\n            return preprocess.fss.SelectRandomFeatures(k=k)\n        elif strategy == RandomFeatureSelectEditor.Percentage:\n            return preprocess.fss.SelectRandomFeatures(k=p/100)\n        else:\n            # further implementations\n            raise NotImplementedError\n\n\ndef index_to_enum(enum, i):\n    \"\"\"Enums, by default, are not int-comparable, so use an ad-hoc mapping of\n    int to enum value at that position\"\"\"\n    return list(enum)[i]\n\n\ndef enum_to_index(enum, key):\n    \"\"\"Enums, by default, are not int-comparable, so use an ad-hoc mapping of\n    enum key to its int position\"\"\"\n    return list(enum).index(key)\n\nclass Scale(BaseEditor):\n    CenterByMean, ScaleBySD, NormalizeBySD, NormalizeBySpan_ZeroBased, \\\n        NormalizeSpan_NonZeroBased = 0, 1, 2, 3, 4\n\n    Names = {\n        NormalizeBySD: \"Standardize to μ=0, σ²=1\",\n        CenterByMean: \"Center to μ=0\",\n        ScaleBySD: \"Scale to σ²=1\",\n        NormalizeSpan_NonZeroBased: \"Normalize to interval [-1, 1]\",\n        NormalizeBySpan_ZeroBased: \"Normalize to interval [0, 1]\",\n    }\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.__method = Scale.NormalizeBySD\n        self.__group = group = QButtonGroup(self, exclusive=True)\n        group.buttonClicked.connect(self.__on_buttonClicked)\n\n        for methodid in [self.NormalizeBySD, self.CenterByMean, self.ScaleBySD,\n                         self.NormalizeSpan_NonZeroBased,\n                         self.NormalizeBySpan_ZeroBased]:\n            text = self.Names[methodid]\n            rb = QRadioButton(text=text, checked=self.__method == methodid)\n            group.addButton(rb, methodid)\n            self.layout().addWidget(rb)\n\n    def setMethod(self, method):\n        b = self.__group.button(method)\n        if b is not None:\n            b.setChecked(True)\n            self.__method = method\n            self.changed.emit()\n\n    def setParameters(self, params):\n        method = params.get(\"method\", Scale.NormalizeBySD)\n        self.setMethod(method)\n\n    def parameters(self):\n        return {\"method\": self.__method}\n\n    def __on_buttonClicked(self):\n        self.__method = self.__group.checkedId()\n        self.changed.emit()\n        self.edited.emit()\n\n    @staticmethod\n    def createinstance(params):\n        method = params.get(\"method\", Scale.NormalizeBySD)\n        if method == Scale.CenterByMean:\n            return _Scale(_Scale.CenteringType.Mean,\n                          _Scale.ScalingType.NoScaling)\n        elif method == Scale.ScaleBySD:\n            return _Scale(_Scale.CenteringType.NoCentering,\n                          _Scale.ScalingType.Std)\n        elif method == Scale.NormalizeBySD:\n            return Normalize(norm_type=Normalize.NormalizeBySD)\n        elif method == Scale.NormalizeBySpan_ZeroBased:\n            return Normalize(norm_type=Normalize.NormalizeBySpan)\n        else:  # method == Scale.NormalizeSpan_NonZeroBased\n            return Normalize(norm_type=Normalize.NormalizeBySpan,\n                             zero_based=False)\n\n    def __repr__(self):\n        return self.Names[self.__method]\n\n\nclass Randomize(BaseEditor):\n    RandomizeClasses, RandomizeAttributes, RandomizeMetas = _Randomize.Type\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        form = QFormLayout()\n        self.__rand_type_cb = QComboBox()\n        self.__rand_type_cb.addItems([\"Classes\",\n                                      \"Features\",\n                                      \"Meta data\"])\n\n        self.__rand_type_cb.currentIndexChanged.connect(self.changed)\n        self.__rand_type_cb.activated.connect(self.edited)\n\n        self.__rand_seed_ch = QCheckBox()\n        self.__rand_seed_ch.clicked.connect(self.edited)\n\n        form.addRow(\"Randomize:\", self.__rand_type_cb)\n        form.addRow(\"Replicable shuffling:\", self.__rand_seed_ch)\n        self.layout().addLayout(form)\n\n    def setParameters(self, params):\n        rand_type = params.get(\"rand_type\", Randomize.RandomizeClasses)\n        self.__rand_type_cb.setCurrentIndex(\n            enum_to_index(_Randomize.Type, rand_type))\n        self.__rand_seed_ch.setChecked(params.get(\"rand_seed\", 1) or 0)\n\n    def parameters(self):\n        return {\"rand_type\": index_to_enum(_Randomize.Type,\n                                           self.__rand_type_cb.currentIndex()),\n                \"rand_seed\": 1 if self.__rand_seed_ch.isChecked() else None}\n\n    @staticmethod\n    def createinstance(params):\n        rand_type = params.get(\"rand_type\", Randomize.RandomizeClasses)\n        rand_seed = params.get(\"rand_seed\", 1)\n        return _Randomize(rand_type=rand_type, rand_seed=rand_seed)\n\n    def __repr__(self):\n        return \"{}, {}\".format(self.__rand_type_cb.currentText(),\n                               \"Replicable\" if self.__rand_seed_ch.isChecked()\n                               else \"Not replicable\")\n\n\nclass PCA(BaseEditor):\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.n_components = 10\n\n        form = QFormLayout()\n        self.cspin = QSpinBox(minimum=1, value=self.n_components)\n        self.cspin.valueChanged[int].connect(self.setC)\n        self.cspin.editingFinished.connect(self.edited)\n\n        form.addRow(\"Components:\", self.cspin)\n        self.layout().addLayout(form)\n\n    def setParameters(self, params):\n        self.n_components = params.get(\"n_components\", 10)\n\n    def parameters(self):\n        return {\"n_components\": self.n_components}\n\n    def setC(self, n_components):\n        if self.n_components != n_components:\n            self.n_components = n_components\n            self.cspin.setValue(n_components)\n            self.changed.emit()\n\n    @staticmethod\n    def createinstance(params):\n        n_components = params.get(\"n_components\", 10)\n        return ProjectPCA(n_components=n_components)\n\n    def __repr__(self):\n        return \"Components: {}\".format(self.cspin.value())\n\n\nclass CUR(BaseEditor):\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.rank = 10\n        self.max_error = 1\n\n        form = QFormLayout()\n        self.rspin = QSpinBox(minimum=2, maximum=1000000, value=self.rank)\n        self.rspin.valueChanged[int].connect(self.setR)\n        self.rspin.editingFinished.connect(self.edited)\n        self.espin = QDoubleSpinBox(\n            minimum=0.1, maximum=100.0, singleStep=0.1,\n            value=self.max_error)\n        self.espin.valueChanged[float].connect(self.setE)\n        self.espin.editingFinished.connect(self.edited)\n\n        form.addRow(\"Rank:\", self.rspin)\n        form.addRow(\"Relative error:\", self.espin)\n        self.layout().addLayout(form)\n\n    def setParameters(self, params):\n        self.setR(params.get(\"rank\", 10))\n        self.setE(params.get(\"max_error\", 1))\n\n    def parameters(self):\n        return {\"rank\": self.rank, \"max_error\": self.max_error}\n\n    def setR(self, rank):\n        if self.rank != rank:\n            self.rank = rank\n            self.rspin.setValue(rank)\n            self.changed.emit()\n\n    def setE(self, max_error):\n        if self.max_error != max_error:\n            self.max_error = max_error\n            self.espin.setValue(max_error)\n            self.changed.emit()\n\n    @staticmethod\n    def createinstance(params):\n        rank = params.get(\"rank\", 10)\n        max_error = params.get(\"max_error\", 1)\n        return ProjectCUR(rank=rank, max_error=max_error)\n\n    def __repr__(self):\n        return \"Rank: {}, Relative error: {}\".format(self.rspin.value(),\n                                                     self.espin.value())\n\n\n# This is intended for future improvements.\n# I.e. it should be possible to add/register preprocessor actions\n# through entry points (for use by add-ons). Maybe it should be a\n# general framework (this is not the only place where such\n# functionality is desired (for instance in Orange v2.* Rank widget\n# already defines its own entry point).\nclass Description:\n    \"\"\"\n    A description of an action/function.\n    \"\"\"\n    def __init__(self, title, icon=None, summary=None, input_=None, output=None,\n                 requires=None, note=None, related=None, keywords=None,\n                 helptopic=None):\n        self.title = title\n        self.icon = icon\n        self.summary = summary\n        self.input = input_\n        self.output = output\n        self.requires = requires\n        self.note = note\n        self.related = related\n        self.keywords = keywords\n        self.helptopic = helptopic\n\n\nclass PreprocessAction:\n    def __init__(self, name, qualname, category, description, viewclass):\n        self.name = name\n        self.qualname = qualname\n        self.category = category\n        self.description = description\n        self.viewclass = viewclass\n\n\ndef icon_path(basename):\n    return pkg_resources.resource_filename(__name__, \"icons/\" + basename)\n\n\nPREPROCESS_ACTIONS = [\n    PreprocessAction(\n        \"Discretize\", \"orange.preprocess.discretize\", \"Discretization\",\n        Description(\"Discretize Continuous Variables\",\n                    icon_path(\"Discretize.svg\")),\n        DiscretizeEditor\n    ),\n    PreprocessAction(\n        \"Continuize\", \"orange.preprocess.continuize\", \"Continuization\",\n        Description(\"Continuize Discrete Variables\",\n                    icon_path(\"Continuize.svg\")),\n        ContinuizeEditor\n    ),\n    PreprocessAction(\n        \"Impute\", \"orange.preprocess.impute\", \"Impute\",\n        Description(\"Impute Missing Values\",\n                    icon_path(\"Impute.svg\")),\n        ImputeEditor\n    ),\n    PreprocessAction(\n        \"Feature Selection\", \"orange.preprocess.fss\", \"Feature Selection\",\n        Description(\"Select Relevant Features\",\n                    icon_path(\"SelectColumns.svg\")),\n        FeatureSelectEditor\n    ),\n    PreprocessAction(\n        \"Random Feature Selection\", \"orange.preprocess.randomfss\",\n        \"Random Feature Selection\",\n        Description(\"Select Random Features\",\n                    icon_path(\"SelectColumnsRandom.svg\")),\n        RandomFeatureSelectEditor\n    ),\n    PreprocessAction(\n        \"Normalize\", \"orange.preprocess.scale\", \"Scale\",\n        Description(\"Normalize Features\",\n                    icon_path(\"Normalize.svg\")),\n        Scale\n    ),\n    PreprocessAction(\n        \"Randomize\", \"orange.preprocess.randomize\", \"Randomization\",\n        Description(\"Randomize\",\n                    icon_path(\"Random.svg\")),\n        Randomize\n    ),\n    PreprocessAction(\n        \"Remove Sparse\", \"orange.preprocess.remove_sparse\", \"Feature Selection\",\n        Description(\"Remove Sparse Features\",\n                    icon_path(\"PurgeDomain.svg\")),\n        RemoveSparseEditor\n    ),\n    PreprocessAction(\n        \"PCA\", \"orange.preprocess.pca\", \"PCA\",\n        Description(\"Principal Component Analysis\",\n                    icon_path(\"PCA.svg\")),\n        PCA\n    ),\n    PreprocessAction(\n        \"CUR\", \"orange.preprocess.cur\", \"CUR\",\n        Description(\"CUR Matrix Decomposition\",\n                    icon_path(\"SelectColumns.svg\")),\n        CUR\n    )\n]\n\n\n# TODO: Extend with entry points here\n# PREPROCESS_ACTIONS += iter_entry_points(\"Orange.widgets.data.owpreprocess\")\n\n# ####\n# The actual owwidget\n# ####\n\n# Note:\n# The preprocessors are drag/dropped onto a sequence widget, where\n# they can be reordered/removed/edited.\n#\n# Model <-> Adapter/Controller <-> View\n#\n# * `Model`: the current constructed preprocessor model.\n# * the source (of drag/drop) is an item model displayed in a list\n#   view (source list).\n# * the drag/drop is controlled by the controller/adapter,\n\n\nclass OWPreprocess(widget.OWWidget):\n    name = \"Preprocess\"\n    description = \"Construct a data preprocessing pipeline.\"\n    icon = \"icons/Preprocess.svg\"\n    priority = 2105\n    keywords = [\"process\"]\n\n    settings_version = 2\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        preprocessor = Output(\"Preprocessor\", preprocess.preprocess.Preprocess, dynamic=False)\n        preprocessed_data = Output(\"Preprocessed Data\", Orange.data.Table)\n\n    storedsettings = Setting({})\n    autocommit = Setting(True)\n    PREPROCESSORS = PREPROCESS_ACTIONS\n    CONTROLLER = Controller\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self._invalidated = False\n\n        # List of available preprocessors (DescriptionRole : Description)\n        self.preprocessors = QStandardItemModel()\n\n        def mimeData(indexlist):\n            assert len(indexlist) == 1\n            index = indexlist[0]\n            qname = index.data(DescriptionRole).qualname\n            m = QMimeData()\n            m.setData(\"application/x-qwidget-ref\", qname.encode(\"utf-8\"))\n            return m\n        # TODO: Fix this (subclass even if just to pass a function\n        # for mimeData delegate)\n        self.preprocessors.mimeData = mimeData\n\n        box = gui.vBox(self.controlArea, \"Preprocessors\")\n\n        self.preprocessorsView = view = QListView(\n            selectionMode=QListView.SingleSelection,\n            dragEnabled=True,\n            dragDropMode=QListView.DragOnly\n        )\n        view.setModel(self.preprocessors)\n        view.activated.connect(self.__activated)\n\n        box.layout().addWidget(view)\n\n        ####\n        self._qname2ppdef = {ppdef.qualname: ppdef for\n                             ppdef in self.PREPROCESSORS}\n\n        # List of 'selected' preprocessors and their parameters.\n        self.preprocessormodel = None\n\n        self.flow_view = SequenceFlow()\n        self.controler = self.CONTROLLER(self.flow_view, parent=self)\n\n        self.overlay = OverlayWidget(self)\n        self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents)\n        self.overlay.setWidget(self.flow_view)\n        self.overlay.setLayout(QVBoxLayout())\n        self.overlay.layout().addWidget(\n            QLabel(\"Drag items from the list on the left\", wordWrap=True))\n\n        self.scroll_area = QScrollArea(\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn\n        )\n        self.scroll_area.viewport().setAcceptDrops(True)\n        self.scroll_area.setWidget(self.flow_view)\n        self.scroll_area.setWidgetResizable(True)\n        self.mainArea.layout().addWidget(self.scroll_area)\n        self.flow_view.installEventFilter(self)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.auto_send(box, self, \"autocommit\", box=False)\n\n        self._initialize()\n\n    def _initialize(self):\n        for pp_def in self.PREPROCESSORS:\n            description = pp_def.description\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item = QStandardItem(icon, description.title)\n            item.setToolTip(description.summary or \"\")\n            item.setData(pp_def, DescriptionRole)\n            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable |\n                          Qt.ItemIsDragEnabled)\n            self.preprocessors.appendRow([item])\n\n        model = self.load(self.storedsettings)\n\n        self.set_model(model)\n\n        if not model.rowCount():\n            # enforce default width constraint if no preprocessors\n            # are instantiated (if the model is not empty the constraints\n            # will be triggered by LayoutRequest event on the `flow_view`)\n            self.__update_size_constraint()\n\n        self.apply()\n\n    def load(self, saved):\n        \"\"\"Load a preprocessor list from a dict.\"\"\"\n        preprocessors = saved.get(\"preprocessors\", [])\n        model = StandardItemModel()\n\n        def dropMimeData(data, action, row, _column, _parent):\n            if data.hasFormat(\"application/x-qwidget-ref\") and \\\n                    action == Qt.CopyAction:\n                qname = bytes(data.data(\"application/x-qwidget-ref\")).decode()\n\n                ppdef = self._qname2ppdef[qname]\n                item = QStandardItem(ppdef.description.title)\n                item.setData({}, ParametersRole)\n                item.setData(ppdef.description.title, Qt.DisplayRole)\n                item.setData(ppdef, DescriptionRole)\n                self.preprocessormodel.insertRow(row, [item])\n                return True\n            else:\n                return False\n\n        model.dropMimeData = dropMimeData\n\n        for qualname, params in preprocessors:\n            pp_def = self._qname2ppdef[qualname]\n            description = pp_def.description\n            item = QStandardItem(description.title)\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item.setIcon(icon)\n            item.setToolTip(description.summary)\n            item.setData(pp_def, DescriptionRole)\n            item.setData(params, ParametersRole)\n\n            model.appendRow(item)\n        return model\n\n    @staticmethod\n    def save(model):\n        \"\"\"Save the preprocessor list to a dict.\"\"\"\n        d = {\"name\": \"\"}\n        preprocessors = []\n        for i in range(model.rowCount()):\n            item = model.item(i)\n            pp_def = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n            preprocessors.append((pp_def.qualname, params))\n\n        d[\"preprocessors\"] = preprocessors\n        return d\n\n\n    def set_model(self, ppmodel):\n        if self.preprocessormodel:\n            self.preprocessormodel.dataChanged.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.deleteLater()\n\n        self.preprocessormodel = ppmodel\n        self.controler.setModel(ppmodel)\n        if ppmodel is not None:\n            self.preprocessormodel.dataChanged.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged)\n\n        self.__update_overlay()\n\n    def __update_overlay(self):\n        if self.preprocessormodel is None or \\\n                self.preprocessormodel.rowCount() == 0:\n            self.overlay.setWidget(self.flow_view)\n            self.overlay.show()\n        else:\n            self.overlay.setWidget(None)\n            self.overlay.hide()\n\n    def __on_modelchanged(self):\n        self.__update_overlay()\n        self.commit()\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.data = data\n\n    def handleNewSignals(self):\n        self.apply()\n\n    def __activated(self, index):\n        item = self.preprocessors.itemFromIndex(index)\n        action = item.data(DescriptionRole)\n        item = QStandardItem()\n        item.setData({}, ParametersRole)\n        item.setData(action.description.title, Qt.DisplayRole)\n        item.setData(action, DescriptionRole)\n        self.preprocessormodel.appendRow([item])\n\n    def buildpreproc(self):\n        plist = []\n        for i in range(self.preprocessormodel.rowCount()):\n            item = self.preprocessormodel.item(i)\n            desc = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n\n            if not isinstance(params, dict):\n                params = {}\n\n            create = desc.viewclass.createinstance\n            plist.append(create(params))\n\n        if len(plist) == 1:\n            return plist[0]\n        else:\n            return preprocess.preprocess.PreprocessorList(plist)\n\n    def apply(self):\n        # Sync the model into storedsettings on every apply.\n        self.storeSpecificSettings()\n        preprocessor = self.buildpreproc()\n\n        if self.data is not None:\n            self.error()\n            try:\n                data = preprocessor(self.data)\n            except (ValueError, ZeroDivisionError) as e:\n                self.error(str(e))\n                return\n        else:\n            data = None\n\n        self.Outputs.preprocessor.send(preprocessor)\n        self.Outputs.preprocessed_data.send(data)\n\n    def commit(self):\n        if not self._invalidated:\n            self._invalidated = True\n            QApplication.postEvent(self, QEvent(QEvent.User))\n\n    def customEvent(self, event):\n        if event.type() == QEvent.User and self._invalidated:\n            self._invalidated = False\n            self.apply()\n\n    def eventFilter(self, receiver, event):\n        if receiver is self.flow_view and event.type() == QEvent.LayoutRequest:\n            QTimer.singleShot(0, self.__update_size_constraint)\n\n        return super().eventFilter(receiver, event)\n\n    def storeSpecificSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().storeSpecificSettings()\n\n    def saveSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().saveSettings()\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version < 2:\n            for action, params in settings[\"storedsettings\"][\"preprocessors\"]:\n                if action == \"orange.preprocess.scale\":\n                    scale = center = None\n                    if \"center\" in params:\n                        center = params.pop(\"center\").name\n                    if \"scale\" in params:\n                        scale = params.pop(\"scale\").name\n                    migratable = {\n                        (\"Mean\", \"NoScaling\"): Scale.CenterByMean,\n                        (\"NoCentering\", \"Std\"): Scale.ScaleBySD,\n                        (\"Mean\", \"Std\"): Scale.NormalizeBySD,\n                        (\"NoCentering\", \"Span\"): Scale.NormalizeBySpan_ZeroBased\n                    }\n                    params[\"method\"] = \\\n                        migratable.get((center, scale), Scale.NormalizeBySD)\n\n    def onDeleteWidget(self):\n        self.data = None\n        self.set_model(None)\n        super().onDeleteWidget()\n\n    @Slot()\n    def __update_size_constraint(self):\n        # Update minimum width constraint on the scroll area containing\n        # the 'instantiated' preprocessor list (to avoid the horizontal\n        # scroll bar).\n        sh = self.flow_view.minimumSizeHint()\n        scroll_width = self.scroll_area.verticalScrollBar().width()\n        self.scroll_area.setMinimumWidth(\n            min(max(sh.width() + scroll_width + 2, self.controlArea.width()),\n                520))\n\n    def sizeHint(self):\n        sh = super().sizeHint()\n        return sh.expandedTo(QSize(sh.width() + 300, 500))\n\n    def send_report(self):\n        pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w)\n              for i, w in enumerate(self.controler.view.widgets())]\n        if pp:\n            self.report_items(\"Settings\", pp)\n\nif __name__ == \"__main__\":  # pragma: no cover\n    WidgetPreview(OWPreprocess).run(Orange.data.Table(\"brown-selected\"))\n",
            "new_file_content": "from collections import OrderedDict\nimport pkg_resources\n\nimport numpy\n\nfrom AnyQt.QtWidgets import (\n    QWidget, QButtonGroup, QGroupBox, QRadioButton, QSlider,\n    QDoubleSpinBox, QComboBox, QSpinBox, QListView, QLabel,\n    QScrollArea, QVBoxLayout, QHBoxLayout, QFormLayout,\n    QSizePolicy, QApplication, QCheckBox\n)\n\nfrom AnyQt.QtGui import (\n    QIcon, QStandardItemModel, QStandardItem\n)\n\nfrom AnyQt.QtCore import (\n    Qt, QEvent, QSize, QMimeData, QTimer\n)\n\nfrom AnyQt.QtCore import pyqtSignal as Signal, pyqtSlot as Slot\n\nimport Orange.data\nfrom Orange import preprocess\nfrom Orange.preprocess import Continuize, ProjectPCA, RemoveNaNRows, \\\n    ProjectCUR, Scale as _Scale, Randomize as _Randomize, RemoveSparse\nfrom Orange.widgets import widget, gui\nfrom Orange.widgets.settings import Setting\nfrom Orange.widgets.utils.overlay import OverlayWidget\nfrom Orange.widgets.utils.sql import check_sql_input\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.widget import Input, Output\nfrom Orange.preprocess import Normalize\nfrom Orange.widgets.data.utils.preprocess import (\n    BaseEditor, blocked, StandardItemModel, DescriptionRole,\n    ParametersRole, Controller, SequenceFlow\n)\n\nclass _NoneDisc(preprocess.discretize.Discretization):\n    \"\"\"Discretize all variables into None.\n\n    Used in combination with preprocess.Discretize to remove\n    all discrete features from the domain.\n\n    \"\"\"\n    _reprable_module = True\n\n    def __call__(self, data, variable):\n        return None\n\n\nclass DiscretizeEditor(BaseEditor):\n    \"\"\"\n    Editor for preprocess.Discretize.\n    \"\"\"\n    #: Discretize methods\n    NoDisc, EqualWidth, EqualFreq, Drop, EntropyMDL = 0, 1, 2, 3, 4\n    Discretizers = {\n        NoDisc: (None, {}),\n        EqualWidth: (preprocess.discretize.EqualWidth, {\"n\": 4}),\n        EqualFreq:  (preprocess.discretize.EqualFreq, {\"n\": 4}),\n        Drop: (_NoneDisc, {}),\n        EntropyMDL: (preprocess.discretize.EntropyMDL, {\"force\": False})\n    }\n    Names = {\n        NoDisc: \"None\",\n        EqualWidth: \"Equal width discretization\",\n        EqualFreq: \"Equal frequency discretization\",\n        Drop: \"Remove numeric features\",\n        EntropyMDL: \"Entropy-MDL discretization\"\n    }\n\n    def __init__(self, parent=None, **kwargs):\n        BaseEditor.__init__(self, parent, **kwargs)\n        self.__method = DiscretizeEditor.EqualFreq\n        self.__nintervals = 4\n\n        layout = QVBoxLayout()\n        self.setLayout(layout)\n        self.__group = group = QButtonGroup(self, exclusive=True)\n\n        for method in [self.EntropyMDL, self.EqualFreq, self.EqualWidth,\n                       self.Drop]:\n            rb = QRadioButton(\n                self, text=self.Names[method],\n                checked=self.__method == method\n            )\n            layout.addWidget(rb)\n            group.addButton(rb, method)\n\n        group.buttonClicked.connect(self.__on_buttonClicked)\n\n        self.__slbox = slbox = QGroupBox(\n            title=\"Number of intervals (for equal width/frequency)\",\n            flat=True\n        )\n        slbox.setLayout(QHBoxLayout())\n        self.__slider = slider = QSlider(\n            orientation=Qt.Horizontal,\n            minimum=2, maximum=10, value=self.__nintervals,\n            enabled=self.__method in [self.EqualFreq, self.EqualWidth],\n            pageStep=1, tickPosition=QSlider.TicksBelow\n        )\n        slider.valueChanged.connect(self.__on_valueChanged)\n        slbox.layout().addWidget(slider)\n        self.__slabel = slabel = QLabel()\n        slbox.layout().addWidget(slabel)\n\n        container = QHBoxLayout()\n        container.setContentsMargins(13, 0, 0, 0)\n        container.addWidget(slbox)\n        self.layout().insertLayout(3, container)\n        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)\n\n    def setMethod(self, method):\n        if self.__method != method:\n            self.__method = method\n            b = self.__group.button(method)\n            b.setChecked(True)\n            self.__slider.setEnabled(\n                method in [self.EqualFreq, self.EqualWidth]\n            )\n            self.changed.emit()\n\n    def method(self):\n        return self.__method\n\n    def intervals(self):\n        return self.__nintervals\n\n    def setIntervals(self, n):\n        n = numpy.clip(n, self.__slider.minimum(), self.__slider.maximum())\n        n = int(n)\n        if self.__nintervals != n:\n            self.__nintervals = n\n            # blocking signals in order to differentiate between\n            # changed by user (notified through __on_valueChanged) or\n            # changed programmatically (this)\n            with blocked(self.__slider):\n                self.__slider.setValue(n)\n                self.__slabel.setText(str(self.__slider.value()))\n            self.changed.emit()\n\n    def setParameters(self, params):\n        method = params.get(\"method\", self.EqualFreq)\n        nintervals = params.get(\"n\", 5)\n        self.setMethod(method)\n        if method in [self.EqualFreq, self.EqualWidth]:\n            self.setIntervals(nintervals)\n\n    def parameters(self):\n        if self.__method in [self.EqualFreq, self.EqualWidth]:\n            return {\"method\": self.__method, \"n\": self.__nintervals}\n        else:\n            return {\"method\": self.__method}\n\n    def __on_buttonClicked(self):\n        # on user 'method' button click\n        method = self.__group.checkedId()\n        if method != self.__method:\n            self.setMethod(self.__group.checkedId())\n            self.edited.emit()\n\n    def __on_valueChanged(self):\n        # on user n intervals slider change.\n        self.__nintervals = self.__slider.value()\n        self.changed.emit()\n        self.edited.emit()\n        self.__slabel.setText(str(self.__slider.value()))\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        method = params.pop(\"method\", DiscretizeEditor.EqualFreq)\n        method, defaults = DiscretizeEditor.Discretizers[method]\n\n        if method is None:\n            return None\n\n        resolved = dict(defaults)\n        # update only keys in defaults?\n        resolved.update(params)\n        return preprocess.Discretize(method(**params), remove_const=False)\n\n    def __repr__(self):\n        n_int = \", Number of intervals: {}\".format(self.__nintervals) \\\n            if self.__method in [self.EqualFreq, self.EqualWidth] else \"\"\n        return \"{}{}\".format(self.Names[self.__method], n_int)\n\n\nclass ContinuizeEditor(BaseEditor):\n    _Type = type(Continuize.FirstAsBase)\n\n    Continuizers = OrderedDict([\n        (Continuize.FrequentAsBase, \"Most frequent is base\"),\n        (Continuize.Indicators, \"One feature per value\"),\n        (Continuize.RemoveMultinomial, \"Remove non-binary features\"),\n        (Continuize.Remove, \"Remove categorical features\"),\n        (Continuize.AsOrdinal, \"Treat as ordinal\"),\n        (Continuize.AsNormalizedOrdinal, \"Divide by number of values\")\n    ])\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n        self.__treatment = Continuize.Indicators\n        self.__group = group = QButtonGroup(exclusive=True)\n        group.buttonClicked.connect(self.__on_buttonClicked)\n\n        for treatment, text in ContinuizeEditor.Continuizers.items():\n            rb = QRadioButton(\n                text=text,\n                checked=self.__treatment == treatment)\n            group.addButton(rb, enum_to_index(ContinuizeEditor._Type,\n                                              treatment))\n            self.layout().addWidget(rb)\n\n        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)\n\n    def setTreatment(self, treatment):\n        buttonid = enum_to_index(ContinuizeEditor._Type, treatment)\n        b = self.__group.button(buttonid)\n        if b is not None:\n            b.setChecked(True)\n            self.__treatment = treatment\n            self.changed.emit()\n\n    def treatment(self):\n        return self.__treatment\n\n    def setParameters(self, params):\n        treatment = params.get(\"multinomial_treatment\", Continuize.Indicators)\n        self.setTreatment(treatment)\n\n    def parameters(self):\n        return {\"multinomial_treatment\": self.__treatment}\n\n    def __on_buttonClicked(self):\n        self.__treatment = index_to_enum(\n            ContinuizeEditor._Type, self.__group.checkedId())\n        self.changed.emit()\n        self.edited.emit()\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        treatment = params.pop(\"multinomial_treatment\", Continuize.Indicators)\n        return Continuize(multinomial_treatment=treatment)\n\n    def __repr__(self):\n        return self.Continuizers[self.__treatment]\n\nclass RemoveSparseEditor(BaseEditor):\n\n    options = [\"missing\", \"zeros\"]\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.fixedThresh = 50\n        self.percThresh = 5\n        self.useFixedThreshold = False\n        self.filter0 = True\n        self.setLayout(QVBoxLayout())\n\n        self.layout().addWidget(QLabel(\"Remove features with too many\"))\n        options = [\"missing values\",\n                   \"zeros\"]\n        self.filter_buttons = QButtonGroup(exclusive=True)\n        self.filter_buttons.buttonClicked.connect(self.filterByClicked)\n        for idx, option, in enumerate(options):\n            btn = QRadioButton(self, text=option, checked=idx == 0)\n            self.filter_buttons.addButton(btn, id=idx)\n            self.layout().addWidget(btn)\n\n        self.layout().addSpacing(20)\n\n        filter_settings = QGroupBox(title='Threshold:', flat=True)\n        filter_settings.setLayout(QFormLayout())\n        self.settings_buttons = QButtonGroup(exclusive=True)\n        self.settings_buttons.buttonClicked.connect(self.filterSettingsClicked)\n\n        btn_perc = QRadioButton(self, text='Percentage', checked=not self.useFixedThreshold)\n        self.settings_buttons.addButton(btn_perc, id=0)\n        self.percSpin = QSpinBox(minimum=0, maximum=100, value=self.percThresh,\n                                 enabled=not self.useFixedThreshold)\n        self.percSpin.valueChanged[int].connect(self.setPercThresh)\n        self.percSpin.editingFinished.connect(self.edited)\n\n        btn_fix = QRadioButton(self, text='Fixed', checked=self.useFixedThreshold)\n        self.settings_buttons.addButton(btn_fix, id=1)\n        self.fixedSpin = QSpinBox(minimum=0, maximum=1000000, value=self.fixedThresh,\n                                  enabled=self.useFixedThreshold)\n        self.fixedSpin.valueChanged[int].connect(self.setFixedThresh)\n        self.fixedSpin.editingFinished.connect(self.edited)\n        filter_settings.layout().addRow(btn_fix, self.fixedSpin)\n        filter_settings.layout().addRow(btn_perc, self.percSpin)\n\n        self.layout().addWidget(filter_settings)\n\n    def filterSettingsClicked(self):\n        self.setUseFixedThreshold(self.settings_buttons.checkedId())\n        self.percSpin.setEnabled(not self.useFixedThreshold)\n        self.fixedSpin.setEnabled(self.useFixedThreshold)\n        self.edited.emit()\n\n    def filterByClicked(self):\n        self.setFilter0(self.filter_buttons.checkedId())\n\n    def setFilter0(self, id_):\n        if self.filter0 != id_:\n            self.filter0 = id_\n            self.edited.emit()\n\n    def setFixedThresh(self, thresh):\n        if self.fixedThresh != thresh:\n            self.fixedThresh = thresh\n            self.fixedSpin.setValue(thresh)\n            self.edited.emit()\n\n    def setPercThresh(self, thresh):\n        if self.percThresh != thresh:\n            self.percThresh = thresh\n            self.percSpin.setValue(thresh)\n            self.edited.emit()\n\n    def setUseFixedThreshold(self, val):\n        if self.useFixedThreshold != val:\n            self.useFixedThreshold = val\n            self.edited.emit()\n\n    def parameters(self):\n        return {'fixedThresh': self.fixedThresh,\n                'percThresh' : self.percThresh,\n                'useFixedThreshold' : self.useFixedThreshold,\n                'filter0' : self.filter0}\n\n    def setParameters(self, params):\n        self.setPercThresh(params.get('percThresh', 5))\n        self.setFixedThresh(params.get('fixedThresh', 50))\n        self.setUseFixedThreshold(params.get('useFixedThreshold', False))\n        self.setFilter0(params.get('filter0', True))\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        filter0 = params.pop('filter0', True)\n        useFixedThreshold = params.pop('useFixedThreshold', True)\n        if useFixedThreshold:\n            threshold = params.pop('fixedThresh', 50)\n        else:\n            threshold = params.pop('percThresh', 5) / 100\n        return RemoveSparse(threshold, filter0)\n\nclass ImputeEditor(BaseEditor):\n    (NoImputation, Constant, Average,\n     Model, Random, DropRows, DropColumns) = 0, 1, 2, 3, 4, 5, 6\n\n    Imputers = {\n        NoImputation: (None, {}),\n        #         Constant: (None, {\"value\": 0})\n        Average: (preprocess.impute.Average(), {}),\n        #         Model: (preprocess.impute.Model, {}),\n        Random: (preprocess.impute.Random(), {}),\n        DropRows: (None, {})\n    }\n    Names = {\n        NoImputation: \"Don't impute.\",\n        Constant: \"Replace with constant\",\n        Average: \"Average/Most frequent\",\n        Model: \"Model based imputer\",\n        Random: \"Replace with random value\",\n        DropRows: \"Remove rows with missing values.\",\n    }\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.__method = ImputeEditor.Average\n        self.__group = group = QButtonGroup(self, exclusive=True)\n        group.buttonClicked.connect(self.__on_buttonClicked)\n\n        for methodid in [self.Average, self.Random, self.DropRows]:\n            text = self.Names[methodid]\n            rb = QRadioButton(text=text, checked=self.__method == methodid)\n            group.addButton(rb, methodid)\n            self.layout().addWidget(rb)\n\n    def setMethod(self, method):\n        b = self.__group.button(method)\n        if b is not None:\n            b.setChecked(True)\n            self.__method = method\n            self.changed.emit()\n\n    def setParameters(self, params):\n        method = params.get(\"method\", ImputeEditor.Average)\n        self.setMethod(method)\n\n    def parameters(self):\n        return {\"method\": self.__method}\n\n    def __on_buttonClicked(self):\n        self.__method = self.__group.checkedId()\n        self.changed.emit()\n        self.edited.emit()\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        method = params.pop(\"method\", ImputeEditor.Average)\n        if method == ImputeEditor.NoImputation:\n            return None\n        elif method == ImputeEditor.Average:\n            return preprocess.Impute()\n        elif method == ImputeEditor.Model:\n            return preprocess.Impute(method=preprocess.impute.Model())\n        elif method == ImputeEditor.DropRows:\n            return RemoveNaNRows()\n        elif method == ImputeEditor.DropColumns:\n            return preprocess.RemoveNaNColumns()\n        else:\n            method, defaults = ImputeEditor.Imputers[method]\n            defaults = dict(defaults)\n            defaults.update(params)\n            return preprocess.Impute(method=method)\n\n    def __repr__(self):\n        return self.Names[self.__method]\n\n\nclass UnivariateFeatureSelect(QWidget):\n    changed = Signal()\n    edited = Signal()\n\n    #: Strategy\n    Fixed, Proportion, FDR, FPR, FWE = 1, 2, 3, 4, 5\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.__scoreidx = 0\n        self.__strategy = UnivariateFeatureSelect.Fixed\n        self.__k = 10\n        self.__p = 75.0\n\n        box = QGroupBox(title=\"Score\", flat=True)\n        box.setLayout(QVBoxLayout())\n        self.__cb = cb = QComboBox(self, )\n        self.__cb.currentIndexChanged.connect(self.setScoreIndex)\n        self.__cb.activated.connect(self.edited)\n        box.layout().addWidget(cb)\n\n        self.layout().addWidget(box)\n\n        box = QGroupBox(title=\"Number of features\", flat=True)\n        self.__group = group = QButtonGroup(self, exclusive=True)\n        self.__spins = {}\n\n        form = QFormLayout()\n        fixedrb = QRadioButton(\"Fixed:\", checked=True)\n        group.addButton(fixedrb, UnivariateFeatureSelect.Fixed)\n        kspin = QSpinBox(\n            minimum=1, maximum=1000000, value=self.__k,\n            enabled=self.__strategy == UnivariateFeatureSelect.Fixed\n        )\n        kspin.valueChanged[int].connect(self.setK)\n        kspin.editingFinished.connect(self.edited)\n        self.__spins[UnivariateFeatureSelect.Fixed] = kspin\n        form.addRow(fixedrb, kspin)\n\n        percrb = QRadioButton(\"Proportion:\")\n        group.addButton(percrb, UnivariateFeatureSelect.Proportion)\n        pspin = QDoubleSpinBox(\n            minimum=1.0, maximum=100.0, singleStep=0.5,\n            value=self.__p, suffix=\"%\",\n            enabled=self.__strategy == UnivariateFeatureSelect.Proportion\n        )\n\n        pspin.valueChanged[float].connect(self.setP)\n        pspin.editingFinished.connect(self.edited)\n        self.__spins[UnivariateFeatureSelect.Proportion] = pspin\n        form.addRow(percrb, pspin)\n\n#         form.addRow(QRadioButton(\"FDR\"), QDoubleSpinBox())\n#         form.addRow(QRadioButton(\"FPR\"), QDoubleSpinBox())\n#         form.addRow(QRadioButton(\"FWE\"), QDoubleSpinBox())\n\n        self.__group.buttonClicked.connect(self.__on_buttonClicked)\n        box.setLayout(form)\n        self.layout().addWidget(box)\n\n    def setScoreIndex(self, scoreindex):\n        if self.__scoreidx != scoreindex:\n            self.__scoreidx = scoreindex\n            self.__cb.setCurrentIndex(scoreindex)\n            self.changed.emit()\n\n    def scoreIndex(self):\n        return self.__scoreidx\n\n    def setStrategy(self, strategy):\n        if self.__strategy != strategy:\n            self.__strategy = strategy\n            b = self.__group.button(strategy)\n            b.setChecked(True)\n            for st, rb in self.__spins.items():\n                rb.setEnabled(st == strategy)\n            self.changed.emit()\n\n    def setK(self, k):\n        if self.__k != k:\n            self.__k = k\n            spin = self.__spins[UnivariateFeatureSelect.Fixed]\n            spin.setValue(k)\n            if self.__strategy == UnivariateFeatureSelect.Fixed:\n                self.changed.emit()\n\n    def setP(self, p):\n        if self.__p != p:\n            self.__p = p\n            spin = self.__spins[UnivariateFeatureSelect.Proportion]\n            spin.setValue(p)\n            if self.__strategy == UnivariateFeatureSelect.Proportion:\n                self.changed.emit()\n\n    def setItems(self, itemlist):\n        for item in itemlist:\n            self.__cb.addItem(item[\"text\"])\n\n    def __on_buttonClicked(self):\n        strategy = self.__group.checkedId()\n        self.setStrategy(strategy)\n        self.edited.emit()\n\n    def setParameters(self, params):\n        score = params.get(\"score\", 0)\n        strategy = params.get(\"strategy\", UnivariateFeatureSelect.Fixed)\n        self.setScoreIndex(score)\n        self.setStrategy(strategy)\n        if strategy == UnivariateFeatureSelect.Fixed:\n            self.setK(params.get(\"k\", 10))\n        else:\n            self.setP(params.get(\"p\", 75))\n\n    def parameters(self):\n        score = self.__scoreidx\n        strategy = self.__strategy\n        p = self.__p\n        k = self.__k\n\n        return {\"score\": score, \"strategy\": strategy, \"p\": p, \"k\": k}\n\n\nclass FeatureSelectEditor(BaseEditor):\n\n    MEASURES = [\n        (\"Information Gain\", preprocess.score.InfoGain),\n        (\"Gain ratio\", preprocess.score.GainRatio),\n        (\"Gini index\", preprocess.score.Gini),\n        (\"ReliefF\", preprocess.score.ReliefF),\n        (\"Fast Correlation Based Filter\", preprocess.score.FCBF),\n        (\"ANOVA\", preprocess.score.ANOVA),\n        (\"Chi2\", preprocess.score.Chi2),\n        (\"RReliefF\", preprocess.score.RReliefF),\n        (\"Univariate Linear Regression\", preprocess.score.UnivariateLinearRegression)\n    ]\n\n    def __init__(self, parent=None):\n        super().__init__(parent)\n        self.setLayout(QVBoxLayout())\n        self.layout().setContentsMargins(0, 0, 0, 0)\n\n        self.__score = 0\n        self.__selecionidx = 0\n\n        self.__uni_fs = UnivariateFeatureSelect()\n        self.__uni_fs.setItems(\n            [{\"text\": \"Information Gain\", \"tooltip\": \"\"},\n             {\"text\": \"Gain Ratio\"},\n             {\"text\": \"Gini Index\"},\n             {\"text\": \"ReliefF\"},\n             {\"text\": \"Fast Correlation Based Filter\"},\n             {\"text\": \"ANOVA\"},\n             {\"text\": \"Chi2\"},\n             {\"text\": \"RReliefF\"},\n             {\"text\": \"Univariate Linear Regression\"}\n            ]\n        )\n        self.layout().addWidget(self.__uni_fs)\n        self.__uni_fs.changed.connect(self.changed)\n        self.__uni_fs.edited.connect(self.edited)\n\n    def setParameters(self, params):\n        self.__uni_fs.setParameters(params)\n\n    def parameters(self):\n        return self.__uni_fs.parameters()\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        score = params.pop(\"score\", 0)\n        score = FeatureSelectEditor.MEASURES[score][1]\n        strategy = params.get(\"strategy\", UnivariateFeatureSelect.Fixed)\n        k = params.get(\"k\", 10)\n        p = params.get(\"p\", 75.0)\n        if strategy == UnivariateFeatureSelect.Fixed:\n            return preprocess.fss.SelectBestFeatures(score(), k=k)\n        elif strategy == UnivariateFeatureSelect.Proportion:\n            return preprocess.fss.SelectBestFeatures(score(), k=p / 100)\n        else:\n            raise NotImplementedError\n\n    def __repr__(self):\n        params = self.__uni_fs.parameters()\n        return \"Score: {}, Strategy (Fixed): {}\".format(\n            self.MEASURES[params[\"score\"]][0], params[\"k\"])\n\n# TODO: Model based FS (random forest variable importance, ...), RFE\n# Unsupervised (min variance, constant, ...)??\n\nclass RandomFeatureSelectEditor(BaseEditor):\n    #: Strategy\n    Fixed, Percentage = 1, 2\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.__strategy = RandomFeatureSelectEditor.Fixed\n        self.__k = 10\n        self.__p = 75.0\n\n        box = QGroupBox(title=\"Number of features\", flat=True)\n        self.__group = group = QButtonGroup(self, exclusive=True)\n        self.__spins = {}\n\n        form = QFormLayout()\n        fixedrb = QRadioButton(\"Fixed\", checked=True)\n        group.addButton(fixedrb, RandomFeatureSelectEditor.Fixed)\n        kspin = QSpinBox(\n            minimum=1, maximum=1000000, value=self.__k,\n            enabled=self.__strategy == RandomFeatureSelectEditor.Fixed\n        )\n        kspin.valueChanged[int].connect(self.setK)\n        kspin.editingFinished.connect(self.edited)\n        self.__spins[RandomFeatureSelectEditor.Fixed] = kspin\n        form.addRow(fixedrb, kspin)\n\n        percrb = QRadioButton(\"Percentage\")\n        group.addButton(percrb, RandomFeatureSelectEditor.Percentage)\n        pspin = QDoubleSpinBox(\n            minimum=0.0, maximum=100.0, singleStep=0.5,\n            value=self.__p, suffix=\"%\",\n            enabled=self.__strategy == RandomFeatureSelectEditor.Percentage\n        )\n        pspin.valueChanged[float].connect(self.setP)\n        pspin.editingFinished.connect(self.edited)\n        self.__spins[RandomFeatureSelectEditor.Percentage] = pspin\n        form.addRow(percrb, pspin)\n\n        self.__group.buttonClicked.connect(self.__on_buttonClicked)\n        box.setLayout(form)\n        self.layout().addWidget(box)\n\n    def setStrategy(self, strategy):\n        if self.__strategy != strategy:\n            self.__strategy = strategy\n            b = self.__group.button(strategy)\n            b.setChecked(True)\n            for st, rb in self.__spins.items():\n                rb.setEnabled(st == strategy)\n            self.changed.emit()\n\n    def setK(self, k):\n        if self.__k != k:\n            self.__k = k\n            spin = self.__spins[RandomFeatureSelectEditor.Fixed]\n            spin.setValue(k)\n            if self.__strategy == RandomFeatureSelectEditor.Fixed:\n                self.changed.emit()\n\n    def setP(self, p):\n        if self.__p != p:\n            self.__p = p\n            spin = self.__spins[RandomFeatureSelectEditor.Percentage]\n            spin.setValue(p)\n            if self.__strategy == RandomFeatureSelectEditor.Percentage:\n                self.changed.emit()\n\n    def __on_buttonClicked(self):\n        strategy = self.__group.checkedId()\n        self.setStrategy(strategy)\n        self.edited.emit()\n\n    def setParameters(self, params):\n        strategy = params.get(\"strategy\", RandomFeatureSelectEditor.Fixed)\n        self.setStrategy(strategy)\n        if strategy == RandomFeatureSelectEditor.Fixed:\n            self.setK(params.get(\"k\", 10))\n        else:\n            self.setP(params.get(\"p\", 75.0))\n\n    def parameters(self):\n        strategy = self.__strategy\n        p = self.__p\n        k = self.__k\n\n        return {\"strategy\": strategy, \"p\": p, \"k\": k}\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        strategy = params.get(\"strategy\", RandomFeatureSelectEditor.Fixed)\n        k = params.get(\"k\", 10)\n        p = params.get(\"p\", 75.0)\n        if strategy == RandomFeatureSelectEditor.Fixed:\n            return preprocess.fss.SelectRandomFeatures(k=k)\n        elif strategy == RandomFeatureSelectEditor.Percentage:\n            return preprocess.fss.SelectRandomFeatures(k=p/100)\n        else:\n            # further implementations\n            raise NotImplementedError\n\n\ndef index_to_enum(enum, i):\n    \"\"\"Enums, by default, are not int-comparable, so use an ad-hoc mapping of\n    int to enum value at that position\"\"\"\n    return list(enum)[i]\n\n\ndef enum_to_index(enum, key):\n    \"\"\"Enums, by default, are not int-comparable, so use an ad-hoc mapping of\n    enum key to its int position\"\"\"\n    return list(enum).index(key)\n\nclass Scale(BaseEditor):\n    CenterByMean, ScaleBySD, NormalizeBySD, NormalizeBySpan_ZeroBased, \\\n        NormalizeSpan_NonZeroBased = 0, 1, 2, 3, 4\n\n    Names = {\n        NormalizeBySD: \"Standardize to μ=0, σ²=1\",\n        CenterByMean: \"Center to μ=0\",\n        ScaleBySD: \"Scale to σ²=1\",\n        NormalizeSpan_NonZeroBased: \"Normalize to interval [-1, 1]\",\n        NormalizeBySpan_ZeroBased: \"Normalize to interval [0, 1]\",\n    }\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.__method = Scale.NormalizeBySD\n        self.__group = group = QButtonGroup(self, exclusive=True)\n        group.buttonClicked.connect(self.__on_buttonClicked)\n\n        for methodid in [self.NormalizeBySD, self.CenterByMean, self.ScaleBySD,\n                         self.NormalizeSpan_NonZeroBased,\n                         self.NormalizeBySpan_ZeroBased]:\n            text = self.Names[methodid]\n            rb = QRadioButton(text=text, checked=self.__method == methodid)\n            group.addButton(rb, methodid)\n            self.layout().addWidget(rb)\n\n    def setMethod(self, method):\n        b = self.__group.button(method)\n        if b is not None:\n            b.setChecked(True)\n            self.__method = method\n            self.changed.emit()\n\n    def setParameters(self, params):\n        method = params.get(\"method\", Scale.NormalizeBySD)\n        self.setMethod(method)\n\n    def parameters(self):\n        return {\"method\": self.__method}\n\n    def __on_buttonClicked(self):\n        self.__method = self.__group.checkedId()\n        self.changed.emit()\n        self.edited.emit()\n\n    @staticmethod\n    def createinstance(params):\n        method = params.get(\"method\", Scale.NormalizeBySD)\n        if method == Scale.CenterByMean:\n            return _Scale(_Scale.CenteringType.Mean,\n                          _Scale.ScalingType.NoScaling)\n        elif method == Scale.ScaleBySD:\n            return _Scale(_Scale.CenteringType.NoCentering,\n                          _Scale.ScalingType.Std)\n        elif method == Scale.NormalizeBySD:\n            return Normalize(norm_type=Normalize.NormalizeBySD)\n        elif method == Scale.NormalizeBySpan_ZeroBased:\n            return Normalize(norm_type=Normalize.NormalizeBySpan)\n        else:  # method == Scale.NormalizeSpan_NonZeroBased\n            return Normalize(norm_type=Normalize.NormalizeBySpan,\n                             zero_based=False)\n\n    def __repr__(self):\n        return self.Names[self.__method]\n\n\nclass Randomize(BaseEditor):\n    RandomizeClasses, RandomizeAttributes, RandomizeMetas = _Randomize.Type\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        form = QFormLayout()\n        self.__rand_type_cb = QComboBox()\n        self.__rand_type_cb.addItems([\"Classes\",\n                                      \"Features\",\n                                      \"Meta data\"])\n\n        self.__rand_type_cb.currentIndexChanged.connect(self.changed)\n        self.__rand_type_cb.activated.connect(self.edited)\n\n        self.__rand_seed_ch = QCheckBox()\n        self.__rand_seed_ch.clicked.connect(self.edited)\n\n        form.addRow(\"Randomize:\", self.__rand_type_cb)\n        form.addRow(\"Replicable shuffling:\", self.__rand_seed_ch)\n        self.layout().addLayout(form)\n\n    def setParameters(self, params):\n        rand_type = params.get(\"rand_type\", Randomize.RandomizeClasses)\n        self.__rand_type_cb.setCurrentIndex(\n            enum_to_index(_Randomize.Type, rand_type))\n        self.__rand_seed_ch.setChecked(params.get(\"rand_seed\", 1) or 0)\n\n    def parameters(self):\n        return {\"rand_type\": index_to_enum(_Randomize.Type,\n                                           self.__rand_type_cb.currentIndex()),\n                \"rand_seed\": 1 if self.__rand_seed_ch.isChecked() else None}\n\n    @staticmethod\n    def createinstance(params):\n        rand_type = params.get(\"rand_type\", Randomize.RandomizeClasses)\n        rand_seed = params.get(\"rand_seed\", 1)\n        return _Randomize(rand_type=rand_type, rand_seed=rand_seed)\n\n    def __repr__(self):\n        return \"{}, {}\".format(self.__rand_type_cb.currentText(),\n                               \"Replicable\" if self.__rand_seed_ch.isChecked()\n                               else \"Not replicable\")\n\n\nclass PCA(BaseEditor):\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.n_components = 10\n\n        form = QFormLayout()\n        self.cspin = QSpinBox(minimum=1, value=self.n_components)\n        self.cspin.valueChanged[int].connect(self.setC)\n        self.cspin.editingFinished.connect(self.edited)\n\n        form.addRow(\"Components:\", self.cspin)\n        self.layout().addLayout(form)\n\n    def setParameters(self, params):\n        self.n_components = params.get(\"n_components\", 10)\n\n    def parameters(self):\n        return {\"n_components\": self.n_components}\n\n    def setC(self, n_components):\n        if self.n_components != n_components:\n            self.n_components = n_components\n            self.cspin.setValue(n_components)\n            self.changed.emit()\n\n    @staticmethod\n    def createinstance(params):\n        n_components = params.get(\"n_components\", 10)\n        return ProjectPCA(n_components=n_components)\n\n    def __repr__(self):\n        return \"Components: {}\".format(self.cspin.value())\n\n\nclass CUR(BaseEditor):\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.rank = 10\n        self.max_error = 1\n\n        form = QFormLayout()\n        self.rspin = QSpinBox(minimum=2, maximum=1000000, value=self.rank)\n        self.rspin.valueChanged[int].connect(self.setR)\n        self.rspin.editingFinished.connect(self.edited)\n        self.espin = QDoubleSpinBox(\n            minimum=0.1, maximum=100.0, singleStep=0.1,\n            value=self.max_error)\n        self.espin.valueChanged[float].connect(self.setE)\n        self.espin.editingFinished.connect(self.edited)\n\n        form.addRow(\"Rank:\", self.rspin)\n        form.addRow(\"Relative error:\", self.espin)\n        self.layout().addLayout(form)\n\n    def setParameters(self, params):\n        self.setR(params.get(\"rank\", 10))\n        self.setE(params.get(\"max_error\", 1))\n\n    def parameters(self):\n        return {\"rank\": self.rank, \"max_error\": self.max_error}\n\n    def setR(self, rank):\n        if self.rank != rank:\n            self.rank = rank\n            self.rspin.setValue(rank)\n            self.changed.emit()\n\n    def setE(self, max_error):\n        if self.max_error != max_error:\n            self.max_error = max_error\n            self.espin.setValue(max_error)\n            self.changed.emit()\n\n    @staticmethod\n    def createinstance(params):\n        rank = params.get(\"rank\", 10)\n        max_error = params.get(\"max_error\", 1)\n        return ProjectCUR(rank=rank, max_error=max_error)\n\n    def __repr__(self):\n        return \"Rank: {}, Relative error: {}\".format(self.rspin.value(),\n                                                     self.espin.value())\n\n\n# This is intended for future improvements.\n# I.e. it should be possible to add/register preprocessor actions\n# through entry points (for use by add-ons). Maybe it should be a\n# general framework (this is not the only place where such\n# functionality is desired (for instance in Orange v2.* Rank widget\n# already defines its own entry point).\nclass Description:\n    \"\"\"\n    A description of an action/function.\n    \"\"\"\n    def __init__(self, title, icon=None, summary=None, input_=None, output=None,\n                 requires=None, note=None, related=None, keywords=None,\n                 helptopic=None):\n        self.title = title\n        self.icon = icon\n        self.summary = summary\n        self.input = input_\n        self.output = output\n        self.requires = requires\n        self.note = note\n        self.related = related\n        self.keywords = keywords\n        self.helptopic = helptopic\n\n\nclass PreprocessAction:\n    def __init__(self, name, qualname, category, description, viewclass):\n        self.name = name\n        self.qualname = qualname\n        self.category = category\n        self.description = description\n        self.viewclass = viewclass\n\n\ndef icon_path(basename):\n    return pkg_resources.resource_filename(__name__, \"icons/\" + basename)\n\n\nPREPROCESS_ACTIONS = [\n    PreprocessAction(\n        \"Discretize\", \"orange.preprocess.discretize\", \"Discretization\",\n        Description(\"Discretize Continuous Variables\",\n                    icon_path(\"Discretize.svg\")),\n        DiscretizeEditor\n    ),\n    PreprocessAction(\n        \"Continuize\", \"orange.preprocess.continuize\", \"Continuization\",\n        Description(\"Continuize Discrete Variables\",\n                    icon_path(\"Continuize.svg\")),\n        ContinuizeEditor\n    ),\n    PreprocessAction(\n        \"Impute\", \"orange.preprocess.impute\", \"Impute\",\n        Description(\"Impute Missing Values\",\n                    icon_path(\"Impute.svg\")),\n        ImputeEditor\n    ),\n    PreprocessAction(\n        \"Feature Selection\", \"orange.preprocess.fss\", \"Feature Selection\",\n        Description(\"Select Relevant Features\",\n                    icon_path(\"SelectColumns.svg\")),\n        FeatureSelectEditor\n    ),\n    PreprocessAction(\n        \"Random Feature Selection\", \"orange.preprocess.randomfss\",\n        \"Random Feature Selection\",\n        Description(\"Select Random Features\",\n                    icon_path(\"SelectColumnsRandom.svg\")),\n        RandomFeatureSelectEditor\n    ),\n    PreprocessAction(\n        \"Normalize\", \"orange.preprocess.scale\", \"Scale\",\n        Description(\"Normalize Features\",\n                    icon_path(\"Normalize.svg\")),\n        Scale\n    ),\n    PreprocessAction(\n        \"Randomize\", \"orange.preprocess.randomize\", \"Randomization\",\n        Description(\"Randomize\",\n                    icon_path(\"Random.svg\")),\n        Randomize\n    ),\n    PreprocessAction(\n        \"Remove Sparse\", \"orange.preprocess.remove_sparse\", \"Feature Selection\",\n        Description(\"Remove Sparse Features\",\n                    icon_path(\"PurgeDomain.svg\")),\n        RemoveSparseEditor\n    ),\n    PreprocessAction(\n        \"PCA\", \"orange.preprocess.pca\", \"PCA\",\n        Description(\"Principal Component Analysis\",\n                    icon_path(\"PCA.svg\")),\n        PCA\n    ),\n    PreprocessAction(\n        \"CUR\", \"orange.preprocess.cur\", \"CUR\",\n        Description(\"CUR Matrix Decomposition\",\n                    icon_path(\"SelectColumns.svg\")),\n        CUR\n    )\n]\n\n\n# TODO: Extend with entry points here\n# PREPROCESS_ACTIONS += iter_entry_points(\"Orange.widgets.data.owpreprocess\")\n\n# ####\n# The actual owwidget\n# ####\n\n# Note:\n# The preprocessors are drag/dropped onto a sequence widget, where\n# they can be reordered/removed/edited.\n#\n# Model <-> Adapter/Controller <-> View\n#\n# * `Model`: the current constructed preprocessor model.\n# * the source (of drag/drop) is an item model displayed in a list\n#   view (source list).\n# * the drag/drop is controlled by the controller/adapter,\n\n\nclass OWPreprocess(widget.OWWidget):\n    name = \"Preprocess\"\n    description = \"Construct a data preprocessing pipeline.\"\n    icon = \"icons/Preprocess.svg\"\n    priority = 2105\n    keywords = [\"process\"]\n\n    settings_version = 2\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        preprocessor = Output(\"Preprocessor\", preprocess.preprocess.Preprocess, dynamic=False)\n        preprocessed_data = Output(\"Preprocessed Data\", Orange.data.Table)\n\n    storedsettings = Setting({})\n    autocommit = Setting(True)\n    PREPROCESSORS = PREPROCESS_ACTIONS\n    CONTROLLER = Controller\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self._invalidated = False\n\n        # List of available preprocessors (DescriptionRole : Description)\n        self.preprocessors = QStandardItemModel()\n\n        def mimeData(indexlist):\n            assert len(indexlist) == 1\n            index = indexlist[0]\n            qname = index.data(DescriptionRole).qualname\n            m = QMimeData()\n            m.setData(\"application/x-qwidget-ref\", qname.encode(\"utf-8\"))\n            return m\n        # TODO: Fix this (subclass even if just to pass a function\n        # for mimeData delegate)\n        self.preprocessors.mimeData = mimeData\n\n        box = gui.vBox(self.controlArea, \"Preprocessors\")\n\n        self.preprocessorsView = view = QListView(\n            selectionMode=QListView.SingleSelection,\n            dragEnabled=True,\n            dragDropMode=QListView.DragOnly\n        )\n        view.setModel(self.preprocessors)\n        view.activated.connect(self.__activated)\n\n        box.layout().addWidget(view)\n\n        ####\n        self._qname2ppdef = {ppdef.qualname: ppdef for\n                             ppdef in self.PREPROCESSORS}\n\n        # List of 'selected' preprocessors and their parameters.\n        self.preprocessormodel = None\n\n        self.flow_view = SequenceFlow()\n        self.controler = self.CONTROLLER(self.flow_view, parent=self)\n\n        self.overlay = OverlayWidget(self)\n        self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents)\n        self.overlay.setWidget(self.flow_view)\n        self.overlay.setLayout(QVBoxLayout())\n        self.overlay.layout().addWidget(\n            QLabel(\"Drag items from the list on the left\", wordWrap=True))\n\n        self.scroll_area = QScrollArea(\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn\n        )\n        self.scroll_area.viewport().setAcceptDrops(True)\n        self.scroll_area.setWidget(self.flow_view)\n        self.scroll_area.setWidgetResizable(True)\n        self.mainArea.layout().addWidget(self.scroll_area)\n        self.flow_view.installEventFilter(self)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.auto_send(box, self, \"autocommit\", box=False)\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        self._initialize()\n\n    def _initialize(self):\n        for pp_def in self.PREPROCESSORS:\n            description = pp_def.description\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item = QStandardItem(icon, description.title)\n            item.setToolTip(description.summary or \"\")\n            item.setData(pp_def, DescriptionRole)\n            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable |\n                          Qt.ItemIsDragEnabled)\n            self.preprocessors.appendRow([item])\n\n        model = self.load(self.storedsettings)\n\n        self.set_model(model)\n\n        if not model.rowCount():\n            # enforce default width constraint if no preprocessors\n            # are instantiated (if the model is not empty the constraints\n            # will be triggered by LayoutRequest event on the `flow_view`)\n            self.__update_size_constraint()\n\n        self.apply()\n\n    def load(self, saved):\n        \"\"\"Load a preprocessor list from a dict.\"\"\"\n        preprocessors = saved.get(\"preprocessors\", [])\n        model = StandardItemModel()\n\n        def dropMimeData(data, action, row, _column, _parent):\n            if data.hasFormat(\"application/x-qwidget-ref\") and \\\n                    action == Qt.CopyAction:\n                qname = bytes(data.data(\"application/x-qwidget-ref\")).decode()\n\n                ppdef = self._qname2ppdef[qname]\n                item = QStandardItem(ppdef.description.title)\n                item.setData({}, ParametersRole)\n                item.setData(ppdef.description.title, Qt.DisplayRole)\n                item.setData(ppdef, DescriptionRole)\n                self.preprocessormodel.insertRow(row, [item])\n                return True\n            else:\n                return False\n\n        model.dropMimeData = dropMimeData\n\n        for qualname, params in preprocessors:\n            pp_def = self._qname2ppdef[qualname]\n            description = pp_def.description\n            item = QStandardItem(description.title)\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item.setIcon(icon)\n            item.setToolTip(description.summary)\n            item.setData(pp_def, DescriptionRole)\n            item.setData(params, ParametersRole)\n\n            model.appendRow(item)\n        return model\n\n    @staticmethod\n    def save(model):\n        \"\"\"Save the preprocessor list to a dict.\"\"\"\n        d = {\"name\": \"\"}\n        preprocessors = []\n        for i in range(model.rowCount()):\n            item = model.item(i)\n            pp_def = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n            preprocessors.append((pp_def.qualname, params))\n\n        d[\"preprocessors\"] = preprocessors\n        return d\n\n\n    def set_model(self, ppmodel):\n        if self.preprocessormodel:\n            self.preprocessormodel.dataChanged.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.deleteLater()\n\n        self.preprocessormodel = ppmodel\n        self.controler.setModel(ppmodel)\n        if ppmodel is not None:\n            self.preprocessormodel.dataChanged.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged)\n\n        self.__update_overlay()\n\n    def __update_overlay(self):\n        if self.preprocessormodel is None or \\\n                self.preprocessormodel.rowCount() == 0:\n            self.overlay.setWidget(self.flow_view)\n            self.overlay.show()\n        else:\n            self.overlay.setWidget(None)\n            self.overlay.hide()\n\n    def __on_modelchanged(self):\n        self.__update_overlay()\n        self.commit()\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.data = data\n        if data is not None:\n            self.info.set_input_summary(len(data))\n        else:\n            self.info.set_input_summary(self.info.NoInput)\n\n    def handleNewSignals(self):\n        self.apply()\n\n    def __activated(self, index):\n        item = self.preprocessors.itemFromIndex(index)\n        action = item.data(DescriptionRole)\n        item = QStandardItem()\n        item.setData({}, ParametersRole)\n        item.setData(action.description.title, Qt.DisplayRole)\n        item.setData(action, DescriptionRole)\n        self.preprocessormodel.appendRow([item])\n\n    def buildpreproc(self):\n        plist = []\n        for i in range(self.preprocessormodel.rowCount()):\n            item = self.preprocessormodel.item(i)\n            desc = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n\n            if not isinstance(params, dict):\n                params = {}\n\n            create = desc.viewclass.createinstance\n            plist.append(create(params))\n\n        if len(plist) == 1:\n            return plist[0]\n        else:\n            return preprocess.preprocess.PreprocessorList(plist)\n\n    def apply(self):\n        # Sync the model into storedsettings on every apply.\n        self.storeSpecificSettings()\n        preprocessor = self.buildpreproc()\n\n        if self.data is not None:\n            self.error()\n            try:\n                data = preprocessor(self.data)\n            except (ValueError, ZeroDivisionError) as e:\n                self.error(str(e))\n                return\n            self.info.set_output_summary(len(data))\n        else:\n            data = None\n            self.info.set_output_summary(self.info.NoOutput)\n\n        self.Outputs.preprocessor.send(preprocessor)\n        self.Outputs.preprocessed_data.send(data)\n\n    def commit(self):\n        if not self._invalidated:\n            self._invalidated = True\n            QApplication.postEvent(self, QEvent(QEvent.User))\n\n    def customEvent(self, event):\n        if event.type() == QEvent.User and self._invalidated:\n            self._invalidated = False\n            self.apply()\n\n    def eventFilter(self, receiver, event):\n        if receiver is self.flow_view and event.type() == QEvent.LayoutRequest:\n            QTimer.singleShot(0, self.__update_size_constraint)\n\n        return super().eventFilter(receiver, event)\n\n    def storeSpecificSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().storeSpecificSettings()\n\n    def saveSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().saveSettings()\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version < 2:\n            for action, params in settings[\"storedsettings\"][\"preprocessors\"]:\n                if action == \"orange.preprocess.scale\":\n                    scale = center = None\n                    if \"center\" in params:\n                        center = params.pop(\"center\").name\n                    if \"scale\" in params:\n                        scale = params.pop(\"scale\").name\n                    migratable = {\n                        (\"Mean\", \"NoScaling\"): Scale.CenterByMean,\n                        (\"NoCentering\", \"Std\"): Scale.ScaleBySD,\n                        (\"Mean\", \"Std\"): Scale.NormalizeBySD,\n                        (\"NoCentering\", \"Span\"): Scale.NormalizeBySpan_ZeroBased\n                    }\n                    params[\"method\"] = \\\n                        migratable.get((center, scale), Scale.NormalizeBySD)\n\n    def onDeleteWidget(self):\n        self.data = None\n        self.set_model(None)\n        super().onDeleteWidget()\n\n    @Slot()\n    def __update_size_constraint(self):\n        # Update minimum width constraint on the scroll area containing\n        # the 'instantiated' preprocessor list (to avoid the horizontal\n        # scroll bar).\n        sh = self.flow_view.minimumSizeHint()\n        scroll_width = self.scroll_area.verticalScrollBar().width()\n        self.scroll_area.setMinimumWidth(\n            min(max(sh.width() + scroll_width + 2, self.controlArea.width()),\n                520))\n\n    def sizeHint(self):\n        sh = super().sizeHint()\n        return sh.expandedTo(QSize(sh.width() + 300, 500))\n\n    def send_report(self):\n        pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w)\n              for i, w in enumerate(self.controler.view.widgets())]\n        if pp:\n            self.report_items(\"Settings\", pp)\n\nif __name__ == \"__main__\":  # pragma: no cover\n    WidgetPreview(OWPreprocess).run(Orange.data.Table(\"brown-selected\"))\n",
            "header": {
                "file": {
                    "path": "Orange/widgets/data/owpreprocess.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "bf776df70",
                "new_commit_hash": "238be791e",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/widgets/data/owpreprocess.py"
            },
            "plus_file": {
                "path": "b/Orange/widgets/data/owpreprocess.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 1144,
                            "length": 6
                        },
                        "new_range": {
                            "start": 1144,
                            "length": 9
                        },
                        "section": "class OWPreprocess(widget.OWWidget):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        box = gui.vBox(self.controlArea, \"Output\")",
                                "type": "context"
                            },
                            {
                                "content": "        gui.auto_send(box, self, \"autocommit\", box=False)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        self.info.set_input_summary(self.info.NoInput)",
                                "type": "added"
                            },
                            {
                                "content": "        self.info.set_output_summary(self.info.NoOutput)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        self._initialize()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def _initialize(self):",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owpreprocess.py",
                            "type": "class",
                            "name": "OWPreprocess",
                            "content": "class OWPreprocess(widget.OWWidget):\n    name = \"Preprocess\"\n    description = \"Construct a data preprocessing pipeline.\"\n    icon = \"icons/Preprocess.svg\"\n    priority = 2105\n    keywords = [\"process\"]\n\n    settings_version = 2\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        preprocessor = Output(\"Preprocessor\", preprocess.preprocess.Preprocess, dynamic=False)\n        preprocessed_data = Output(\"Preprocessed Data\", Orange.data.Table)\n\n    storedsettings = Setting({})\n    autocommit = Setting(True)\n    PREPROCESSORS = PREPROCESS_ACTIONS\n    CONTROLLER = Controller\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self._invalidated = False\n\n        # List of available preprocessors (DescriptionRole : Description)\n        self.preprocessors = QStandardItemModel()\n\n        def mimeData(indexlist):\n            assert len(indexlist) == 1\n            index = indexlist[0]\n            qname = index.data(DescriptionRole).qualname\n            m = QMimeData()\n            m.setData(\"application/x-qwidget-ref\", qname.encode(\"utf-8\"))\n            return m\n        # TODO: Fix this (subclass even if just to pass a function\n        # for mimeData delegate)\n        self.preprocessors.mimeData = mimeData\n\n        box = gui.vBox(self.controlArea, \"Preprocessors\")\n\n        self.preprocessorsView = view = QListView(\n            selectionMode=QListView.SingleSelection,\n            dragEnabled=True,\n            dragDropMode=QListView.DragOnly\n        )\n        view.setModel(self.preprocessors)\n        view.activated.connect(self.__activated)\n\n        box.layout().addWidget(view)\n\n        ####\n        self._qname2ppdef = {ppdef.qualname: ppdef for\n                             ppdef in self.PREPROCESSORS}\n\n        # List of 'selected' preprocessors and their parameters.\n        self.preprocessormodel = None\n\n        self.flow_view = SequenceFlow()\n        self.controler = self.CONTROLLER(self.flow_view, parent=self)\n\n        self.overlay = OverlayWidget(self)\n        self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents)\n        self.overlay.setWidget(self.flow_view)\n        self.overlay.setLayout(QVBoxLayout())\n        self.overlay.layout().addWidget(\n            QLabel(\"Drag items from the list on the left\", wordWrap=True))\n\n        self.scroll_area = QScrollArea(\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn\n        )\n        self.scroll_area.viewport().setAcceptDrops(True)\n        self.scroll_area.setWidget(self.flow_view)\n        self.scroll_area.setWidgetResizable(True)\n        self.mainArea.layout().addWidget(self.scroll_area)\n        self.flow_view.installEventFilter(self)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.auto_send(box, self, \"autocommit\", box=False)\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        self._initialize()\n\n    def _initialize(self):\n        for pp_def in self.PREPROCESSORS:\n            description = pp_def.description\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item = QStandardItem(icon, description.title)\n            item.setToolTip(description.summary or \"\")\n            item.setData(pp_def, DescriptionRole)\n            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable |\n                          Qt.ItemIsDragEnabled)\n            self.preprocessors.appendRow([item])\n\n        model = self.load(self.storedsettings)\n\n        self.set_model(model)\n\n        if not model.rowCount():\n            # enforce default width constraint if no preprocessors\n            # are instantiated (if the model is not empty the constraints\n            # will be triggered by LayoutRequest event on the `flow_view`)\n            self.__update_size_constraint()\n\n        self.apply()\n\n    def load(self, saved):\n        \"\"\"Load a preprocessor list from a dict.\"\"\"\n        preprocessors = saved.get(\"preprocessors\", [])\n        model = StandardItemModel()\n\n        def dropMimeData(data, action, row, _column, _parent):\n            if data.hasFormat(\"application/x-qwidget-ref\") and \\\n                    action == Qt.CopyAction:\n                qname = bytes(data.data(\"application/x-qwidget-ref\")).decode()\n\n                ppdef = self._qname2ppdef[qname]\n                item = QStandardItem(ppdef.description.title)\n                item.setData({}, ParametersRole)\n                item.setData(ppdef.description.title, Qt.DisplayRole)\n                item.setData(ppdef, DescriptionRole)\n                self.preprocessormodel.insertRow(row, [item])\n                return True\n            else:\n                return False\n\n        model.dropMimeData = dropMimeData\n\n        for qualname, params in preprocessors:\n            pp_def = self._qname2ppdef[qualname]\n            description = pp_def.description\n            item = QStandardItem(description.title)\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item.setIcon(icon)\n            item.setToolTip(description.summary)\n            item.setData(pp_def, DescriptionRole)\n            item.setData(params, ParametersRole)\n\n            model.appendRow(item)\n        return model\n\n    @staticmethod\n    def save(model):\n        \"\"\"Save the preprocessor list to a dict.\"\"\"\n        d = {\"name\": \"\"}\n        preprocessors = []\n        for i in range(model.rowCount()):\n            item = model.item(i)\n            pp_def = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n            preprocessors.append((pp_def.qualname, params))\n\n        d[\"preprocessors\"] = preprocessors\n        return d\n\n\n    def set_model(self, ppmodel):\n        if self.preprocessormodel:\n            self.preprocessormodel.dataChanged.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.deleteLater()\n\n        self.preprocessormodel = ppmodel\n        self.controler.setModel(ppmodel)\n        if ppmodel is not None:\n            self.preprocessormodel.dataChanged.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged)\n\n        self.__update_overlay()\n\n    def __update_overlay(self):\n        if self.preprocessormodel is None or \\\n                self.preprocessormodel.rowCount() == 0:\n            self.overlay.setWidget(self.flow_view)\n            self.overlay.show()\n        else:\n            self.overlay.setWidget(None)\n            self.overlay.hide()\n\n    def __on_modelchanged(self):\n        self.__update_overlay()\n        self.commit()\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.data = data\n        if data is not None:\n            self.info.set_input_summary(len(data))\n        else:\n            self.info.set_input_summary(self.info.NoInput)\n\n    def handleNewSignals(self):\n        self.apply()\n\n    def __activated(self, index):\n        item = self.preprocessors.itemFromIndex(index)\n        action = item.data(DescriptionRole)\n        item = QStandardItem()\n        item.setData({}, ParametersRole)\n        item.setData(action.description.title, Qt.DisplayRole)\n        item.setData(action, DescriptionRole)\n        self.preprocessormodel.appendRow([item])\n\n    def buildpreproc(self):\n        plist = []\n        for i in range(self.preprocessormodel.rowCount()):\n            item = self.preprocessormodel.item(i)\n            desc = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n\n            if not isinstance(params, dict):\n                params = {}\n\n            create = desc.viewclass.createinstance\n            plist.append(create(params))\n\n        if len(plist) == 1:\n            return plist[0]\n        else:\n            return preprocess.preprocess.PreprocessorList(plist)\n\n    def apply(self):\n        # Sync the model into storedsettings on every apply.\n        self.storeSpecificSettings()\n        preprocessor = self.buildpreproc()\n\n        if self.data is not None:\n            self.error()\n            try:\n                data = preprocessor(self.data)\n            except (ValueError, ZeroDivisionError) as e:\n                self.error(str(e))\n                return\n            self.info.set_output_summary(len(data))\n        else:\n            data = None\n            self.info.set_output_summary(self.info.NoOutput)\n\n        self.Outputs.preprocessor.send(preprocessor)\n        self.Outputs.preprocessed_data.send(data)\n\n    def commit(self):\n        if not self._invalidated:\n            self._invalidated = True\n            QApplication.postEvent(self, QEvent(QEvent.User))\n\n    def customEvent(self, event):\n        if event.type() == QEvent.User and self._invalidated:\n            self._invalidated = False\n            self.apply()\n\n    def eventFilter(self, receiver, event):\n        if receiver is self.flow_view and event.type() == QEvent.LayoutRequest:\n            QTimer.singleShot(0, self.__update_size_constraint)\n\n        return super().eventFilter(receiver, event)\n\n    def storeSpecificSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().storeSpecificSettings()\n\n    def saveSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().saveSettings()\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version < 2:\n            for action, params in settings[\"storedsettings\"][\"preprocessors\"]:\n                if action == \"orange.preprocess.scale\":\n                    scale = center = None\n                    if \"center\" in params:\n                        center = params.pop(\"center\").name\n                    if \"scale\" in params:\n                        scale = params.pop(\"scale\").name\n                    migratable = {\n                        (\"Mean\", \"NoScaling\"): Scale.CenterByMean,\n                        (\"NoCentering\", \"Std\"): Scale.ScaleBySD,\n                        (\"Mean\", \"Std\"): Scale.NormalizeBySD,\n                        (\"NoCentering\", \"Span\"): Scale.NormalizeBySpan_ZeroBased\n                    }\n                    params[\"method\"] = \\\n                        migratable.get((center, scale), Scale.NormalizeBySD)\n\n    def onDeleteWidget(self):\n        self.data = None\n        self.set_model(None)\n        super().onDeleteWidget()\n\n    @Slot()\n    def __update_size_constraint(self):\n        # Update minimum width constraint on the scroll area containing\n        # the 'instantiated' preprocessor list (to avoid the horizontal\n        # scroll bar).\n        sh = self.flow_view.minimumSizeHint()\n        scroll_width = self.scroll_area.verticalScrollBar().width()\n        self.scroll_area.setMinimumWidth(\n            min(max(sh.width() + scroll_width + 2, self.controlArea.width()),\n                520))\n\n    def sizeHint(self):\n        sh = super().sizeHint()\n        return sh.expandedTo(QSize(sh.width() + 300, 500))\n\n    def send_report(self):\n        pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w)\n              for i, w in enumerate(self.controler.view.widgets())]\n        if pp:\n            self.report_items(\"Settings\", pp)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 1065,
                            "end_lineno": 1391,
                            "parent": null
                        },
                        {
                            "file_name": "Orange/widgets/data/owpreprocess.py",
                            "type": "method",
                            "name": "OWPreprocess.__init__",
                            "content": "class OWPreprocess(widget.OWWidget):\n    name = \"Preprocess\"\n    description = \"Construct a data preprocessing pipeline.\"\n    icon = \"icons/Preprocess.svg\"\n    priority = 2105\n    keywords = [\"process\"]\n\n    settings_version = 2\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        preprocessor = Output(\"Preprocessor\", preprocess.preprocess.Preprocess, dynamic=False)\n        preprocessed_data = Output(\"Preprocessed Data\", Orange.data.Table)\n\n    storedsettings = Setting({})\n    autocommit = Setting(True)\n    PREPROCESSORS = PREPROCESS_ACTIONS\n    CONTROLLER = Controller\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self._invalidated = False\n\n        # List of available preprocessors (DescriptionRole : Description)\n        self.preprocessors = QStandardItemModel()\n\n        def mimeData(indexlist):\n            assert len(indexlist) == 1\n            index = indexlist[0]\n            qname = index.data(DescriptionRole).qualname\n            m = QMimeData()\n            m.setData(\"application/x-qwidget-ref\", qname.encode(\"utf-8\"))\n            return m\n        # TODO: Fix this (subclass even if just to pass a function\n        # for mimeData delegate)\n        self.preprocessors.mimeData = mimeData\n\n        box = gui.vBox(self.controlArea, \"Preprocessors\")\n\n        self.preprocessorsView = view = QListView(\n            selectionMode=QListView.SingleSelection,\n            dragEnabled=True,\n            dragDropMode=QListView.DragOnly\n        )\n        view.setModel(self.preprocessors)\n        view.activated.connect(self.__activated)\n\n        box.layout().addWidget(view)\n\n        ####\n        self._qname2ppdef = {ppdef.qualname: ppdef for\n                             ppdef in self.PREPROCESSORS}\n\n        # List of 'selected' preprocessors and their parameters.\n        self.preprocessormodel = None\n\n        self.flow_view = SequenceFlow()\n        self.controler = self.CONTROLLER(self.flow_view, parent=self)\n\n        self.overlay = OverlayWidget(self)\n        self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents)\n        self.overlay.setWidget(self.flow_view)\n        self.overlay.setLayout(QVBoxLayout())\n        self.overlay.layout().addWidget(\n            QLabel(\"Drag items from the list on the left\", wordWrap=True))\n\n        self.scroll_area = QScrollArea(\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn\n        )\n        self.scroll_area.viewport().setAcceptDrops(True)\n        self.scroll_area.setWidget(self.flow_view)\n        self.scroll_area.setWidgetResizable(True)\n        self.mainArea.layout().addWidget(self.scroll_area)\n        self.flow_view.installEventFilter(self)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.auto_send(box, self, \"autocommit\", box=False)\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        self._initialize()\n\n    def _initialize(self):\n        for pp_def in self.PREPROCESSORS:\n            description = pp_def.description\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item = QStandardItem(icon, description.title)\n            item.setToolTip(description.summary or \"\")\n            item.setData(pp_def, DescriptionRole)\n            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable |\n                          Qt.ItemIsDragEnabled)\n            self.preprocessors.appendRow([item])\n\n        model = self.load(self.storedsettings)\n\n        self.set_model(model)\n\n        if not model.rowCount():\n            # enforce default width constraint if no preprocessors\n            # are instantiated (if the model is not empty the constraints\n            # will be triggered by LayoutRequest event on the `flow_view`)\n            self.__update_size_constraint()\n\n        self.apply()\n\n    def load(self, saved):\n        \"\"\"Load a preprocessor list from a dict.\"\"\"\n        preprocessors = saved.get(\"preprocessors\", [])\n        model = StandardItemModel()\n\n        def dropMimeData(data, action, row, _column, _parent):\n            if data.hasFormat(\"application/x-qwidget-ref\") and \\\n                    action == Qt.CopyAction:\n                qname = bytes(data.data(\"application/x-qwidget-ref\")).decode()\n\n                ppdef = self._qname2ppdef[qname]\n                item = QStandardItem(ppdef.description.title)\n                item.setData({}, ParametersRole)\n                item.setData(ppdef.description.title, Qt.DisplayRole)\n                item.setData(ppdef, DescriptionRole)\n                self.preprocessormodel.insertRow(row, [item])\n                return True\n            else:\n                return False\n\n        model.dropMimeData = dropMimeData\n\n        for qualname, params in preprocessors:\n            pp_def = self._qname2ppdef[qualname]\n            description = pp_def.description\n            item = QStandardItem(description.title)\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item.setIcon(icon)\n            item.setToolTip(description.summary)\n            item.setData(pp_def, DescriptionRole)\n            item.setData(params, ParametersRole)\n\n            model.appendRow(item)\n        return model\n\n    @staticmethod\n    def save(model):\n        \"\"\"Save the preprocessor list to a dict.\"\"\"\n        d = {\"name\": \"\"}\n        preprocessors = []\n        for i in range(model.rowCount()):\n            item = model.item(i)\n            pp_def = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n            preprocessors.append((pp_def.qualname, params))\n\n        d[\"preprocessors\"] = preprocessors\n        return d\n\n\n    def set_model(self, ppmodel):\n        if self.preprocessormodel:\n            self.preprocessormodel.dataChanged.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.deleteLater()\n\n        self.preprocessormodel = ppmodel\n        self.controler.setModel(ppmodel)\n        if ppmodel is not None:\n            self.preprocessormodel.dataChanged.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged)\n\n        self.__update_overlay()\n\n    def __update_overlay(self):\n        if self.preprocessormodel is None or \\\n                self.preprocessormodel.rowCount() == 0:\n            self.overlay.setWidget(self.flow_view)\n            self.overlay.show()\n        else:\n            self.overlay.setWidget(None)\n            self.overlay.hide()\n\n    def __on_modelchanged(self):\n        self.__update_overlay()\n        self.commit()\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.data = data\n        if data is not None:\n            self.info.set_input_summary(len(data))\n        else:\n            self.info.set_input_summary(self.info.NoInput)\n\n    def handleNewSignals(self):\n        self.apply()\n\n    def __activated(self, index):\n        item = self.preprocessors.itemFromIndex(index)\n        action = item.data(DescriptionRole)\n        item = QStandardItem()\n        item.setData({}, ParametersRole)\n        item.setData(action.description.title, Qt.DisplayRole)\n        item.setData(action, DescriptionRole)\n        self.preprocessormodel.appendRow([item])\n\n    def buildpreproc(self):\n        plist = []\n        for i in range(self.preprocessormodel.rowCount()):\n            item = self.preprocessormodel.item(i)\n            desc = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n\n            if not isinstance(params, dict):\n                params = {}\n\n            create = desc.viewclass.createinstance\n            plist.append(create(params))\n\n        if len(plist) == 1:\n            return plist[0]\n        else:\n            return preprocess.preprocess.PreprocessorList(plist)\n\n    def apply(self):\n        # Sync the model into storedsettings on every apply.\n        self.storeSpecificSettings()\n        preprocessor = self.buildpreproc()\n\n        if self.data is not None:\n            self.error()\n            try:\n                data = preprocessor(self.data)\n            except (ValueError, ZeroDivisionError) as e:\n                self.error(str(e))\n                return\n            self.info.set_output_summary(len(data))\n        else:\n            data = None\n            self.info.set_output_summary(self.info.NoOutput)\n\n        self.Outputs.preprocessor.send(preprocessor)\n        self.Outputs.preprocessed_data.send(data)\n\n    def commit(self):\n        if not self._invalidated:\n            self._invalidated = True\n            QApplication.postEvent(self, QEvent(QEvent.User))\n\n    def customEvent(self, event):\n        if event.type() == QEvent.User and self._invalidated:\n            self._invalidated = False\n            self.apply()\n\n    def eventFilter(self, receiver, event):\n        if receiver is self.flow_view and event.type() == QEvent.LayoutRequest:\n            QTimer.singleShot(0, self.__update_size_constraint)\n\n        return super().eventFilter(receiver, event)\n\n    def storeSpecificSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().storeSpecificSettings()\n\n    def saveSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().saveSettings()\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version < 2:\n            for action, params in settings[\"storedsettings\"][\"preprocessors\"]:\n                if action == \"orange.preprocess.scale\":\n                    scale = center = None\n                    if \"center\" in params:\n                        center = params.pop(\"center\").name\n                    if \"scale\" in params:\n                        scale = params.pop(\"scale\").name\n                    migratable = {\n                        (\"Mean\", \"NoScaling\"): Scale.CenterByMean,\n                        (\"NoCentering\", \"Std\"): Scale.ScaleBySD,\n                        (\"Mean\", \"Std\"): Scale.NormalizeBySD,\n                        (\"NoCentering\", \"Span\"): Scale.NormalizeBySpan_ZeroBased\n                    }\n                    params[\"method\"] = \\\n                        migratable.get((center, scale), Scale.NormalizeBySD)\n\n    def onDeleteWidget(self):\n        self.data = None\n        self.set_model(None)\n        super().onDeleteWidget()\n\n    @Slot()\n    def __update_size_constraint(self):\n        # Update minimum width constraint on the scroll area containing\n        # the 'instantiated' preprocessor list (to avoid the horizontal\n        # scroll bar).\n        sh = self.flow_view.minimumSizeHint()\n        scroll_width = self.scroll_area.verticalScrollBar().width()\n        self.scroll_area.setMinimumWidth(\n            min(max(sh.width() + scroll_width + 2, self.controlArea.width()),\n                520))\n\n    def sizeHint(self):\n        sh = super().sizeHint()\n        return sh.expandedTo(QSize(sh.width() + 300, 500))\n\n    def send_report(self):\n        pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w)\n              for i, w in enumerate(self.controler.view.widgets())]\n        if pp:\n            self.report_items(\"Settings\", pp)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 1086,
                            "end_lineno": 1150,
                            "parent": {
                                "file_name": "Orange/widgets/data/owpreprocess.py",
                                "type": "class",
                                "name": "OWPreprocess",
                                "content": "class OWPreprocess(widget.OWWidget):\n    name = \"Preprocess\"\n    description = \"Construct a data preprocessing pipeline.\"\n    icon = \"icons/Preprocess.svg\"\n    priority = 2105\n    keywords = [\"process\"]\n\n    settings_version = 2\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        preprocessor = Output(\"Preprocessor\", preprocess.preprocess.Preprocess, dynamic=False)\n        preprocessed_data = Output(\"Preprocessed Data\", Orange.data.Table)\n\n    storedsettings = Setting({})\n    autocommit = Setting(True)\n    PREPROCESSORS = PREPROCESS_ACTIONS\n    CONTROLLER = Controller\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self._invalidated = False\n\n        # List of available preprocessors (DescriptionRole : Description)\n        self.preprocessors = QStandardItemModel()\n\n        def mimeData(indexlist):\n            assert len(indexlist) == 1\n            index = indexlist[0]\n            qname = index.data(DescriptionRole).qualname\n            m = QMimeData()\n            m.setData(\"application/x-qwidget-ref\", qname.encode(\"utf-8\"))\n            return m\n        # TODO: Fix this (subclass even if just to pass a function\n        # for mimeData delegate)\n        self.preprocessors.mimeData = mimeData\n\n        box = gui.vBox(self.controlArea, \"Preprocessors\")\n\n        self.preprocessorsView = view = QListView(\n            selectionMode=QListView.SingleSelection,\n            dragEnabled=True,\n            dragDropMode=QListView.DragOnly\n        )\n        view.setModel(self.preprocessors)\n        view.activated.connect(self.__activated)\n\n        box.layout().addWidget(view)\n\n        ####\n        self._qname2ppdef = {ppdef.qualname: ppdef for\n                             ppdef in self.PREPROCESSORS}\n\n        # List of 'selected' preprocessors and their parameters.\n        self.preprocessormodel = None\n\n        self.flow_view = SequenceFlow()\n        self.controler = self.CONTROLLER(self.flow_view, parent=self)\n\n        self.overlay = OverlayWidget(self)\n        self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents)\n        self.overlay.setWidget(self.flow_view)\n        self.overlay.setLayout(QVBoxLayout())\n        self.overlay.layout().addWidget(\n            QLabel(\"Drag items from the list on the left\", wordWrap=True))\n\n        self.scroll_area = QScrollArea(\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn\n        )\n        self.scroll_area.viewport().setAcceptDrops(True)\n        self.scroll_area.setWidget(self.flow_view)\n        self.scroll_area.setWidgetResizable(True)\n        self.mainArea.layout().addWidget(self.scroll_area)\n        self.flow_view.installEventFilter(self)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.auto_send(box, self, \"autocommit\", box=False)\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        self._initialize()\n\n    def _initialize(self):\n        for pp_def in self.PREPROCESSORS:\n            description = pp_def.description\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item = QStandardItem(icon, description.title)\n            item.setToolTip(description.summary or \"\")\n            item.setData(pp_def, DescriptionRole)\n            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable |\n                          Qt.ItemIsDragEnabled)\n            self.preprocessors.appendRow([item])\n\n        model = self.load(self.storedsettings)\n\n        self.set_model(model)\n\n        if not model.rowCount():\n            # enforce default width constraint if no preprocessors\n            # are instantiated (if the model is not empty the constraints\n            # will be triggered by LayoutRequest event on the `flow_view`)\n            self.__update_size_constraint()\n\n        self.apply()\n\n    def load(self, saved):\n        \"\"\"Load a preprocessor list from a dict.\"\"\"\n        preprocessors = saved.get(\"preprocessors\", [])\n        model = StandardItemModel()\n\n        def dropMimeData(data, action, row, _column, _parent):\n            if data.hasFormat(\"application/x-qwidget-ref\") and \\\n                    action == Qt.CopyAction:\n                qname = bytes(data.data(\"application/x-qwidget-ref\")).decode()\n\n                ppdef = self._qname2ppdef[qname]\n                item = QStandardItem(ppdef.description.title)\n                item.setData({}, ParametersRole)\n                item.setData(ppdef.description.title, Qt.DisplayRole)\n                item.setData(ppdef, DescriptionRole)\n                self.preprocessormodel.insertRow(row, [item])\n                return True\n            else:\n                return False\n\n        model.dropMimeData = dropMimeData\n\n        for qualname, params in preprocessors:\n            pp_def = self._qname2ppdef[qualname]\n            description = pp_def.description\n            item = QStandardItem(description.title)\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item.setIcon(icon)\n            item.setToolTip(description.summary)\n            item.setData(pp_def, DescriptionRole)\n            item.setData(params, ParametersRole)\n\n            model.appendRow(item)\n        return model\n\n    @staticmethod\n    def save(model):\n        \"\"\"Save the preprocessor list to a dict.\"\"\"\n        d = {\"name\": \"\"}\n        preprocessors = []\n        for i in range(model.rowCount()):\n            item = model.item(i)\n            pp_def = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n            preprocessors.append((pp_def.qualname, params))\n\n        d[\"preprocessors\"] = preprocessors\n        return d\n\n\n    def set_model(self, ppmodel):\n        if self.preprocessormodel:\n            self.preprocessormodel.dataChanged.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.deleteLater()\n\n        self.preprocessormodel = ppmodel\n        self.controler.setModel(ppmodel)\n        if ppmodel is not None:\n            self.preprocessormodel.dataChanged.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged)\n\n        self.__update_overlay()\n\n    def __update_overlay(self):\n        if self.preprocessormodel is None or \\\n                self.preprocessormodel.rowCount() == 0:\n            self.overlay.setWidget(self.flow_view)\n            self.overlay.show()\n        else:\n            self.overlay.setWidget(None)\n            self.overlay.hide()\n\n    def __on_modelchanged(self):\n        self.__update_overlay()\n        self.commit()\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.data = data\n        if data is not None:\n            self.info.set_input_summary(len(data))\n        else:\n            self.info.set_input_summary(self.info.NoInput)\n\n    def handleNewSignals(self):\n        self.apply()\n\n    def __activated(self, index):\n        item = self.preprocessors.itemFromIndex(index)\n        action = item.data(DescriptionRole)\n        item = QStandardItem()\n        item.setData({}, ParametersRole)\n        item.setData(action.description.title, Qt.DisplayRole)\n        item.setData(action, DescriptionRole)\n        self.preprocessormodel.appendRow([item])\n\n    def buildpreproc(self):\n        plist = []\n        for i in range(self.preprocessormodel.rowCount()):\n            item = self.preprocessormodel.item(i)\n            desc = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n\n            if not isinstance(params, dict):\n                params = {}\n\n            create = desc.viewclass.createinstance\n            plist.append(create(params))\n\n        if len(plist) == 1:\n            return plist[0]\n        else:\n            return preprocess.preprocess.PreprocessorList(plist)\n\n    def apply(self):\n        # Sync the model into storedsettings on every apply.\n        self.storeSpecificSettings()\n        preprocessor = self.buildpreproc()\n\n        if self.data is not None:\n            self.error()\n            try:\n                data = preprocessor(self.data)\n            except (ValueError, ZeroDivisionError) as e:\n                self.error(str(e))\n                return\n            self.info.set_output_summary(len(data))\n        else:\n            data = None\n            self.info.set_output_summary(self.info.NoOutput)\n\n        self.Outputs.preprocessor.send(preprocessor)\n        self.Outputs.preprocessed_data.send(data)\n\n    def commit(self):\n        if not self._invalidated:\n            self._invalidated = True\n            QApplication.postEvent(self, QEvent(QEvent.User))\n\n    def customEvent(self, event):\n        if event.type() == QEvent.User and self._invalidated:\n            self._invalidated = False\n            self.apply()\n\n    def eventFilter(self, receiver, event):\n        if receiver is self.flow_view and event.type() == QEvent.LayoutRequest:\n            QTimer.singleShot(0, self.__update_size_constraint)\n\n        return super().eventFilter(receiver, event)\n\n    def storeSpecificSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().storeSpecificSettings()\n\n    def saveSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().saveSettings()\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version < 2:\n            for action, params in settings[\"storedsettings\"][\"preprocessors\"]:\n                if action == \"orange.preprocess.scale\":\n                    scale = center = None\n                    if \"center\" in params:\n                        center = params.pop(\"center\").name\n                    if \"scale\" in params:\n                        scale = params.pop(\"scale\").name\n                    migratable = {\n                        (\"Mean\", \"NoScaling\"): Scale.CenterByMean,\n                        (\"NoCentering\", \"Std\"): Scale.ScaleBySD,\n                        (\"Mean\", \"Std\"): Scale.NormalizeBySD,\n                        (\"NoCentering\", \"Span\"): Scale.NormalizeBySpan_ZeroBased\n                    }\n                    params[\"method\"] = \\\n                        migratable.get((center, scale), Scale.NormalizeBySD)\n\n    def onDeleteWidget(self):\n        self.data = None\n        self.set_model(None)\n        super().onDeleteWidget()\n\n    @Slot()\n    def __update_size_constraint(self):\n        # Update minimum width constraint on the scroll area containing\n        # the 'instantiated' preprocessor list (to avoid the horizontal\n        # scroll bar).\n        sh = self.flow_view.minimumSizeHint()\n        scroll_width = self.scroll_area.verticalScrollBar().width()\n        self.scroll_area.setMinimumWidth(\n            min(max(sh.width() + scroll_width + 2, self.controlArea.width()),\n                520))\n\n    def sizeHint(self):\n        sh = super().sizeHint()\n        return sh.expandedTo(QSize(sh.width() + 300, 500))\n\n    def send_report(self):\n        pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w)\n              for i, w in enumerate(self.controler.view.widgets())]\n        if pp:\n            self.report_items(\"Settings\", pp)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 1065,
                                "end_lineno": 1391,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 1261,
                            "length": 6
                        },
                        "new_range": {
                            "start": 1264,
                            "length": 10
                        },
                        "section": "class OWPreprocess(widget.OWWidget):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    def set_data(self, data=None):",
                                "type": "context"
                            },
                            {
                                "content": "        \"\"\"Set the input dataset.\"\"\"",
                                "type": "context"
                            },
                            {
                                "content": "        self.data = data",
                                "type": "context"
                            },
                            {
                                "content": "        if data is not None:",
                                "type": "added"
                            },
                            {
                                "content": "            self.info.set_input_summary(len(data))",
                                "type": "added"
                            },
                            {
                                "content": "        else:",
                                "type": "added"
                            },
                            {
                                "content": "            self.info.set_input_summary(self.info.NoInput)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def handleNewSignals(self):",
                                "type": "context"
                            },
                            {
                                "content": "        self.apply()",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owpreprocess.py",
                            "type": "class",
                            "name": "OWPreprocess",
                            "content": "class OWPreprocess(widget.OWWidget):\n    name = \"Preprocess\"\n    description = \"Construct a data preprocessing pipeline.\"\n    icon = \"icons/Preprocess.svg\"\n    priority = 2105\n    keywords = [\"process\"]\n\n    settings_version = 2\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        preprocessor = Output(\"Preprocessor\", preprocess.preprocess.Preprocess, dynamic=False)\n        preprocessed_data = Output(\"Preprocessed Data\", Orange.data.Table)\n\n    storedsettings = Setting({})\n    autocommit = Setting(True)\n    PREPROCESSORS = PREPROCESS_ACTIONS\n    CONTROLLER = Controller\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self._invalidated = False\n\n        # List of available preprocessors (DescriptionRole : Description)\n        self.preprocessors = QStandardItemModel()\n\n        def mimeData(indexlist):\n            assert len(indexlist) == 1\n            index = indexlist[0]\n            qname = index.data(DescriptionRole).qualname\n            m = QMimeData()\n            m.setData(\"application/x-qwidget-ref\", qname.encode(\"utf-8\"))\n            return m\n        # TODO: Fix this (subclass even if just to pass a function\n        # for mimeData delegate)\n        self.preprocessors.mimeData = mimeData\n\n        box = gui.vBox(self.controlArea, \"Preprocessors\")\n\n        self.preprocessorsView = view = QListView(\n            selectionMode=QListView.SingleSelection,\n            dragEnabled=True,\n            dragDropMode=QListView.DragOnly\n        )\n        view.setModel(self.preprocessors)\n        view.activated.connect(self.__activated)\n\n        box.layout().addWidget(view)\n\n        ####\n        self._qname2ppdef = {ppdef.qualname: ppdef for\n                             ppdef in self.PREPROCESSORS}\n\n        # List of 'selected' preprocessors and their parameters.\n        self.preprocessormodel = None\n\n        self.flow_view = SequenceFlow()\n        self.controler = self.CONTROLLER(self.flow_view, parent=self)\n\n        self.overlay = OverlayWidget(self)\n        self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents)\n        self.overlay.setWidget(self.flow_view)\n        self.overlay.setLayout(QVBoxLayout())\n        self.overlay.layout().addWidget(\n            QLabel(\"Drag items from the list on the left\", wordWrap=True))\n\n        self.scroll_area = QScrollArea(\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn\n        )\n        self.scroll_area.viewport().setAcceptDrops(True)\n        self.scroll_area.setWidget(self.flow_view)\n        self.scroll_area.setWidgetResizable(True)\n        self.mainArea.layout().addWidget(self.scroll_area)\n        self.flow_view.installEventFilter(self)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.auto_send(box, self, \"autocommit\", box=False)\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        self._initialize()\n\n    def _initialize(self):\n        for pp_def in self.PREPROCESSORS:\n            description = pp_def.description\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item = QStandardItem(icon, description.title)\n            item.setToolTip(description.summary or \"\")\n            item.setData(pp_def, DescriptionRole)\n            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable |\n                          Qt.ItemIsDragEnabled)\n            self.preprocessors.appendRow([item])\n\n        model = self.load(self.storedsettings)\n\n        self.set_model(model)\n\n        if not model.rowCount():\n            # enforce default width constraint if no preprocessors\n            # are instantiated (if the model is not empty the constraints\n            # will be triggered by LayoutRequest event on the `flow_view`)\n            self.__update_size_constraint()\n\n        self.apply()\n\n    def load(self, saved):\n        \"\"\"Load a preprocessor list from a dict.\"\"\"\n        preprocessors = saved.get(\"preprocessors\", [])\n        model = StandardItemModel()\n\n        def dropMimeData(data, action, row, _column, _parent):\n            if data.hasFormat(\"application/x-qwidget-ref\") and \\\n                    action == Qt.CopyAction:\n                qname = bytes(data.data(\"application/x-qwidget-ref\")).decode()\n\n                ppdef = self._qname2ppdef[qname]\n                item = QStandardItem(ppdef.description.title)\n                item.setData({}, ParametersRole)\n                item.setData(ppdef.description.title, Qt.DisplayRole)\n                item.setData(ppdef, DescriptionRole)\n                self.preprocessormodel.insertRow(row, [item])\n                return True\n            else:\n                return False\n\n        model.dropMimeData = dropMimeData\n\n        for qualname, params in preprocessors:\n            pp_def = self._qname2ppdef[qualname]\n            description = pp_def.description\n            item = QStandardItem(description.title)\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item.setIcon(icon)\n            item.setToolTip(description.summary)\n            item.setData(pp_def, DescriptionRole)\n            item.setData(params, ParametersRole)\n\n            model.appendRow(item)\n        return model\n\n    @staticmethod\n    def save(model):\n        \"\"\"Save the preprocessor list to a dict.\"\"\"\n        d = {\"name\": \"\"}\n        preprocessors = []\n        for i in range(model.rowCount()):\n            item = model.item(i)\n            pp_def = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n            preprocessors.append((pp_def.qualname, params))\n\n        d[\"preprocessors\"] = preprocessors\n        return d\n\n\n    def set_model(self, ppmodel):\n        if self.preprocessormodel:\n            self.preprocessormodel.dataChanged.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.deleteLater()\n\n        self.preprocessormodel = ppmodel\n        self.controler.setModel(ppmodel)\n        if ppmodel is not None:\n            self.preprocessormodel.dataChanged.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged)\n\n        self.__update_overlay()\n\n    def __update_overlay(self):\n        if self.preprocessormodel is None or \\\n                self.preprocessormodel.rowCount() == 0:\n            self.overlay.setWidget(self.flow_view)\n            self.overlay.show()\n        else:\n            self.overlay.setWidget(None)\n            self.overlay.hide()\n\n    def __on_modelchanged(self):\n        self.__update_overlay()\n        self.commit()\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.data = data\n        if data is not None:\n            self.info.set_input_summary(len(data))\n        else:\n            self.info.set_input_summary(self.info.NoInput)\n\n    def handleNewSignals(self):\n        self.apply()\n\n    def __activated(self, index):\n        item = self.preprocessors.itemFromIndex(index)\n        action = item.data(DescriptionRole)\n        item = QStandardItem()\n        item.setData({}, ParametersRole)\n        item.setData(action.description.title, Qt.DisplayRole)\n        item.setData(action, DescriptionRole)\n        self.preprocessormodel.appendRow([item])\n\n    def buildpreproc(self):\n        plist = []\n        for i in range(self.preprocessormodel.rowCount()):\n            item = self.preprocessormodel.item(i)\n            desc = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n\n            if not isinstance(params, dict):\n                params = {}\n\n            create = desc.viewclass.createinstance\n            plist.append(create(params))\n\n        if len(plist) == 1:\n            return plist[0]\n        else:\n            return preprocess.preprocess.PreprocessorList(plist)\n\n    def apply(self):\n        # Sync the model into storedsettings on every apply.\n        self.storeSpecificSettings()\n        preprocessor = self.buildpreproc()\n\n        if self.data is not None:\n            self.error()\n            try:\n                data = preprocessor(self.data)\n            except (ValueError, ZeroDivisionError) as e:\n                self.error(str(e))\n                return\n            self.info.set_output_summary(len(data))\n        else:\n            data = None\n            self.info.set_output_summary(self.info.NoOutput)\n\n        self.Outputs.preprocessor.send(preprocessor)\n        self.Outputs.preprocessed_data.send(data)\n\n    def commit(self):\n        if not self._invalidated:\n            self._invalidated = True\n            QApplication.postEvent(self, QEvent(QEvent.User))\n\n    def customEvent(self, event):\n        if event.type() == QEvent.User and self._invalidated:\n            self._invalidated = False\n            self.apply()\n\n    def eventFilter(self, receiver, event):\n        if receiver is self.flow_view and event.type() == QEvent.LayoutRequest:\n            QTimer.singleShot(0, self.__update_size_constraint)\n\n        return super().eventFilter(receiver, event)\n\n    def storeSpecificSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().storeSpecificSettings()\n\n    def saveSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().saveSettings()\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version < 2:\n            for action, params in settings[\"storedsettings\"][\"preprocessors\"]:\n                if action == \"orange.preprocess.scale\":\n                    scale = center = None\n                    if \"center\" in params:\n                        center = params.pop(\"center\").name\n                    if \"scale\" in params:\n                        scale = params.pop(\"scale\").name\n                    migratable = {\n                        (\"Mean\", \"NoScaling\"): Scale.CenterByMean,\n                        (\"NoCentering\", \"Std\"): Scale.ScaleBySD,\n                        (\"Mean\", \"Std\"): Scale.NormalizeBySD,\n                        (\"NoCentering\", \"Span\"): Scale.NormalizeBySpan_ZeroBased\n                    }\n                    params[\"method\"] = \\\n                        migratable.get((center, scale), Scale.NormalizeBySD)\n\n    def onDeleteWidget(self):\n        self.data = None\n        self.set_model(None)\n        super().onDeleteWidget()\n\n    @Slot()\n    def __update_size_constraint(self):\n        # Update minimum width constraint on the scroll area containing\n        # the 'instantiated' preprocessor list (to avoid the horizontal\n        # scroll bar).\n        sh = self.flow_view.minimumSizeHint()\n        scroll_width = self.scroll_area.verticalScrollBar().width()\n        self.scroll_area.setMinimumWidth(\n            min(max(sh.width() + scroll_width + 2, self.controlArea.width()),\n                520))\n\n    def sizeHint(self):\n        sh = super().sizeHint()\n        return sh.expandedTo(QSize(sh.width() + 300, 500))\n\n    def send_report(self):\n        pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w)\n              for i, w in enumerate(self.controler.view.widgets())]\n        if pp:\n            self.report_items(\"Settings\", pp)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 1065,
                            "end_lineno": 1391,
                            "parent": null
                        },
                        {
                            "file_name": "Orange/widgets/data/owpreprocess.py",
                            "type": "method",
                            "name": "OWPreprocess.set_data",
                            "content": "class OWPreprocess(widget.OWWidget):\n    name = \"Preprocess\"\n    description = \"Construct a data preprocessing pipeline.\"\n    icon = \"icons/Preprocess.svg\"\n    priority = 2105\n    keywords = [\"process\"]\n\n    settings_version = 2\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        preprocessor = Output(\"Preprocessor\", preprocess.preprocess.Preprocess, dynamic=False)\n        preprocessed_data = Output(\"Preprocessed Data\", Orange.data.Table)\n\n    storedsettings = Setting({})\n    autocommit = Setting(True)\n    PREPROCESSORS = PREPROCESS_ACTIONS\n    CONTROLLER = Controller\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self._invalidated = False\n\n        # List of available preprocessors (DescriptionRole : Description)\n        self.preprocessors = QStandardItemModel()\n\n        def mimeData(indexlist):\n            assert len(indexlist) == 1\n            index = indexlist[0]\n            qname = index.data(DescriptionRole).qualname\n            m = QMimeData()\n            m.setData(\"application/x-qwidget-ref\", qname.encode(\"utf-8\"))\n            return m\n        # TODO: Fix this (subclass even if just to pass a function\n        # for mimeData delegate)\n        self.preprocessors.mimeData = mimeData\n\n        box = gui.vBox(self.controlArea, \"Preprocessors\")\n\n        self.preprocessorsView = view = QListView(\n            selectionMode=QListView.SingleSelection,\n            dragEnabled=True,\n            dragDropMode=QListView.DragOnly\n        )\n        view.setModel(self.preprocessors)\n        view.activated.connect(self.__activated)\n\n        box.layout().addWidget(view)\n\n        ####\n        self._qname2ppdef = {ppdef.qualname: ppdef for\n                             ppdef in self.PREPROCESSORS}\n\n        # List of 'selected' preprocessors and their parameters.\n        self.preprocessormodel = None\n\n        self.flow_view = SequenceFlow()\n        self.controler = self.CONTROLLER(self.flow_view, parent=self)\n\n        self.overlay = OverlayWidget(self)\n        self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents)\n        self.overlay.setWidget(self.flow_view)\n        self.overlay.setLayout(QVBoxLayout())\n        self.overlay.layout().addWidget(\n            QLabel(\"Drag items from the list on the left\", wordWrap=True))\n\n        self.scroll_area = QScrollArea(\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn\n        )\n        self.scroll_area.viewport().setAcceptDrops(True)\n        self.scroll_area.setWidget(self.flow_view)\n        self.scroll_area.setWidgetResizable(True)\n        self.mainArea.layout().addWidget(self.scroll_area)\n        self.flow_view.installEventFilter(self)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.auto_send(box, self, \"autocommit\", box=False)\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        self._initialize()\n\n    def _initialize(self):\n        for pp_def in self.PREPROCESSORS:\n            description = pp_def.description\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item = QStandardItem(icon, description.title)\n            item.setToolTip(description.summary or \"\")\n            item.setData(pp_def, DescriptionRole)\n            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable |\n                          Qt.ItemIsDragEnabled)\n            self.preprocessors.appendRow([item])\n\n        model = self.load(self.storedsettings)\n\n        self.set_model(model)\n\n        if not model.rowCount():\n            # enforce default width constraint if no preprocessors\n            # are instantiated (if the model is not empty the constraints\n            # will be triggered by LayoutRequest event on the `flow_view`)\n            self.__update_size_constraint()\n\n        self.apply()\n\n    def load(self, saved):\n        \"\"\"Load a preprocessor list from a dict.\"\"\"\n        preprocessors = saved.get(\"preprocessors\", [])\n        model = StandardItemModel()\n\n        def dropMimeData(data, action, row, _column, _parent):\n            if data.hasFormat(\"application/x-qwidget-ref\") and \\\n                    action == Qt.CopyAction:\n                qname = bytes(data.data(\"application/x-qwidget-ref\")).decode()\n\n                ppdef = self._qname2ppdef[qname]\n                item = QStandardItem(ppdef.description.title)\n                item.setData({}, ParametersRole)\n                item.setData(ppdef.description.title, Qt.DisplayRole)\n                item.setData(ppdef, DescriptionRole)\n                self.preprocessormodel.insertRow(row, [item])\n                return True\n            else:\n                return False\n\n        model.dropMimeData = dropMimeData\n\n        for qualname, params in preprocessors:\n            pp_def = self._qname2ppdef[qualname]\n            description = pp_def.description\n            item = QStandardItem(description.title)\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item.setIcon(icon)\n            item.setToolTip(description.summary)\n            item.setData(pp_def, DescriptionRole)\n            item.setData(params, ParametersRole)\n\n            model.appendRow(item)\n        return model\n\n    @staticmethod\n    def save(model):\n        \"\"\"Save the preprocessor list to a dict.\"\"\"\n        d = {\"name\": \"\"}\n        preprocessors = []\n        for i in range(model.rowCount()):\n            item = model.item(i)\n            pp_def = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n            preprocessors.append((pp_def.qualname, params))\n\n        d[\"preprocessors\"] = preprocessors\n        return d\n\n\n    def set_model(self, ppmodel):\n        if self.preprocessormodel:\n            self.preprocessormodel.dataChanged.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.deleteLater()\n\n        self.preprocessormodel = ppmodel\n        self.controler.setModel(ppmodel)\n        if ppmodel is not None:\n            self.preprocessormodel.dataChanged.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged)\n\n        self.__update_overlay()\n\n    def __update_overlay(self):\n        if self.preprocessormodel is None or \\\n                self.preprocessormodel.rowCount() == 0:\n            self.overlay.setWidget(self.flow_view)\n            self.overlay.show()\n        else:\n            self.overlay.setWidget(None)\n            self.overlay.hide()\n\n    def __on_modelchanged(self):\n        self.__update_overlay()\n        self.commit()\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.data = data\n        if data is not None:\n            self.info.set_input_summary(len(data))\n        else:\n            self.info.set_input_summary(self.info.NoInput)\n\n    def handleNewSignals(self):\n        self.apply()\n\n    def __activated(self, index):\n        item = self.preprocessors.itemFromIndex(index)\n        action = item.data(DescriptionRole)\n        item = QStandardItem()\n        item.setData({}, ParametersRole)\n        item.setData(action.description.title, Qt.DisplayRole)\n        item.setData(action, DescriptionRole)\n        self.preprocessormodel.appendRow([item])\n\n    def buildpreproc(self):\n        plist = []\n        for i in range(self.preprocessormodel.rowCount()):\n            item = self.preprocessormodel.item(i)\n            desc = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n\n            if not isinstance(params, dict):\n                params = {}\n\n            create = desc.viewclass.createinstance\n            plist.append(create(params))\n\n        if len(plist) == 1:\n            return plist[0]\n        else:\n            return preprocess.preprocess.PreprocessorList(plist)\n\n    def apply(self):\n        # Sync the model into storedsettings on every apply.\n        self.storeSpecificSettings()\n        preprocessor = self.buildpreproc()\n\n        if self.data is not None:\n            self.error()\n            try:\n                data = preprocessor(self.data)\n            except (ValueError, ZeroDivisionError) as e:\n                self.error(str(e))\n                return\n            self.info.set_output_summary(len(data))\n        else:\n            data = None\n            self.info.set_output_summary(self.info.NoOutput)\n\n        self.Outputs.preprocessor.send(preprocessor)\n        self.Outputs.preprocessed_data.send(data)\n\n    def commit(self):\n        if not self._invalidated:\n            self._invalidated = True\n            QApplication.postEvent(self, QEvent(QEvent.User))\n\n    def customEvent(self, event):\n        if event.type() == QEvent.User and self._invalidated:\n            self._invalidated = False\n            self.apply()\n\n    def eventFilter(self, receiver, event):\n        if receiver is self.flow_view and event.type() == QEvent.LayoutRequest:\n            QTimer.singleShot(0, self.__update_size_constraint)\n\n        return super().eventFilter(receiver, event)\n\n    def storeSpecificSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().storeSpecificSettings()\n\n    def saveSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().saveSettings()\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version < 2:\n            for action, params in settings[\"storedsettings\"][\"preprocessors\"]:\n                if action == \"orange.preprocess.scale\":\n                    scale = center = None\n                    if \"center\" in params:\n                        center = params.pop(\"center\").name\n                    if \"scale\" in params:\n                        scale = params.pop(\"scale\").name\n                    migratable = {\n                        (\"Mean\", \"NoScaling\"): Scale.CenterByMean,\n                        (\"NoCentering\", \"Std\"): Scale.ScaleBySD,\n                        (\"Mean\", \"Std\"): Scale.NormalizeBySD,\n                        (\"NoCentering\", \"Span\"): Scale.NormalizeBySpan_ZeroBased\n                    }\n                    params[\"method\"] = \\\n                        migratable.get((center, scale), Scale.NormalizeBySD)\n\n    def onDeleteWidget(self):\n        self.data = None\n        self.set_model(None)\n        super().onDeleteWidget()\n\n    @Slot()\n    def __update_size_constraint(self):\n        # Update minimum width constraint on the scroll area containing\n        # the 'instantiated' preprocessor list (to avoid the horizontal\n        # scroll bar).\n        sh = self.flow_view.minimumSizeHint()\n        scroll_width = self.scroll_area.verticalScrollBar().width()\n        self.scroll_area.setMinimumWidth(\n            min(max(sh.width() + scroll_width + 2, self.controlArea.width()),\n                520))\n\n    def sizeHint(self):\n        sh = super().sizeHint()\n        return sh.expandedTo(QSize(sh.width() + 300, 500))\n\n    def send_report(self):\n        pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w)\n              for i, w in enumerate(self.controler.view.widgets())]\n        if pp:\n            self.report_items(\"Settings\", pp)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 1264,
                            "end_lineno": 1270,
                            "parent": {
                                "file_name": "Orange/widgets/data/owpreprocess.py",
                                "type": "class",
                                "name": "OWPreprocess",
                                "content": "class OWPreprocess(widget.OWWidget):\n    name = \"Preprocess\"\n    description = \"Construct a data preprocessing pipeline.\"\n    icon = \"icons/Preprocess.svg\"\n    priority = 2105\n    keywords = [\"process\"]\n\n    settings_version = 2\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        preprocessor = Output(\"Preprocessor\", preprocess.preprocess.Preprocess, dynamic=False)\n        preprocessed_data = Output(\"Preprocessed Data\", Orange.data.Table)\n\n    storedsettings = Setting({})\n    autocommit = Setting(True)\n    PREPROCESSORS = PREPROCESS_ACTIONS\n    CONTROLLER = Controller\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self._invalidated = False\n\n        # List of available preprocessors (DescriptionRole : Description)\n        self.preprocessors = QStandardItemModel()\n\n        def mimeData(indexlist):\n            assert len(indexlist) == 1\n            index = indexlist[0]\n            qname = index.data(DescriptionRole).qualname\n            m = QMimeData()\n            m.setData(\"application/x-qwidget-ref\", qname.encode(\"utf-8\"))\n            return m\n        # TODO: Fix this (subclass even if just to pass a function\n        # for mimeData delegate)\n        self.preprocessors.mimeData = mimeData\n\n        box = gui.vBox(self.controlArea, \"Preprocessors\")\n\n        self.preprocessorsView = view = QListView(\n            selectionMode=QListView.SingleSelection,\n            dragEnabled=True,\n            dragDropMode=QListView.DragOnly\n        )\n        view.setModel(self.preprocessors)\n        view.activated.connect(self.__activated)\n\n        box.layout().addWidget(view)\n\n        ####\n        self._qname2ppdef = {ppdef.qualname: ppdef for\n                             ppdef in self.PREPROCESSORS}\n\n        # List of 'selected' preprocessors and their parameters.\n        self.preprocessormodel = None\n\n        self.flow_view = SequenceFlow()\n        self.controler = self.CONTROLLER(self.flow_view, parent=self)\n\n        self.overlay = OverlayWidget(self)\n        self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents)\n        self.overlay.setWidget(self.flow_view)\n        self.overlay.setLayout(QVBoxLayout())\n        self.overlay.layout().addWidget(\n            QLabel(\"Drag items from the list on the left\", wordWrap=True))\n\n        self.scroll_area = QScrollArea(\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn\n        )\n        self.scroll_area.viewport().setAcceptDrops(True)\n        self.scroll_area.setWidget(self.flow_view)\n        self.scroll_area.setWidgetResizable(True)\n        self.mainArea.layout().addWidget(self.scroll_area)\n        self.flow_view.installEventFilter(self)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.auto_send(box, self, \"autocommit\", box=False)\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        self._initialize()\n\n    def _initialize(self):\n        for pp_def in self.PREPROCESSORS:\n            description = pp_def.description\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item = QStandardItem(icon, description.title)\n            item.setToolTip(description.summary or \"\")\n            item.setData(pp_def, DescriptionRole)\n            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable |\n                          Qt.ItemIsDragEnabled)\n            self.preprocessors.appendRow([item])\n\n        model = self.load(self.storedsettings)\n\n        self.set_model(model)\n\n        if not model.rowCount():\n            # enforce default width constraint if no preprocessors\n            # are instantiated (if the model is not empty the constraints\n            # will be triggered by LayoutRequest event on the `flow_view`)\n            self.__update_size_constraint()\n\n        self.apply()\n\n    def load(self, saved):\n        \"\"\"Load a preprocessor list from a dict.\"\"\"\n        preprocessors = saved.get(\"preprocessors\", [])\n        model = StandardItemModel()\n\n        def dropMimeData(data, action, row, _column, _parent):\n            if data.hasFormat(\"application/x-qwidget-ref\") and \\\n                    action == Qt.CopyAction:\n                qname = bytes(data.data(\"application/x-qwidget-ref\")).decode()\n\n                ppdef = self._qname2ppdef[qname]\n                item = QStandardItem(ppdef.description.title)\n                item.setData({}, ParametersRole)\n                item.setData(ppdef.description.title, Qt.DisplayRole)\n                item.setData(ppdef, DescriptionRole)\n                self.preprocessormodel.insertRow(row, [item])\n                return True\n            else:\n                return False\n\n        model.dropMimeData = dropMimeData\n\n        for qualname, params in preprocessors:\n            pp_def = self._qname2ppdef[qualname]\n            description = pp_def.description\n            item = QStandardItem(description.title)\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item.setIcon(icon)\n            item.setToolTip(description.summary)\n            item.setData(pp_def, DescriptionRole)\n            item.setData(params, ParametersRole)\n\n            model.appendRow(item)\n        return model\n\n    @staticmethod\n    def save(model):\n        \"\"\"Save the preprocessor list to a dict.\"\"\"\n        d = {\"name\": \"\"}\n        preprocessors = []\n        for i in range(model.rowCount()):\n            item = model.item(i)\n            pp_def = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n            preprocessors.append((pp_def.qualname, params))\n\n        d[\"preprocessors\"] = preprocessors\n        return d\n\n\n    def set_model(self, ppmodel):\n        if self.preprocessormodel:\n            self.preprocessormodel.dataChanged.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.deleteLater()\n\n        self.preprocessormodel = ppmodel\n        self.controler.setModel(ppmodel)\n        if ppmodel is not None:\n            self.preprocessormodel.dataChanged.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged)\n\n        self.__update_overlay()\n\n    def __update_overlay(self):\n        if self.preprocessormodel is None or \\\n                self.preprocessormodel.rowCount() == 0:\n            self.overlay.setWidget(self.flow_view)\n            self.overlay.show()\n        else:\n            self.overlay.setWidget(None)\n            self.overlay.hide()\n\n    def __on_modelchanged(self):\n        self.__update_overlay()\n        self.commit()\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.data = data\n        if data is not None:\n            self.info.set_input_summary(len(data))\n        else:\n            self.info.set_input_summary(self.info.NoInput)\n\n    def handleNewSignals(self):\n        self.apply()\n\n    def __activated(self, index):\n        item = self.preprocessors.itemFromIndex(index)\n        action = item.data(DescriptionRole)\n        item = QStandardItem()\n        item.setData({}, ParametersRole)\n        item.setData(action.description.title, Qt.DisplayRole)\n        item.setData(action, DescriptionRole)\n        self.preprocessormodel.appendRow([item])\n\n    def buildpreproc(self):\n        plist = []\n        for i in range(self.preprocessormodel.rowCount()):\n            item = self.preprocessormodel.item(i)\n            desc = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n\n            if not isinstance(params, dict):\n                params = {}\n\n            create = desc.viewclass.createinstance\n            plist.append(create(params))\n\n        if len(plist) == 1:\n            return plist[0]\n        else:\n            return preprocess.preprocess.PreprocessorList(plist)\n\n    def apply(self):\n        # Sync the model into storedsettings on every apply.\n        self.storeSpecificSettings()\n        preprocessor = self.buildpreproc()\n\n        if self.data is not None:\n            self.error()\n            try:\n                data = preprocessor(self.data)\n            except (ValueError, ZeroDivisionError) as e:\n                self.error(str(e))\n                return\n            self.info.set_output_summary(len(data))\n        else:\n            data = None\n            self.info.set_output_summary(self.info.NoOutput)\n\n        self.Outputs.preprocessor.send(preprocessor)\n        self.Outputs.preprocessed_data.send(data)\n\n    def commit(self):\n        if not self._invalidated:\n            self._invalidated = True\n            QApplication.postEvent(self, QEvent(QEvent.User))\n\n    def customEvent(self, event):\n        if event.type() == QEvent.User and self._invalidated:\n            self._invalidated = False\n            self.apply()\n\n    def eventFilter(self, receiver, event):\n        if receiver is self.flow_view and event.type() == QEvent.LayoutRequest:\n            QTimer.singleShot(0, self.__update_size_constraint)\n\n        return super().eventFilter(receiver, event)\n\n    def storeSpecificSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().storeSpecificSettings()\n\n    def saveSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().saveSettings()\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version < 2:\n            for action, params in settings[\"storedsettings\"][\"preprocessors\"]:\n                if action == \"orange.preprocess.scale\":\n                    scale = center = None\n                    if \"center\" in params:\n                        center = params.pop(\"center\").name\n                    if \"scale\" in params:\n                        scale = params.pop(\"scale\").name\n                    migratable = {\n                        (\"Mean\", \"NoScaling\"): Scale.CenterByMean,\n                        (\"NoCentering\", \"Std\"): Scale.ScaleBySD,\n                        (\"Mean\", \"Std\"): Scale.NormalizeBySD,\n                        (\"NoCentering\", \"Span\"): Scale.NormalizeBySpan_ZeroBased\n                    }\n                    params[\"method\"] = \\\n                        migratable.get((center, scale), Scale.NormalizeBySD)\n\n    def onDeleteWidget(self):\n        self.data = None\n        self.set_model(None)\n        super().onDeleteWidget()\n\n    @Slot()\n    def __update_size_constraint(self):\n        # Update minimum width constraint on the scroll area containing\n        # the 'instantiated' preprocessor list (to avoid the horizontal\n        # scroll bar).\n        sh = self.flow_view.minimumSizeHint()\n        scroll_width = self.scroll_area.verticalScrollBar().width()\n        self.scroll_area.setMinimumWidth(\n            min(max(sh.width() + scroll_width + 2, self.controlArea.width()),\n                520))\n\n    def sizeHint(self):\n        sh = super().sizeHint()\n        return sh.expandedTo(QSize(sh.width() + 300, 500))\n\n    def send_report(self):\n        pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w)\n              for i, w in enumerate(self.controler.view.widgets())]\n        if pp:\n            self.report_items(\"Settings\", pp)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 1065,
                                "end_lineno": 1391,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 1304,
                            "length": 8
                        },
                        "new_range": {
                            "start": 1311,
                            "length": 10
                        },
                        "section": "class OWPreprocess(widget.OWWidget):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "            except (ValueError, ZeroDivisionError) as e:",
                                "type": "context"
                            },
                            {
                                "content": "                self.error(str(e))",
                                "type": "context"
                            },
                            {
                                "content": "                return",
                                "type": "context"
                            },
                            {
                                "content": "            self.info.set_output_summary(len(data))",
                                "type": "added"
                            },
                            {
                                "content": "        else:",
                                "type": "context"
                            },
                            {
                                "content": "            data = None",
                                "type": "context"
                            },
                            {
                                "content": "            self.info.set_output_summary(self.info.NoOutput)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        self.Outputs.preprocessor.send(preprocessor)",
                                "type": "context"
                            },
                            {
                                "content": "        self.Outputs.preprocessed_data.send(data)",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owpreprocess.py",
                            "type": "class",
                            "name": "OWPreprocess",
                            "content": "class OWPreprocess(widget.OWWidget):\n    name = \"Preprocess\"\n    description = \"Construct a data preprocessing pipeline.\"\n    icon = \"icons/Preprocess.svg\"\n    priority = 2105\n    keywords = [\"process\"]\n\n    settings_version = 2\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        preprocessor = Output(\"Preprocessor\", preprocess.preprocess.Preprocess, dynamic=False)\n        preprocessed_data = Output(\"Preprocessed Data\", Orange.data.Table)\n\n    storedsettings = Setting({})\n    autocommit = Setting(True)\n    PREPROCESSORS = PREPROCESS_ACTIONS\n    CONTROLLER = Controller\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self._invalidated = False\n\n        # List of available preprocessors (DescriptionRole : Description)\n        self.preprocessors = QStandardItemModel()\n\n        def mimeData(indexlist):\n            assert len(indexlist) == 1\n            index = indexlist[0]\n            qname = index.data(DescriptionRole).qualname\n            m = QMimeData()\n            m.setData(\"application/x-qwidget-ref\", qname.encode(\"utf-8\"))\n            return m\n        # TODO: Fix this (subclass even if just to pass a function\n        # for mimeData delegate)\n        self.preprocessors.mimeData = mimeData\n\n        box = gui.vBox(self.controlArea, \"Preprocessors\")\n\n        self.preprocessorsView = view = QListView(\n            selectionMode=QListView.SingleSelection,\n            dragEnabled=True,\n            dragDropMode=QListView.DragOnly\n        )\n        view.setModel(self.preprocessors)\n        view.activated.connect(self.__activated)\n\n        box.layout().addWidget(view)\n\n        ####\n        self._qname2ppdef = {ppdef.qualname: ppdef for\n                             ppdef in self.PREPROCESSORS}\n\n        # List of 'selected' preprocessors and their parameters.\n        self.preprocessormodel = None\n\n        self.flow_view = SequenceFlow()\n        self.controler = self.CONTROLLER(self.flow_view, parent=self)\n\n        self.overlay = OverlayWidget(self)\n        self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents)\n        self.overlay.setWidget(self.flow_view)\n        self.overlay.setLayout(QVBoxLayout())\n        self.overlay.layout().addWidget(\n            QLabel(\"Drag items from the list on the left\", wordWrap=True))\n\n        self.scroll_area = QScrollArea(\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn\n        )\n        self.scroll_area.viewport().setAcceptDrops(True)\n        self.scroll_area.setWidget(self.flow_view)\n        self.scroll_area.setWidgetResizable(True)\n        self.mainArea.layout().addWidget(self.scroll_area)\n        self.flow_view.installEventFilter(self)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.auto_send(box, self, \"autocommit\", box=False)\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        self._initialize()\n\n    def _initialize(self):\n        for pp_def in self.PREPROCESSORS:\n            description = pp_def.description\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item = QStandardItem(icon, description.title)\n            item.setToolTip(description.summary or \"\")\n            item.setData(pp_def, DescriptionRole)\n            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable |\n                          Qt.ItemIsDragEnabled)\n            self.preprocessors.appendRow([item])\n\n        model = self.load(self.storedsettings)\n\n        self.set_model(model)\n\n        if not model.rowCount():\n            # enforce default width constraint if no preprocessors\n            # are instantiated (if the model is not empty the constraints\n            # will be triggered by LayoutRequest event on the `flow_view`)\n            self.__update_size_constraint()\n\n        self.apply()\n\n    def load(self, saved):\n        \"\"\"Load a preprocessor list from a dict.\"\"\"\n        preprocessors = saved.get(\"preprocessors\", [])\n        model = StandardItemModel()\n\n        def dropMimeData(data, action, row, _column, _parent):\n            if data.hasFormat(\"application/x-qwidget-ref\") and \\\n                    action == Qt.CopyAction:\n                qname = bytes(data.data(\"application/x-qwidget-ref\")).decode()\n\n                ppdef = self._qname2ppdef[qname]\n                item = QStandardItem(ppdef.description.title)\n                item.setData({}, ParametersRole)\n                item.setData(ppdef.description.title, Qt.DisplayRole)\n                item.setData(ppdef, DescriptionRole)\n                self.preprocessormodel.insertRow(row, [item])\n                return True\n            else:\n                return False\n\n        model.dropMimeData = dropMimeData\n\n        for qualname, params in preprocessors:\n            pp_def = self._qname2ppdef[qualname]\n            description = pp_def.description\n            item = QStandardItem(description.title)\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item.setIcon(icon)\n            item.setToolTip(description.summary)\n            item.setData(pp_def, DescriptionRole)\n            item.setData(params, ParametersRole)\n\n            model.appendRow(item)\n        return model\n\n    @staticmethod\n    def save(model):\n        \"\"\"Save the preprocessor list to a dict.\"\"\"\n        d = {\"name\": \"\"}\n        preprocessors = []\n        for i in range(model.rowCount()):\n            item = model.item(i)\n            pp_def = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n            preprocessors.append((pp_def.qualname, params))\n\n        d[\"preprocessors\"] = preprocessors\n        return d\n\n\n    def set_model(self, ppmodel):\n        if self.preprocessormodel:\n            self.preprocessormodel.dataChanged.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.deleteLater()\n\n        self.preprocessormodel = ppmodel\n        self.controler.setModel(ppmodel)\n        if ppmodel is not None:\n            self.preprocessormodel.dataChanged.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged)\n\n        self.__update_overlay()\n\n    def __update_overlay(self):\n        if self.preprocessormodel is None or \\\n                self.preprocessormodel.rowCount() == 0:\n            self.overlay.setWidget(self.flow_view)\n            self.overlay.show()\n        else:\n            self.overlay.setWidget(None)\n            self.overlay.hide()\n\n    def __on_modelchanged(self):\n        self.__update_overlay()\n        self.commit()\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.data = data\n        if data is not None:\n            self.info.set_input_summary(len(data))\n        else:\n            self.info.set_input_summary(self.info.NoInput)\n\n    def handleNewSignals(self):\n        self.apply()\n\n    def __activated(self, index):\n        item = self.preprocessors.itemFromIndex(index)\n        action = item.data(DescriptionRole)\n        item = QStandardItem()\n        item.setData({}, ParametersRole)\n        item.setData(action.description.title, Qt.DisplayRole)\n        item.setData(action, DescriptionRole)\n        self.preprocessormodel.appendRow([item])\n\n    def buildpreproc(self):\n        plist = []\n        for i in range(self.preprocessormodel.rowCount()):\n            item = self.preprocessormodel.item(i)\n            desc = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n\n            if not isinstance(params, dict):\n                params = {}\n\n            create = desc.viewclass.createinstance\n            plist.append(create(params))\n\n        if len(plist) == 1:\n            return plist[0]\n        else:\n            return preprocess.preprocess.PreprocessorList(plist)\n\n    def apply(self):\n        # Sync the model into storedsettings on every apply.\n        self.storeSpecificSettings()\n        preprocessor = self.buildpreproc()\n\n        if self.data is not None:\n            self.error()\n            try:\n                data = preprocessor(self.data)\n            except (ValueError, ZeroDivisionError) as e:\n                self.error(str(e))\n                return\n            self.info.set_output_summary(len(data))\n        else:\n            data = None\n            self.info.set_output_summary(self.info.NoOutput)\n\n        self.Outputs.preprocessor.send(preprocessor)\n        self.Outputs.preprocessed_data.send(data)\n\n    def commit(self):\n        if not self._invalidated:\n            self._invalidated = True\n            QApplication.postEvent(self, QEvent(QEvent.User))\n\n    def customEvent(self, event):\n        if event.type() == QEvent.User and self._invalidated:\n            self._invalidated = False\n            self.apply()\n\n    def eventFilter(self, receiver, event):\n        if receiver is self.flow_view and event.type() == QEvent.LayoutRequest:\n            QTimer.singleShot(0, self.__update_size_constraint)\n\n        return super().eventFilter(receiver, event)\n\n    def storeSpecificSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().storeSpecificSettings()\n\n    def saveSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().saveSettings()\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version < 2:\n            for action, params in settings[\"storedsettings\"][\"preprocessors\"]:\n                if action == \"orange.preprocess.scale\":\n                    scale = center = None\n                    if \"center\" in params:\n                        center = params.pop(\"center\").name\n                    if \"scale\" in params:\n                        scale = params.pop(\"scale\").name\n                    migratable = {\n                        (\"Mean\", \"NoScaling\"): Scale.CenterByMean,\n                        (\"NoCentering\", \"Std\"): Scale.ScaleBySD,\n                        (\"Mean\", \"Std\"): Scale.NormalizeBySD,\n                        (\"NoCentering\", \"Span\"): Scale.NormalizeBySpan_ZeroBased\n                    }\n                    params[\"method\"] = \\\n                        migratable.get((center, scale), Scale.NormalizeBySD)\n\n    def onDeleteWidget(self):\n        self.data = None\n        self.set_model(None)\n        super().onDeleteWidget()\n\n    @Slot()\n    def __update_size_constraint(self):\n        # Update minimum width constraint on the scroll area containing\n        # the 'instantiated' preprocessor list (to avoid the horizontal\n        # scroll bar).\n        sh = self.flow_view.minimumSizeHint()\n        scroll_width = self.scroll_area.verticalScrollBar().width()\n        self.scroll_area.setMinimumWidth(\n            min(max(sh.width() + scroll_width + 2, self.controlArea.width()),\n                520))\n\n    def sizeHint(self):\n        sh = super().sizeHint()\n        return sh.expandedTo(QSize(sh.width() + 300, 500))\n\n    def send_report(self):\n        pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w)\n              for i, w in enumerate(self.controler.view.widgets())]\n        if pp:\n            self.report_items(\"Settings\", pp)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 1065,
                            "end_lineno": 1391,
                            "parent": null
                        },
                        {
                            "file_name": "Orange/widgets/data/owpreprocess.py",
                            "type": "method",
                            "name": "OWPreprocess.apply",
                            "content": "class OWPreprocess(widget.OWWidget):\n    name = \"Preprocess\"\n    description = \"Construct a data preprocessing pipeline.\"\n    icon = \"icons/Preprocess.svg\"\n    priority = 2105\n    keywords = [\"process\"]\n\n    settings_version = 2\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        preprocessor = Output(\"Preprocessor\", preprocess.preprocess.Preprocess, dynamic=False)\n        preprocessed_data = Output(\"Preprocessed Data\", Orange.data.Table)\n\n    storedsettings = Setting({})\n    autocommit = Setting(True)\n    PREPROCESSORS = PREPROCESS_ACTIONS\n    CONTROLLER = Controller\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self._invalidated = False\n\n        # List of available preprocessors (DescriptionRole : Description)\n        self.preprocessors = QStandardItemModel()\n\n        def mimeData(indexlist):\n            assert len(indexlist) == 1\n            index = indexlist[0]\n            qname = index.data(DescriptionRole).qualname\n            m = QMimeData()\n            m.setData(\"application/x-qwidget-ref\", qname.encode(\"utf-8\"))\n            return m\n        # TODO: Fix this (subclass even if just to pass a function\n        # for mimeData delegate)\n        self.preprocessors.mimeData = mimeData\n\n        box = gui.vBox(self.controlArea, \"Preprocessors\")\n\n        self.preprocessorsView = view = QListView(\n            selectionMode=QListView.SingleSelection,\n            dragEnabled=True,\n            dragDropMode=QListView.DragOnly\n        )\n        view.setModel(self.preprocessors)\n        view.activated.connect(self.__activated)\n\n        box.layout().addWidget(view)\n\n        ####\n        self._qname2ppdef = {ppdef.qualname: ppdef for\n                             ppdef in self.PREPROCESSORS}\n\n        # List of 'selected' preprocessors and their parameters.\n        self.preprocessormodel = None\n\n        self.flow_view = SequenceFlow()\n        self.controler = self.CONTROLLER(self.flow_view, parent=self)\n\n        self.overlay = OverlayWidget(self)\n        self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents)\n        self.overlay.setWidget(self.flow_view)\n        self.overlay.setLayout(QVBoxLayout())\n        self.overlay.layout().addWidget(\n            QLabel(\"Drag items from the list on the left\", wordWrap=True))\n\n        self.scroll_area = QScrollArea(\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn\n        )\n        self.scroll_area.viewport().setAcceptDrops(True)\n        self.scroll_area.setWidget(self.flow_view)\n        self.scroll_area.setWidgetResizable(True)\n        self.mainArea.layout().addWidget(self.scroll_area)\n        self.flow_view.installEventFilter(self)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.auto_send(box, self, \"autocommit\", box=False)\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        self._initialize()\n\n    def _initialize(self):\n        for pp_def in self.PREPROCESSORS:\n            description = pp_def.description\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item = QStandardItem(icon, description.title)\n            item.setToolTip(description.summary or \"\")\n            item.setData(pp_def, DescriptionRole)\n            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable |\n                          Qt.ItemIsDragEnabled)\n            self.preprocessors.appendRow([item])\n\n        model = self.load(self.storedsettings)\n\n        self.set_model(model)\n\n        if not model.rowCount():\n            # enforce default width constraint if no preprocessors\n            # are instantiated (if the model is not empty the constraints\n            # will be triggered by LayoutRequest event on the `flow_view`)\n            self.__update_size_constraint()\n\n        self.apply()\n\n    def load(self, saved):\n        \"\"\"Load a preprocessor list from a dict.\"\"\"\n        preprocessors = saved.get(\"preprocessors\", [])\n        model = StandardItemModel()\n\n        def dropMimeData(data, action, row, _column, _parent):\n            if data.hasFormat(\"application/x-qwidget-ref\") and \\\n                    action == Qt.CopyAction:\n                qname = bytes(data.data(\"application/x-qwidget-ref\")).decode()\n\n                ppdef = self._qname2ppdef[qname]\n                item = QStandardItem(ppdef.description.title)\n                item.setData({}, ParametersRole)\n                item.setData(ppdef.description.title, Qt.DisplayRole)\n                item.setData(ppdef, DescriptionRole)\n                self.preprocessormodel.insertRow(row, [item])\n                return True\n            else:\n                return False\n\n        model.dropMimeData = dropMimeData\n\n        for qualname, params in preprocessors:\n            pp_def = self._qname2ppdef[qualname]\n            description = pp_def.description\n            item = QStandardItem(description.title)\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item.setIcon(icon)\n            item.setToolTip(description.summary)\n            item.setData(pp_def, DescriptionRole)\n            item.setData(params, ParametersRole)\n\n            model.appendRow(item)\n        return model\n\n    @staticmethod\n    def save(model):\n        \"\"\"Save the preprocessor list to a dict.\"\"\"\n        d = {\"name\": \"\"}\n        preprocessors = []\n        for i in range(model.rowCount()):\n            item = model.item(i)\n            pp_def = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n            preprocessors.append((pp_def.qualname, params))\n\n        d[\"preprocessors\"] = preprocessors\n        return d\n\n\n    def set_model(self, ppmodel):\n        if self.preprocessormodel:\n            self.preprocessormodel.dataChanged.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.deleteLater()\n\n        self.preprocessormodel = ppmodel\n        self.controler.setModel(ppmodel)\n        if ppmodel is not None:\n            self.preprocessormodel.dataChanged.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged)\n\n        self.__update_overlay()\n\n    def __update_overlay(self):\n        if self.preprocessormodel is None or \\\n                self.preprocessormodel.rowCount() == 0:\n            self.overlay.setWidget(self.flow_view)\n            self.overlay.show()\n        else:\n            self.overlay.setWidget(None)\n            self.overlay.hide()\n\n    def __on_modelchanged(self):\n        self.__update_overlay()\n        self.commit()\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.data = data\n        if data is not None:\n            self.info.set_input_summary(len(data))\n        else:\n            self.info.set_input_summary(self.info.NoInput)\n\n    def handleNewSignals(self):\n        self.apply()\n\n    def __activated(self, index):\n        item = self.preprocessors.itemFromIndex(index)\n        action = item.data(DescriptionRole)\n        item = QStandardItem()\n        item.setData({}, ParametersRole)\n        item.setData(action.description.title, Qt.DisplayRole)\n        item.setData(action, DescriptionRole)\n        self.preprocessormodel.appendRow([item])\n\n    def buildpreproc(self):\n        plist = []\n        for i in range(self.preprocessormodel.rowCount()):\n            item = self.preprocessormodel.item(i)\n            desc = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n\n            if not isinstance(params, dict):\n                params = {}\n\n            create = desc.viewclass.createinstance\n            plist.append(create(params))\n\n        if len(plist) == 1:\n            return plist[0]\n        else:\n            return preprocess.preprocess.PreprocessorList(plist)\n\n    def apply(self):\n        # Sync the model into storedsettings on every apply.\n        self.storeSpecificSettings()\n        preprocessor = self.buildpreproc()\n\n        if self.data is not None:\n            self.error()\n            try:\n                data = preprocessor(self.data)\n            except (ValueError, ZeroDivisionError) as e:\n                self.error(str(e))\n                return\n            self.info.set_output_summary(len(data))\n        else:\n            data = None\n            self.info.set_output_summary(self.info.NoOutput)\n\n        self.Outputs.preprocessor.send(preprocessor)\n        self.Outputs.preprocessed_data.send(data)\n\n    def commit(self):\n        if not self._invalidated:\n            self._invalidated = True\n            QApplication.postEvent(self, QEvent(QEvent.User))\n\n    def customEvent(self, event):\n        if event.type() == QEvent.User and self._invalidated:\n            self._invalidated = False\n            self.apply()\n\n    def eventFilter(self, receiver, event):\n        if receiver is self.flow_view and event.type() == QEvent.LayoutRequest:\n            QTimer.singleShot(0, self.__update_size_constraint)\n\n        return super().eventFilter(receiver, event)\n\n    def storeSpecificSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().storeSpecificSettings()\n\n    def saveSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().saveSettings()\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version < 2:\n            for action, params in settings[\"storedsettings\"][\"preprocessors\"]:\n                if action == \"orange.preprocess.scale\":\n                    scale = center = None\n                    if \"center\" in params:\n                        center = params.pop(\"center\").name\n                    if \"scale\" in params:\n                        scale = params.pop(\"scale\").name\n                    migratable = {\n                        (\"Mean\", \"NoScaling\"): Scale.CenterByMean,\n                        (\"NoCentering\", \"Std\"): Scale.ScaleBySD,\n                        (\"Mean\", \"Std\"): Scale.NormalizeBySD,\n                        (\"NoCentering\", \"Span\"): Scale.NormalizeBySpan_ZeroBased\n                    }\n                    params[\"method\"] = \\\n                        migratable.get((center, scale), Scale.NormalizeBySD)\n\n    def onDeleteWidget(self):\n        self.data = None\n        self.set_model(None)\n        super().onDeleteWidget()\n\n    @Slot()\n    def __update_size_constraint(self):\n        # Update minimum width constraint on the scroll area containing\n        # the 'instantiated' preprocessor list (to avoid the horizontal\n        # scroll bar).\n        sh = self.flow_view.minimumSizeHint()\n        scroll_width = self.scroll_area.verticalScrollBar().width()\n        self.scroll_area.setMinimumWidth(\n            min(max(sh.width() + scroll_width + 2, self.controlArea.width()),\n                520))\n\n    def sizeHint(self):\n        sh = super().sizeHint()\n        return sh.expandedTo(QSize(sh.width() + 300, 500))\n\n    def send_report(self):\n        pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w)\n              for i, w in enumerate(self.controler.view.widgets())]\n        if pp:\n            self.report_items(\"Settings\", pp)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 1302,
                            "end_lineno": 1320,
                            "parent": {
                                "file_name": "Orange/widgets/data/owpreprocess.py",
                                "type": "class",
                                "name": "OWPreprocess",
                                "content": "class OWPreprocess(widget.OWWidget):\n    name = \"Preprocess\"\n    description = \"Construct a data preprocessing pipeline.\"\n    icon = \"icons/Preprocess.svg\"\n    priority = 2105\n    keywords = [\"process\"]\n\n    settings_version = 2\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        preprocessor = Output(\"Preprocessor\", preprocess.preprocess.Preprocess, dynamic=False)\n        preprocessed_data = Output(\"Preprocessed Data\", Orange.data.Table)\n\n    storedsettings = Setting({})\n    autocommit = Setting(True)\n    PREPROCESSORS = PREPROCESS_ACTIONS\n    CONTROLLER = Controller\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self._invalidated = False\n\n        # List of available preprocessors (DescriptionRole : Description)\n        self.preprocessors = QStandardItemModel()\n\n        def mimeData(indexlist):\n            assert len(indexlist) == 1\n            index = indexlist[0]\n            qname = index.data(DescriptionRole).qualname\n            m = QMimeData()\n            m.setData(\"application/x-qwidget-ref\", qname.encode(\"utf-8\"))\n            return m\n        # TODO: Fix this (subclass even if just to pass a function\n        # for mimeData delegate)\n        self.preprocessors.mimeData = mimeData\n\n        box = gui.vBox(self.controlArea, \"Preprocessors\")\n\n        self.preprocessorsView = view = QListView(\n            selectionMode=QListView.SingleSelection,\n            dragEnabled=True,\n            dragDropMode=QListView.DragOnly\n        )\n        view.setModel(self.preprocessors)\n        view.activated.connect(self.__activated)\n\n        box.layout().addWidget(view)\n\n        ####\n        self._qname2ppdef = {ppdef.qualname: ppdef for\n                             ppdef in self.PREPROCESSORS}\n\n        # List of 'selected' preprocessors and their parameters.\n        self.preprocessormodel = None\n\n        self.flow_view = SequenceFlow()\n        self.controler = self.CONTROLLER(self.flow_view, parent=self)\n\n        self.overlay = OverlayWidget(self)\n        self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents)\n        self.overlay.setWidget(self.flow_view)\n        self.overlay.setLayout(QVBoxLayout())\n        self.overlay.layout().addWidget(\n            QLabel(\"Drag items from the list on the left\", wordWrap=True))\n\n        self.scroll_area = QScrollArea(\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn\n        )\n        self.scroll_area.viewport().setAcceptDrops(True)\n        self.scroll_area.setWidget(self.flow_view)\n        self.scroll_area.setWidgetResizable(True)\n        self.mainArea.layout().addWidget(self.scroll_area)\n        self.flow_view.installEventFilter(self)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.auto_send(box, self, \"autocommit\", box=False)\n\n        self.info.set_input_summary(self.info.NoInput)\n        self.info.set_output_summary(self.info.NoOutput)\n\n        self._initialize()\n\n    def _initialize(self):\n        for pp_def in self.PREPROCESSORS:\n            description = pp_def.description\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item = QStandardItem(icon, description.title)\n            item.setToolTip(description.summary or \"\")\n            item.setData(pp_def, DescriptionRole)\n            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable |\n                          Qt.ItemIsDragEnabled)\n            self.preprocessors.appendRow([item])\n\n        model = self.load(self.storedsettings)\n\n        self.set_model(model)\n\n        if not model.rowCount():\n            # enforce default width constraint if no preprocessors\n            # are instantiated (if the model is not empty the constraints\n            # will be triggered by LayoutRequest event on the `flow_view`)\n            self.__update_size_constraint()\n\n        self.apply()\n\n    def load(self, saved):\n        \"\"\"Load a preprocessor list from a dict.\"\"\"\n        preprocessors = saved.get(\"preprocessors\", [])\n        model = StandardItemModel()\n\n        def dropMimeData(data, action, row, _column, _parent):\n            if data.hasFormat(\"application/x-qwidget-ref\") and \\\n                    action == Qt.CopyAction:\n                qname = bytes(data.data(\"application/x-qwidget-ref\")).decode()\n\n                ppdef = self._qname2ppdef[qname]\n                item = QStandardItem(ppdef.description.title)\n                item.setData({}, ParametersRole)\n                item.setData(ppdef.description.title, Qt.DisplayRole)\n                item.setData(ppdef, DescriptionRole)\n                self.preprocessormodel.insertRow(row, [item])\n                return True\n            else:\n                return False\n\n        model.dropMimeData = dropMimeData\n\n        for qualname, params in preprocessors:\n            pp_def = self._qname2ppdef[qualname]\n            description = pp_def.description\n            item = QStandardItem(description.title)\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item.setIcon(icon)\n            item.setToolTip(description.summary)\n            item.setData(pp_def, DescriptionRole)\n            item.setData(params, ParametersRole)\n\n            model.appendRow(item)\n        return model\n\n    @staticmethod\n    def save(model):\n        \"\"\"Save the preprocessor list to a dict.\"\"\"\n        d = {\"name\": \"\"}\n        preprocessors = []\n        for i in range(model.rowCount()):\n            item = model.item(i)\n            pp_def = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n            preprocessors.append((pp_def.qualname, params))\n\n        d[\"preprocessors\"] = preprocessors\n        return d\n\n\n    def set_model(self, ppmodel):\n        if self.preprocessormodel:\n            self.preprocessormodel.dataChanged.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.deleteLater()\n\n        self.preprocessormodel = ppmodel\n        self.controler.setModel(ppmodel)\n        if ppmodel is not None:\n            self.preprocessormodel.dataChanged.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged)\n\n        self.__update_overlay()\n\n    def __update_overlay(self):\n        if self.preprocessormodel is None or \\\n                self.preprocessormodel.rowCount() == 0:\n            self.overlay.setWidget(self.flow_view)\n            self.overlay.show()\n        else:\n            self.overlay.setWidget(None)\n            self.overlay.hide()\n\n    def __on_modelchanged(self):\n        self.__update_overlay()\n        self.commit()\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.data = data\n        if data is not None:\n            self.info.set_input_summary(len(data))\n        else:\n            self.info.set_input_summary(self.info.NoInput)\n\n    def handleNewSignals(self):\n        self.apply()\n\n    def __activated(self, index):\n        item = self.preprocessors.itemFromIndex(index)\n        action = item.data(DescriptionRole)\n        item = QStandardItem()\n        item.setData({}, ParametersRole)\n        item.setData(action.description.title, Qt.DisplayRole)\n        item.setData(action, DescriptionRole)\n        self.preprocessormodel.appendRow([item])\n\n    def buildpreproc(self):\n        plist = []\n        for i in range(self.preprocessormodel.rowCount()):\n            item = self.preprocessormodel.item(i)\n            desc = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n\n            if not isinstance(params, dict):\n                params = {}\n\n            create = desc.viewclass.createinstance\n            plist.append(create(params))\n\n        if len(plist) == 1:\n            return plist[0]\n        else:\n            return preprocess.preprocess.PreprocessorList(plist)\n\n    def apply(self):\n        # Sync the model into storedsettings on every apply.\n        self.storeSpecificSettings()\n        preprocessor = self.buildpreproc()\n\n        if self.data is not None:\n            self.error()\n            try:\n                data = preprocessor(self.data)\n            except (ValueError, ZeroDivisionError) as e:\n                self.error(str(e))\n                return\n            self.info.set_output_summary(len(data))\n        else:\n            data = None\n            self.info.set_output_summary(self.info.NoOutput)\n\n        self.Outputs.preprocessor.send(preprocessor)\n        self.Outputs.preprocessed_data.send(data)\n\n    def commit(self):\n        if not self._invalidated:\n            self._invalidated = True\n            QApplication.postEvent(self, QEvent(QEvent.User))\n\n    def customEvent(self, event):\n        if event.type() == QEvent.User and self._invalidated:\n            self._invalidated = False\n            self.apply()\n\n    def eventFilter(self, receiver, event):\n        if receiver is self.flow_view and event.type() == QEvent.LayoutRequest:\n            QTimer.singleShot(0, self.__update_size_constraint)\n\n        return super().eventFilter(receiver, event)\n\n    def storeSpecificSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().storeSpecificSettings()\n\n    def saveSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().saveSettings()\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version < 2:\n            for action, params in settings[\"storedsettings\"][\"preprocessors\"]:\n                if action == \"orange.preprocess.scale\":\n                    scale = center = None\n                    if \"center\" in params:\n                        center = params.pop(\"center\").name\n                    if \"scale\" in params:\n                        scale = params.pop(\"scale\").name\n                    migratable = {\n                        (\"Mean\", \"NoScaling\"): Scale.CenterByMean,\n                        (\"NoCentering\", \"Std\"): Scale.ScaleBySD,\n                        (\"Mean\", \"Std\"): Scale.NormalizeBySD,\n                        (\"NoCentering\", \"Span\"): Scale.NormalizeBySpan_ZeroBased\n                    }\n                    params[\"method\"] = \\\n                        migratable.get((center, scale), Scale.NormalizeBySD)\n\n    def onDeleteWidget(self):\n        self.data = None\n        self.set_model(None)\n        super().onDeleteWidget()\n\n    @Slot()\n    def __update_size_constraint(self):\n        # Update minimum width constraint on the scroll area containing\n        # the 'instantiated' preprocessor list (to avoid the horizontal\n        # scroll bar).\n        sh = self.flow_view.minimumSizeHint()\n        scroll_width = self.scroll_area.verticalScrollBar().width()\n        self.scroll_area.setMinimumWidth(\n            min(max(sh.width() + scroll_width + 2, self.controlArea.width()),\n                520))\n\n    def sizeHint(self):\n        sh = super().sizeHint()\n        return sh.expandedTo(QSize(sh.width() + 300, 500))\n\n    def send_report(self):\n        pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w)\n              for i, w in enumerate(self.controler.view.widgets())]\n        if pp:\n            self.report_items(\"Settings\", pp)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 1065,
                                "end_lineno": 1391,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# Test methods with long descriptive names can omit docstrings\n# pylint: disable=missing-docstring\nimport numpy as np\n\nfrom Orange.data import Table\nfrom Orange.preprocess import (\n    Randomize, Scale, Discretize, Continuize, Impute, ProjectPCA, \\\n         ProjectCUR, RemoveSparse\n)\nfrom Orange.preprocess import discretize, impute, fss, score\nfrom Orange.widgets.data import owpreprocess\nfrom Orange.widgets.data.owpreprocess import OWPreprocess, \\\n    UnivariateFeatureSelect, Scale as ScaleEditor\nfrom Orange.widgets.tests.base import WidgetTest, datasets\n\n\nclass TestOWPreprocess(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(OWPreprocess)\n        self.zoo = Table(\"zoo\")\n\n    def test_randomize(self):\n        saved = {\"preprocessors\": [(\"orange.preprocess.randomize\",\n                                    {\"rand_type\": Randomize.RandomizeClasses,\n                                     \"rand_seed\": 1})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, self.zoo)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        r = Randomize(Randomize.RandomizeClasses, rand_seed=1)\n        expected = r(self.zoo)\n\n        np.testing.assert_array_equal(expected.X, output.X)\n        np.testing.assert_array_equal(expected.Y, output.Y)\n        np.testing.assert_array_equal(expected.metas, output.metas)\n\n        np.testing.assert_array_equal(self.zoo.X, output.X)\n        np.testing.assert_array_equal(self.zoo.metas, output.metas)\n        self.assertFalse(np.array_equal(self.zoo.Y, output.Y))\n\n    def test_remove_sparse(self):\n        data = Table(\"iris\")\n        idx = int(data.X.shape[0]/10)\n        data.X[:idx+1, 0] = np.zeros((idx+1,))\n        saved = {\"preprocessors\": [(\"orange.preprocess.remove_sparse\",\n                                    {'filter0': True, 'useFixedThreshold': False,\n                                     'percThresh':10, 'fixedThresh': 50})]}\n        model = self.widget.load(saved)\n\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_array_equal(output.X, data.X[:, 1:])\n        np.testing.assert_array_equal(output.Y, data.Y)\n        np.testing.assert_array_equal(output.metas, data.metas)\n\n    def test_normalize(self):\n        data = Table(\"iris\")\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.NormalizeBySD})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_allclose(output.X.mean(0), 0, atol=1e-7)\n        np.testing.assert_allclose(output.X.std(0), 1, atol=1e-7)\n\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.CenterByMean})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_allclose(output.X.mean(0), 0, atol=1e-7)\n        self.assertTrue((output.X.std(0) > 0).all())\n\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.ScaleBySD})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertTrue((output.X.mean(0) != 0).all())\n        np.testing.assert_allclose(output.X.std(0), 1, atol=1e-7)\n\n        saved = {\"preprocessors\":\n                 [(\"orange.preprocess.scale\",\n                   {\"method\": ScaleEditor.NormalizeBySpan_ZeroBased})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertAlmostEqual(output.X.min(), 0)\n        self.assertAlmostEqual(output.X.max(), 1)\n\n        saved = {\"preprocessors\":\n                 [(\"orange.preprocess.scale\",\n                   {\"method\": ScaleEditor.NormalizeSpan_NonZeroBased})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertAlmostEqual(output.X.min(), -1)\n        self.assertAlmostEqual(output.X.max(), 1)\n\n    def test_select_features(self):\n        data = Table(\"iris\")\n        saved = {\"preprocessors\": [(\"orange.preprocess.fss\",\n                                    {\"strategy\": UnivariateFeatureSelect.Fixed,\n                                     \"k\": 2})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertEqual(len(output.domain.attributes), 2)\n\n        saved = {\"preprocessors\": [\n            (\"orange.preprocess.fss\",\n             {\"strategy\": UnivariateFeatureSelect.Proportion,\n              \"p\": 75})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertEqual(len(output.domain.attributes), 3)\n\n    def test_data_column_nans(self):\n        \"\"\"\n        ZeroDivisonError - Weights sum to zero, can't be normalized\n        In case when all rows in a column are NaN then it throws that error.\n        GH-2064\n        \"\"\"\n        table = datasets.data_one_column_nans()\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"center\": Scale.CenteringType.Mean,\n                                     \"scale\": Scale.ScalingType.Std})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, table)\n\n\n# Test for editors\nclass TestDiscretizeEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.DiscretizeEditor()\n        self.assertEqual(widget.parameters(),\n                         {\"method\": owpreprocess.DiscretizeEditor.EqualFreq,\n                          \"n\": 4})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Discretize)\n        self.assertIsInstance(p.method, discretize.EqualFreq)\n        widget.setParameters(\n            {\"method\": owpreprocess.DiscretizeEditor.EntropyMDL}\n        )\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Discretize)\n        self.assertIsInstance(p.method, discretize.EntropyMDL)\n\n        widget.setParameters(\n            {\"method\": owpreprocess.DiscretizeEditor.EqualWidth,\n             \"n\": 10}\n        )\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Discretize)\n        self.assertIsInstance(p.method, discretize.EqualWidth)\n        self.assertEqual(p.method.n, 10)\n\n\nclass TestContinuizeEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.ContinuizeEditor()\n        self.assertEqual(widget.parameters(),\n                         {\"multinomial_treatment\": Continuize.Indicators})\n\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Continuize)\n        self.assertEqual(p.multinomial_treatment, Continuize.Indicators)\n\n        widget.setParameters(\n            {\"multinomial_treatment\": Continuize.FrequentAsBase})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Continuize)\n        self.assertEqual(p.multinomial_treatment, Continuize.FrequentAsBase)\n\n\nclass TestImputeEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.ImputeEditor()\n        self.assertEqual(widget.parameters(),\n                         {\"method\": owpreprocess.ImputeEditor.Average})\n        widget.setParameters(\n            {\"method\": owpreprocess.ImputeEditor.Average}\n        )\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Impute)\n        self.assertIsInstance(p.method, impute.Average)\n\n\nclass TestFeatureSelectEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.FeatureSelectEditor()\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, fss.SelectBestFeatures)\n        self.assertIsInstance(p.method, score.InfoGain)\n        self.assertEqual(p.k, 10)\n\n        widget.setParameters({\"k\": 9999})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, fss.SelectBestFeatures)\n        self.assertEqual(p.k, 9999)\n\n\nclass TestRandomFeatureSelectEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.RandomFeatureSelectEditor()\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, fss.SelectRandomFeatures)\n        self.assertEqual(p.k, 10)\n\n        widget.setParameters({\"strategy\": owpreprocess.RandomFeatureSelectEditor.Fixed,\n                              \"k\": 9999})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, fss.SelectRandomFeatures)\n        self.assertEqual(p.k, 9999)\n\n        widget.setParameters(\n            {\"strategy\": owpreprocess.RandomFeatureSelectEditor.Percentage,\n             \"p\": 25})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, fss.SelectRandomFeatures)\n        self.assertEqual(p.k, 0.25)\n\n\nclass TestRandomizeEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.Randomize()\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Randomize)\n        self.assertEqual(p.rand_type, Randomize.RandomizeClasses)\n\n        widget.setParameters({\"rand_type\": Randomize.RandomizeAttributes})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Randomize)\n        self.assertEqual(p.rand_type, Randomize.RandomizeAttributes)\n\n\nclass TestPCAEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.PCA()\n        self.assertEqual(widget.parameters(),\n                         {\"n_components\": 10})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, ProjectPCA)\n        self.assertEqual(p.n_components, 10)\n\n        widget.setParameters({\"n_components\": 5})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, ProjectPCA)\n        self.assertEqual(p.n_components, 5)\n\n\nclass TestCUREditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.CUR()\n        self.assertEqual(widget.parameters(),\n                         {\"rank\": 10, \"max_error\": 1})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, ProjectCUR)\n        self.assertEqual(p.rank, 10)\n        self.assertEqual(p.max_error, 1)\n\n        widget.setParameters({\"rank\": 9999})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, ProjectCUR)\n        self.assertEqual(p.rank, 9999)\n\n        widget.setParameters({\"rank\": 5, \"max_error\": 0.5})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, ProjectCUR)\n        self.assertEqual(p.rank, 5)\n        self.assertEqual(p.max_error, 0.5)\n\nclass TestRemoveSparseEditor(WidgetTest):\n\n    def test_editor(self):\n        widget = owpreprocess.RemoveSparseEditor()\n        self.assertEqual(\n            widget.parameters(),\n            dict(fixedThresh=50, percThresh=5, filter0=True,\n                 useFixedThreshold=False))\n\n        p = widget.createinstance(widget.parameters())\n        widget.filterSettingsClicked()\n        self.assertTrue(widget.percSpin.isEnabled())\n        self.assertFalse(widget.fixedSpin.isEnabled())\n        self.assertIsInstance(p, RemoveSparse)\n        self.assertEqual(p.filter0, True)\n        self.assertEqual(p.threshold, 0.05)\n\n        widget.setParameters(\n            dict(useFixedThreshold=True, fixedThresh=30, filter0=False))\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, RemoveSparse)\n        self.assertEqual(p.threshold, 30)\n        self.assertFalse(p.filter0)\n",
            "new_file_content": "# Test methods with long descriptive names can omit docstrings\n# pylint: disable=missing-docstring,unsubscriptable-object\nfrom unittest.mock import Mock\nimport numpy as np\n\nfrom Orange.data import Table\nfrom Orange.preprocess import (\n    Randomize, Scale, Discretize, Continuize, Impute, ProjectPCA, \\\n         ProjectCUR, RemoveSparse\n)\nfrom Orange.preprocess import discretize, impute, fss, score\nfrom Orange.widgets.data import owpreprocess\nfrom Orange.widgets.data.owpreprocess import OWPreprocess, \\\n    UnivariateFeatureSelect, Scale as ScaleEditor\nfrom Orange.widgets.tests.base import WidgetTest, datasets\nfrom orangewidget.widget import StateInfo\n\n\nclass TestOWPreprocess(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(OWPreprocess)\n        self.zoo = Table(\"zoo\")\n\n    def test_randomize(self):\n        saved = {\"preprocessors\": [(\"orange.preprocess.randomize\",\n                                    {\"rand_type\": Randomize.RandomizeClasses,\n                                     \"rand_seed\": 1})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, self.zoo)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        r = Randomize(Randomize.RandomizeClasses, rand_seed=1)\n        expected = r(self.zoo)\n\n        np.testing.assert_array_equal(expected.X, output.X)\n        np.testing.assert_array_equal(expected.Y, output.Y)\n        np.testing.assert_array_equal(expected.metas, output.metas)\n\n        np.testing.assert_array_equal(self.zoo.X, output.X)\n        np.testing.assert_array_equal(self.zoo.metas, output.metas)\n        self.assertFalse(np.array_equal(self.zoo.Y, output.Y))\n\n    def test_remove_sparse(self):\n        data = Table(\"iris\")\n        idx = int(data.X.shape[0]/10)\n        data.X[:idx+1, 0] = np.zeros((idx+1,))\n        saved = {\"preprocessors\": [(\"orange.preprocess.remove_sparse\",\n                                    {'filter0': True, 'useFixedThreshold': False,\n                                     'percThresh':10, 'fixedThresh': 50})]}\n        model = self.widget.load(saved)\n\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_array_equal(output.X, data.X[:, 1:])\n        np.testing.assert_array_equal(output.Y, data.Y)\n        np.testing.assert_array_equal(output.metas, data.metas)\n\n    def test_normalize(self):\n        data = Table(\"iris\")\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.NormalizeBySD})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_allclose(output.X.mean(0), 0, atol=1e-7)\n        np.testing.assert_allclose(output.X.std(0), 1, atol=1e-7)\n\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.CenterByMean})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_allclose(output.X.mean(0), 0, atol=1e-7)\n        self.assertTrue((output.X.std(0) > 0).all())\n\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.ScaleBySD})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertTrue((output.X.mean(0) != 0).all())\n        np.testing.assert_allclose(output.X.std(0), 1, atol=1e-7)\n\n        saved = {\"preprocessors\":\n                 [(\"orange.preprocess.scale\",\n                   {\"method\": ScaleEditor.NormalizeBySpan_ZeroBased})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertAlmostEqual(output.X.min(), 0)\n        self.assertAlmostEqual(output.X.max(), 1)\n\n        saved = {\"preprocessors\":\n                 [(\"orange.preprocess.scale\",\n                   {\"method\": ScaleEditor.NormalizeSpan_NonZeroBased})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertAlmostEqual(output.X.min(), -1)\n        self.assertAlmostEqual(output.X.max(), 1)\n\n    def test_select_features(self):\n        data = Table(\"iris\")\n        saved = {\"preprocessors\": [(\"orange.preprocess.fss\",\n                                    {\"strategy\": UnivariateFeatureSelect.Fixed,\n                                     \"k\": 2})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertEqual(len(output.domain.attributes), 2)\n\n        saved = {\"preprocessors\": [\n            (\"orange.preprocess.fss\",\n             {\"strategy\": UnivariateFeatureSelect.Proportion,\n              \"p\": 75})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertEqual(len(output.domain.attributes), 3)\n\n    def test_data_column_nans(self):\n        \"\"\"\n        ZeroDivisonError - Weights sum to zero, can't be normalized\n        In case when all rows in a column are NaN then it throws that error.\n        GH-2064\n        \"\"\"\n        table = datasets.data_one_column_nans()\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"center\": Scale.CenteringType.Mean,\n                                     \"scale\": Scale.ScalingType.Std})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, table)\n\n    def test_summary(self):\n        \"\"\"Check if status bar is updated when data is received\"\"\"\n        data = Table(\"iris\")\n        input_sum = self.widget.info.set_input_summary = Mock()\n        output_sum = self.widget.info.set_output_summary = Mock()\n\n        self.send_signal(self.widget.Inputs.data, data)\n        input_sum.assert_called_with(int(StateInfo.format_number(len(data))))\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        output_sum.assert_called_with(int(StateInfo.format_number(len(output))))\n\n        input_sum.reset_mock()\n        output_sum.reset_mock()\n        self.send_signal(self.widget.Inputs.data, None)\n        input_sum.assert_called_once()\n        self.assertEqual(input_sum.call_args[0][0].brief, \"\")\n        output_sum.assert_called_once()\n        self.assertEqual(output_sum.call_args[0][0].brief, \"\")\n\n\n# Test for editors\nclass TestDiscretizeEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.DiscretizeEditor()\n        self.assertEqual(widget.parameters(),\n                         {\"method\": owpreprocess.DiscretizeEditor.EqualFreq,\n                          \"n\": 4})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Discretize)\n        self.assertIsInstance(p.method, discretize.EqualFreq)\n        widget.setParameters(\n            {\"method\": owpreprocess.DiscretizeEditor.EntropyMDL}\n        )\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Discretize)\n        self.assertIsInstance(p.method, discretize.EntropyMDL)\n\n        widget.setParameters(\n            {\"method\": owpreprocess.DiscretizeEditor.EqualWidth,\n             \"n\": 10}\n        )\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Discretize)\n        self.assertIsInstance(p.method, discretize.EqualWidth)\n        self.assertEqual(p.method.n, 10)\n\n\nclass TestContinuizeEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.ContinuizeEditor()\n        self.assertEqual(widget.parameters(),\n                         {\"multinomial_treatment\": Continuize.Indicators})\n\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Continuize)\n        self.assertEqual(p.multinomial_treatment, Continuize.Indicators)\n\n        widget.setParameters(\n            {\"multinomial_treatment\": Continuize.FrequentAsBase})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Continuize)\n        self.assertEqual(p.multinomial_treatment, Continuize.FrequentAsBase)\n\n\nclass TestImputeEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.ImputeEditor()\n        self.assertEqual(widget.parameters(),\n                         {\"method\": owpreprocess.ImputeEditor.Average})\n        widget.setParameters(\n            {\"method\": owpreprocess.ImputeEditor.Average}\n        )\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Impute)\n        self.assertIsInstance(p.method, impute.Average)\n\n\nclass TestFeatureSelectEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.FeatureSelectEditor()\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, fss.SelectBestFeatures)\n        self.assertIsInstance(p.method, score.InfoGain)\n        self.assertEqual(p.k, 10)\n\n        widget.setParameters({\"k\": 9999})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, fss.SelectBestFeatures)\n        self.assertEqual(p.k, 9999)\n\n\nclass TestRandomFeatureSelectEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.RandomFeatureSelectEditor()\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, fss.SelectRandomFeatures)\n        self.assertEqual(p.k, 10)\n\n        widget.setParameters({\"strategy\": owpreprocess.RandomFeatureSelectEditor.Fixed,\n                              \"k\": 9999})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, fss.SelectRandomFeatures)\n        self.assertEqual(p.k, 9999)\n\n        widget.setParameters(\n            {\"strategy\": owpreprocess.RandomFeatureSelectEditor.Percentage,\n             \"p\": 25})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, fss.SelectRandomFeatures)\n        self.assertEqual(p.k, 0.25)\n\n\nclass TestRandomizeEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.Randomize()\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Randomize)\n        self.assertEqual(p.rand_type, Randomize.RandomizeClasses)\n\n        widget.setParameters({\"rand_type\": Randomize.RandomizeAttributes})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Randomize)\n        self.assertEqual(p.rand_type, Randomize.RandomizeAttributes)\n\n\nclass TestPCAEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.PCA()\n        self.assertEqual(widget.parameters(),\n                         {\"n_components\": 10})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, ProjectPCA)\n        self.assertEqual(p.n_components, 10)\n\n        widget.setParameters({\"n_components\": 5})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, ProjectPCA)\n        self.assertEqual(p.n_components, 5)\n\n\nclass TestCUREditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.CUR()\n        self.assertEqual(widget.parameters(),\n                         {\"rank\": 10, \"max_error\": 1})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, ProjectCUR)\n        self.assertEqual(p.rank, 10)\n        self.assertEqual(p.max_error, 1)\n\n        widget.setParameters({\"rank\": 9999})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, ProjectCUR)\n        self.assertEqual(p.rank, 9999)\n\n        widget.setParameters({\"rank\": 5, \"max_error\": 0.5})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, ProjectCUR)\n        self.assertEqual(p.rank, 5)\n        self.assertEqual(p.max_error, 0.5)\n\nclass TestRemoveSparseEditor(WidgetTest):\n\n    def test_editor(self):\n        widget = owpreprocess.RemoveSparseEditor()\n        self.assertEqual(\n            widget.parameters(),\n            dict(fixedThresh=50, percThresh=5, filter0=True,\n                 useFixedThreshold=False))\n\n        p = widget.createinstance(widget.parameters())\n        widget.filterSettingsClicked()\n        self.assertTrue(widget.percSpin.isEnabled())\n        self.assertFalse(widget.fixedSpin.isEnabled())\n        self.assertIsInstance(p, RemoveSparse)\n        self.assertEqual(p.filter0, True)\n        self.assertEqual(p.threshold, 0.05)\n\n        widget.setParameters(\n            dict(useFixedThreshold=True, fixedThresh=30, filter0=False))\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, RemoveSparse)\n        self.assertEqual(p.threshold, 30)\n        self.assertFalse(p.filter0)\n",
            "header": {
                "file": {
                    "path": "Orange/widgets/data/tests/test_owpreprocess.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "61a3a6e2f",
                "new_commit_hash": "873403cf4",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/widgets/data/tests/test_owpreprocess.py"
            },
            "plus_file": {
                "path": "b/Orange/widgets/data/tests/test_owpreprocess.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 1,
                            "length": 5
                        },
                        "new_range": {
                            "start": 1,
                            "length": 6
                        },
                        "section": ""
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "# Test methods with long descriptive names can omit docstrings",
                                "type": "context"
                            },
                            {
                                "content": "# pylint: disable=missing-docstring",
                                "type": "deleted"
                            },
                            {
                                "content": "# pylint: disable=missing-docstring,unsubscriptable-object",
                                "type": "added"
                            },
                            {
                                "content": "from unittest.mock import Mock",
                                "type": "added"
                            },
                            {
                                "content": "import numpy as np",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "from Orange.data import Table",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "Orange/widgets/data/tests/test_owpreprocess.py",
                            "type": "statement",
                            "name": "",
                            "content": "from unittest.mock import Mock",
                            "ast_type_str": "ImportFrom",
                            "start_lineno": 3,
                            "end_lineno": 3,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 12,
                            "length": 6
                        },
                        "new_range": {
                            "start": 13,
                            "length": 7
                        },
                        "section": "from Orange.widgets.data import owpreprocess"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "from Orange.widgets.data.owpreprocess import OWPreprocess, \\",
                                "type": "context"
                            },
                            {
                                "content": "    UnivariateFeatureSelect, Scale as ScaleEditor",
                                "type": "context"
                            },
                            {
                                "content": "from Orange.widgets.tests.base import WidgetTest, datasets",
                                "type": "context"
                            },
                            {
                                "content": "from orangewidget.widget import StateInfo",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "class TestOWPreprocess(WidgetTest):",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "Orange/widgets/data/tests/test_owpreprocess.py",
                            "type": "statement",
                            "name": "",
                            "content": "from orangewidget.widget import StateInfo",
                            "ast_type_str": "ImportFrom",
                            "start_lineno": 16,
                            "end_lineno": 16,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 138,
                            "length": 6
                        },
                        "new_range": {
                            "start": 140,
                            "length": 25
                        },
                        "section": "class TestOWPreprocess(WidgetTest):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.widget.set_model(model)",
                                "type": "context"
                            },
                            {
                                "content": "        self.send_signal(self.widget.Inputs.data, table)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def test_summary(self):",
                                "type": "added"
                            },
                            {
                                "content": "        \"\"\"Check if status bar is updated when data is received\"\"\"",
                                "type": "added"
                            },
                            {
                                "content": "        data = Table(\"iris\")",
                                "type": "added"
                            },
                            {
                                "content": "        input_sum = self.widget.info.set_input_summary = Mock()",
                                "type": "added"
                            },
                            {
                                "content": "        output_sum = self.widget.info.set_output_summary = Mock()",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        self.send_signal(self.widget.Inputs.data, data)",
                                "type": "added"
                            },
                            {
                                "content": "        input_sum.assert_called_with(int(StateInfo.format_number(len(data))))",
                                "type": "added"
                            },
                            {
                                "content": "        output = self.get_output(self.widget.Outputs.preprocessed_data)",
                                "type": "added"
                            },
                            {
                                "content": "        output_sum.assert_called_with(int(StateInfo.format_number(len(output))))",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        input_sum.reset_mock()",
                                "type": "added"
                            },
                            {
                                "content": "        output_sum.reset_mock()",
                                "type": "added"
                            },
                            {
                                "content": "        self.send_signal(self.widget.Inputs.data, None)",
                                "type": "added"
                            },
                            {
                                "content": "        input_sum.assert_called_once()",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(input_sum.call_args[0][0].brief, \"\")",
                                "type": "added"
                            },
                            {
                                "content": "        output_sum.assert_called_once()",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(output_sum.call_args[0][0].brief, \"\")",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "# Test for editors",
                                "type": "context"
                            },
                            {
                                "content": "class TestDiscretizeEditor(WidgetTest):",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/tests/test_owpreprocess.py",
                            "type": "class",
                            "name": "TestOWPreprocess",
                            "content": "class TestOWPreprocess(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(OWPreprocess)\n        self.zoo = Table(\"zoo\")\n\n    def test_randomize(self):\n        saved = {\"preprocessors\": [(\"orange.preprocess.randomize\",\n                                    {\"rand_type\": Randomize.RandomizeClasses,\n                                     \"rand_seed\": 1})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, self.zoo)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        r = Randomize(Randomize.RandomizeClasses, rand_seed=1)\n        expected = r(self.zoo)\n\n        np.testing.assert_array_equal(expected.X, output.X)\n        np.testing.assert_array_equal(expected.Y, output.Y)\n        np.testing.assert_array_equal(expected.metas, output.metas)\n\n        np.testing.assert_array_equal(self.zoo.X, output.X)\n        np.testing.assert_array_equal(self.zoo.metas, output.metas)\n        self.assertFalse(np.array_equal(self.zoo.Y, output.Y))\n\n    def test_remove_sparse(self):\n        data = Table(\"iris\")\n        idx = int(data.X.shape[0]/10)\n        data.X[:idx+1, 0] = np.zeros((idx+1,))\n        saved = {\"preprocessors\": [(\"orange.preprocess.remove_sparse\",\n                                    {'filter0': True, 'useFixedThreshold': False,\n                                     'percThresh':10, 'fixedThresh': 50})]}\n        model = self.widget.load(saved)\n\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_array_equal(output.X, data.X[:, 1:])\n        np.testing.assert_array_equal(output.Y, data.Y)\n        np.testing.assert_array_equal(output.metas, data.metas)\n\n    def test_normalize(self):\n        data = Table(\"iris\")\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.NormalizeBySD})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_allclose(output.X.mean(0), 0, atol=1e-7)\n        np.testing.assert_allclose(output.X.std(0), 1, atol=1e-7)\n\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.CenterByMean})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_allclose(output.X.mean(0), 0, atol=1e-7)\n        self.assertTrue((output.X.std(0) > 0).all())\n\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.ScaleBySD})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertTrue((output.X.mean(0) != 0).all())\n        np.testing.assert_allclose(output.X.std(0), 1, atol=1e-7)\n\n        saved = {\"preprocessors\":\n                 [(\"orange.preprocess.scale\",\n                   {\"method\": ScaleEditor.NormalizeBySpan_ZeroBased})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertAlmostEqual(output.X.min(), 0)\n        self.assertAlmostEqual(output.X.max(), 1)\n\n        saved = {\"preprocessors\":\n                 [(\"orange.preprocess.scale\",\n                   {\"method\": ScaleEditor.NormalizeSpan_NonZeroBased})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertAlmostEqual(output.X.min(), -1)\n        self.assertAlmostEqual(output.X.max(), 1)\n\n    def test_select_features(self):\n        data = Table(\"iris\")\n        saved = {\"preprocessors\": [(\"orange.preprocess.fss\",\n                                    {\"strategy\": UnivariateFeatureSelect.Fixed,\n                                     \"k\": 2})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertEqual(len(output.domain.attributes), 2)\n\n        saved = {\"preprocessors\": [\n            (\"orange.preprocess.fss\",\n             {\"strategy\": UnivariateFeatureSelect.Proportion,\n              \"p\": 75})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertEqual(len(output.domain.attributes), 3)\n\n    def test_data_column_nans(self):\n        \"\"\"\n        ZeroDivisonError - Weights sum to zero, can't be normalized\n        In case when all rows in a column are NaN then it throws that error.\n        GH-2064\n        \"\"\"\n        table = datasets.data_one_column_nans()\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"center\": Scale.CenteringType.Mean,\n                                     \"scale\": Scale.ScalingType.Std})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, table)\n\n    def test_summary(self):\n        \"\"\"Check if status bar is updated when data is received\"\"\"\n        data = Table(\"iris\")\n        input_sum = self.widget.info.set_input_summary = Mock()\n        output_sum = self.widget.info.set_output_summary = Mock()\n\n        self.send_signal(self.widget.Inputs.data, data)\n        input_sum.assert_called_with(int(StateInfo.format_number(len(data))))\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        output_sum.assert_called_with(int(StateInfo.format_number(len(output))))\n\n        input_sum.reset_mock()\n        output_sum.reset_mock()\n        self.send_signal(self.widget.Inputs.data, None)\n        input_sum.assert_called_once()\n        self.assertEqual(input_sum.call_args[0][0].brief, \"\")\n        output_sum.assert_called_once()\n        self.assertEqual(output_sum.call_args[0][0].brief, \"\")",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 19,
                            "end_lineno": 160,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "Orange/widgets/data/tests/test_owpreprocess.py",
                            "type": "method",
                            "name": "TestOWPreprocess.test_summary",
                            "content": "class TestOWPreprocess(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(OWPreprocess)\n        self.zoo = Table(\"zoo\")\n\n    def test_randomize(self):\n        saved = {\"preprocessors\": [(\"orange.preprocess.randomize\",\n                                    {\"rand_type\": Randomize.RandomizeClasses,\n                                     \"rand_seed\": 1})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, self.zoo)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        r = Randomize(Randomize.RandomizeClasses, rand_seed=1)\n        expected = r(self.zoo)\n\n        np.testing.assert_array_equal(expected.X, output.X)\n        np.testing.assert_array_equal(expected.Y, output.Y)\n        np.testing.assert_array_equal(expected.metas, output.metas)\n\n        np.testing.assert_array_equal(self.zoo.X, output.X)\n        np.testing.assert_array_equal(self.zoo.metas, output.metas)\n        self.assertFalse(np.array_equal(self.zoo.Y, output.Y))\n\n    def test_remove_sparse(self):\n        data = Table(\"iris\")\n        idx = int(data.X.shape[0]/10)\n        data.X[:idx+1, 0] = np.zeros((idx+1,))\n        saved = {\"preprocessors\": [(\"orange.preprocess.remove_sparse\",\n                                    {'filter0': True, 'useFixedThreshold': False,\n                                     'percThresh':10, 'fixedThresh': 50})]}\n        model = self.widget.load(saved)\n\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_array_equal(output.X, data.X[:, 1:])\n        np.testing.assert_array_equal(output.Y, data.Y)\n        np.testing.assert_array_equal(output.metas, data.metas)\n\n    def test_normalize(self):\n        data = Table(\"iris\")\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.NormalizeBySD})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_allclose(output.X.mean(0), 0, atol=1e-7)\n        np.testing.assert_allclose(output.X.std(0), 1, atol=1e-7)\n\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.CenterByMean})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_allclose(output.X.mean(0), 0, atol=1e-7)\n        self.assertTrue((output.X.std(0) > 0).all())\n\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.ScaleBySD})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertTrue((output.X.mean(0) != 0).all())\n        np.testing.assert_allclose(output.X.std(0), 1, atol=1e-7)\n\n        saved = {\"preprocessors\":\n                 [(\"orange.preprocess.scale\",\n                   {\"method\": ScaleEditor.NormalizeBySpan_ZeroBased})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertAlmostEqual(output.X.min(), 0)\n        self.assertAlmostEqual(output.X.max(), 1)\n\n        saved = {\"preprocessors\":\n                 [(\"orange.preprocess.scale\",\n                   {\"method\": ScaleEditor.NormalizeSpan_NonZeroBased})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertAlmostEqual(output.X.min(), -1)\n        self.assertAlmostEqual(output.X.max(), 1)\n\n    def test_select_features(self):\n        data = Table(\"iris\")\n        saved = {\"preprocessors\": [(\"orange.preprocess.fss\",\n                                    {\"strategy\": UnivariateFeatureSelect.Fixed,\n                                     \"k\": 2})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertEqual(len(output.domain.attributes), 2)\n\n        saved = {\"preprocessors\": [\n            (\"orange.preprocess.fss\",\n             {\"strategy\": UnivariateFeatureSelect.Proportion,\n              \"p\": 75})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertEqual(len(output.domain.attributes), 3)\n\n    def test_data_column_nans(self):\n        \"\"\"\n        ZeroDivisonError - Weights sum to zero, can't be normalized\n        In case when all rows in a column are NaN then it throws that error.\n        GH-2064\n        \"\"\"\n        table = datasets.data_one_column_nans()\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"center\": Scale.CenteringType.Mean,\n                                     \"scale\": Scale.ScalingType.Std})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, table)\n\n    def test_summary(self):\n        \"\"\"Check if status bar is updated when data is received\"\"\"\n        data = Table(\"iris\")\n        input_sum = self.widget.info.set_input_summary = Mock()\n        output_sum = self.widget.info.set_output_summary = Mock()\n\n        self.send_signal(self.widget.Inputs.data, data)\n        input_sum.assert_called_with(int(StateInfo.format_number(len(data))))\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        output_sum.assert_called_with(int(StateInfo.format_number(len(output))))\n\n        input_sum.reset_mock()\n        output_sum.reset_mock()\n        self.send_signal(self.widget.Inputs.data, None)\n        input_sum.assert_called_once()\n        self.assertEqual(input_sum.call_args[0][0].brief, \"\")\n        output_sum.assert_called_once()\n        self.assertEqual(output_sum.call_args[0][0].brief, \"\")",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 143,
                            "end_lineno": 160,
                            "parent": {
                                "file_name": "Orange/widgets/data/tests/test_owpreprocess.py",
                                "type": "class",
                                "name": "TestOWPreprocess",
                                "content": "class TestOWPreprocess(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(OWPreprocess)\n        self.zoo = Table(\"zoo\")\n\n    def test_randomize(self):\n        saved = {\"preprocessors\": [(\"orange.preprocess.randomize\",\n                                    {\"rand_type\": Randomize.RandomizeClasses,\n                                     \"rand_seed\": 1})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, self.zoo)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        r = Randomize(Randomize.RandomizeClasses, rand_seed=1)\n        expected = r(self.zoo)\n\n        np.testing.assert_array_equal(expected.X, output.X)\n        np.testing.assert_array_equal(expected.Y, output.Y)\n        np.testing.assert_array_equal(expected.metas, output.metas)\n\n        np.testing.assert_array_equal(self.zoo.X, output.X)\n        np.testing.assert_array_equal(self.zoo.metas, output.metas)\n        self.assertFalse(np.array_equal(self.zoo.Y, output.Y))\n\n    def test_remove_sparse(self):\n        data = Table(\"iris\")\n        idx = int(data.X.shape[0]/10)\n        data.X[:idx+1, 0] = np.zeros((idx+1,))\n        saved = {\"preprocessors\": [(\"orange.preprocess.remove_sparse\",\n                                    {'filter0': True, 'useFixedThreshold': False,\n                                     'percThresh':10, 'fixedThresh': 50})]}\n        model = self.widget.load(saved)\n\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_array_equal(output.X, data.X[:, 1:])\n        np.testing.assert_array_equal(output.Y, data.Y)\n        np.testing.assert_array_equal(output.metas, data.metas)\n\n    def test_normalize(self):\n        data = Table(\"iris\")\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.NormalizeBySD})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_allclose(output.X.mean(0), 0, atol=1e-7)\n        np.testing.assert_allclose(output.X.std(0), 1, atol=1e-7)\n\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.CenterByMean})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_allclose(output.X.mean(0), 0, atol=1e-7)\n        self.assertTrue((output.X.std(0) > 0).all())\n\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.ScaleBySD})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertTrue((output.X.mean(0) != 0).all())\n        np.testing.assert_allclose(output.X.std(0), 1, atol=1e-7)\n\n        saved = {\"preprocessors\":\n                 [(\"orange.preprocess.scale\",\n                   {\"method\": ScaleEditor.NormalizeBySpan_ZeroBased})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertAlmostEqual(output.X.min(), 0)\n        self.assertAlmostEqual(output.X.max(), 1)\n\n        saved = {\"preprocessors\":\n                 [(\"orange.preprocess.scale\",\n                   {\"method\": ScaleEditor.NormalizeSpan_NonZeroBased})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertAlmostEqual(output.X.min(), -1)\n        self.assertAlmostEqual(output.X.max(), 1)\n\n    def test_select_features(self):\n        data = Table(\"iris\")\n        saved = {\"preprocessors\": [(\"orange.preprocess.fss\",\n                                    {\"strategy\": UnivariateFeatureSelect.Fixed,\n                                     \"k\": 2})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertEqual(len(output.domain.attributes), 2)\n\n        saved = {\"preprocessors\": [\n            (\"orange.preprocess.fss\",\n             {\"strategy\": UnivariateFeatureSelect.Proportion,\n              \"p\": 75})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertEqual(len(output.domain.attributes), 3)\n\n    def test_data_column_nans(self):\n        \"\"\"\n        ZeroDivisonError - Weights sum to zero, can't be normalized\n        In case when all rows in a column are NaN then it throws that error.\n        GH-2064\n        \"\"\"\n        table = datasets.data_one_column_nans()\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"center\": Scale.CenteringType.Mean,\n                                     \"scale\": Scale.ScalingType.Std})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, table)\n\n    def test_summary(self):\n        \"\"\"Check if status bar is updated when data is received\"\"\"\n        data = Table(\"iris\")\n        input_sum = self.widget.info.set_input_summary = Mock()\n        output_sum = self.widget.info.set_output_summary = Mock()\n\n        self.send_signal(self.widget.Inputs.data, data)\n        input_sum.assert_called_with(int(StateInfo.format_number(len(data))))\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        output_sum.assert_called_with(int(StateInfo.format_number(len(output))))\n\n        input_sum.reset_mock()\n        output_sum.reset_mock()\n        self.send_signal(self.widget.Inputs.data, None)\n        input_sum.assert_called_once()\n        self.assertEqual(input_sum.call_args[0][0].brief, \"\")\n        output_sum.assert_called_once()\n        self.assertEqual(output_sum.call_args[0][0].brief, \"\")",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 19,
                                "end_lineno": 160,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "7939273cd469f04d1e8d7dec7a3e4575af6f348a^",
    "new_commit_hash": "7939273cd469f04d1e8d7dec7a3e4575af6f348a",
    "commit_message": "OWPreprocess: data info displayed in status bar",
    "commit_date": "2020-01-15T20:23:30+01:00",
    "metadata": {}
}
{
    "repo_name": "orange3",
    "new_commit_hash": "7939273cd469f04d1e8d7dec7a3e4575af6f348a",
    "test_file_codes": [
        "# Test methods with long descriptive names can omit docstrings\n# pylint: disable=missing-docstring,unsubscriptable-object\nfrom unittest.mock import Mock\nimport numpy as np\n\nfrom Orange.data import Table\nfrom Orange.preprocess import (\n    Randomize, Scale, Discretize, Continuize, Impute, ProjectPCA, \\\n         ProjectCUR, RemoveSparse\n)\nfrom Orange.preprocess import discretize, impute, fss, score\nfrom Orange.widgets.data import owpreprocess\nfrom Orange.widgets.data.owpreprocess import OWPreprocess, \\\n    UnivariateFeatureSelect, Scale as ScaleEditor\nfrom Orange.widgets.tests.base import WidgetTest, datasets\nfrom orangewidget.widget import StateInfo\n\n\nclass TestOWPreprocess(WidgetTest):\n    def setUp(self):\n        self.widget = self.create_widget(OWPreprocess)\n        self.zoo = Table(\"zoo\")\n\n    def test_randomize(self):\n        saved = {\"preprocessors\": [(\"orange.preprocess.randomize\",\n                                    {\"rand_type\": Randomize.RandomizeClasses,\n                                     \"rand_seed\": 1})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, self.zoo)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        r = Randomize(Randomize.RandomizeClasses, rand_seed=1)\n        expected = r(self.zoo)\n\n        np.testing.assert_array_equal(expected.X, output.X)\n        np.testing.assert_array_equal(expected.Y, output.Y)\n        np.testing.assert_array_equal(expected.metas, output.metas)\n\n        np.testing.assert_array_equal(self.zoo.X, output.X)\n        np.testing.assert_array_equal(self.zoo.metas, output.metas)\n        self.assertFalse(np.array_equal(self.zoo.Y, output.Y))\n\n    def test_remove_sparse(self):\n        data = Table(\"iris\")\n        idx = int(data.X.shape[0]/10)\n        data.X[:idx+1, 0] = np.zeros((idx+1,))\n        saved = {\"preprocessors\": [(\"orange.preprocess.remove_sparse\",\n                                    {'filter0': True, 'useFixedThreshold': False,\n                                     'percThresh':10, 'fixedThresh': 50})]}\n        model = self.widget.load(saved)\n\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_array_equal(output.X, data.X[:, 1:])\n        np.testing.assert_array_equal(output.Y, data.Y)\n        np.testing.assert_array_equal(output.metas, data.metas)\n\n    def test_normalize(self):\n        data = Table(\"iris\")\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.NormalizeBySD})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_allclose(output.X.mean(0), 0, atol=1e-7)\n        np.testing.assert_allclose(output.X.std(0), 1, atol=1e-7)\n\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.CenterByMean})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        np.testing.assert_allclose(output.X.mean(0), 0, atol=1e-7)\n        self.assertTrue((output.X.std(0) > 0).all())\n\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"method\": ScaleEditor.ScaleBySD})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertTrue((output.X.mean(0) != 0).all())\n        np.testing.assert_allclose(output.X.std(0), 1, atol=1e-7)\n\n        saved = {\"preprocessors\":\n                 [(\"orange.preprocess.scale\",\n                   {\"method\": ScaleEditor.NormalizeBySpan_ZeroBased})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertAlmostEqual(output.X.min(), 0)\n        self.assertAlmostEqual(output.X.max(), 1)\n\n        saved = {\"preprocessors\":\n                 [(\"orange.preprocess.scale\",\n                   {\"method\": ScaleEditor.NormalizeSpan_NonZeroBased})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertAlmostEqual(output.X.min(), -1)\n        self.assertAlmostEqual(output.X.max(), 1)\n\n    def test_select_features(self):\n        data = Table(\"iris\")\n        saved = {\"preprocessors\": [(\"orange.preprocess.fss\",\n                                    {\"strategy\": UnivariateFeatureSelect.Fixed,\n                                     \"k\": 2})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertEqual(len(output.domain.attributes), 2)\n\n        saved = {\"preprocessors\": [\n            (\"orange.preprocess.fss\",\n             {\"strategy\": UnivariateFeatureSelect.Proportion,\n              \"p\": 75})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, data)\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        self.assertEqual(len(output.domain.attributes), 3)\n\n    def test_data_column_nans(self):\n        \"\"\"\n        ZeroDivisonError - Weights sum to zero, can't be normalized\n        In case when all rows in a column are NaN then it throws that error.\n        GH-2064\n        \"\"\"\n        table = datasets.data_one_column_nans()\n        saved = {\"preprocessors\": [(\"orange.preprocess.scale\",\n                                    {\"center\": Scale.CenteringType.Mean,\n                                     \"scale\": Scale.ScalingType.Std})]}\n        model = self.widget.load(saved)\n        self.widget.set_model(model)\n        self.send_signal(self.widget.Inputs.data, table)\n\n    def test_summary(self):\n        \"\"\"Check if status bar is updated when data is received\"\"\"\n        data = Table(\"iris\")\n        input_sum = self.widget.info.set_input_summary = Mock()\n        output_sum = self.widget.info.set_output_summary = Mock()\n\n        self.send_signal(self.widget.Inputs.data, data)\n        input_sum.assert_called_with(int(StateInfo.format_number(len(data))))\n        output = self.get_output(self.widget.Outputs.preprocessed_data)\n        output_sum.assert_called_with(int(StateInfo.format_number(len(output))))\n\n        input_sum.reset_mock()\n        output_sum.reset_mock()\n        self.send_signal(self.widget.Inputs.data, None)\n        input_sum.assert_called_once()\n        self.assertEqual(input_sum.call_args[0][0].brief, \"\")\n        output_sum.assert_called_once()\n        self.assertEqual(output_sum.call_args[0][0].brief, \"\")\n\n\n# Test for editors\nclass TestDiscretizeEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.DiscretizeEditor()\n        self.assertEqual(widget.parameters(),\n                         {\"method\": owpreprocess.DiscretizeEditor.EqualFreq,\n                          \"n\": 4})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Discretize)\n        self.assertIsInstance(p.method, discretize.EqualFreq)\n        widget.setParameters(\n            {\"method\": owpreprocess.DiscretizeEditor.EntropyMDL}\n        )\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Discretize)\n        self.assertIsInstance(p.method, discretize.EntropyMDL)\n\n        widget.setParameters(\n            {\"method\": owpreprocess.DiscretizeEditor.EqualWidth,\n             \"n\": 10}\n        )\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Discretize)\n        self.assertIsInstance(p.method, discretize.EqualWidth)\n        self.assertEqual(p.method.n, 10)\n\n\nclass TestContinuizeEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.ContinuizeEditor()\n        self.assertEqual(widget.parameters(),\n                         {\"multinomial_treatment\": Continuize.Indicators})\n\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Continuize)\n        self.assertEqual(p.multinomial_treatment, Continuize.Indicators)\n\n        widget.setParameters(\n            {\"multinomial_treatment\": Continuize.FrequentAsBase})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Continuize)\n        self.assertEqual(p.multinomial_treatment, Continuize.FrequentAsBase)\n\n\nclass TestImputeEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.ImputeEditor()\n        self.assertEqual(widget.parameters(),\n                         {\"method\": owpreprocess.ImputeEditor.Average})\n        widget.setParameters(\n            {\"method\": owpreprocess.ImputeEditor.Average}\n        )\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Impute)\n        self.assertIsInstance(p.method, impute.Average)\n\n\nclass TestFeatureSelectEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.FeatureSelectEditor()\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, fss.SelectBestFeatures)\n        self.assertIsInstance(p.method, score.InfoGain)\n        self.assertEqual(p.k, 10)\n\n        widget.setParameters({\"k\": 9999})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, fss.SelectBestFeatures)\n        self.assertEqual(p.k, 9999)\n\n\nclass TestRandomFeatureSelectEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.RandomFeatureSelectEditor()\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, fss.SelectRandomFeatures)\n        self.assertEqual(p.k, 10)\n\n        widget.setParameters({\"strategy\": owpreprocess.RandomFeatureSelectEditor.Fixed,\n                              \"k\": 9999})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, fss.SelectRandomFeatures)\n        self.assertEqual(p.k, 9999)\n\n        widget.setParameters(\n            {\"strategy\": owpreprocess.RandomFeatureSelectEditor.Percentage,\n             \"p\": 25})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, fss.SelectRandomFeatures)\n        self.assertEqual(p.k, 0.25)\n\n\nclass TestRandomizeEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.Randomize()\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Randomize)\n        self.assertEqual(p.rand_type, Randomize.RandomizeClasses)\n\n        widget.setParameters({\"rand_type\": Randomize.RandomizeAttributes})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, Randomize)\n        self.assertEqual(p.rand_type, Randomize.RandomizeAttributes)\n\n\nclass TestPCAEditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.PCA()\n        self.assertEqual(widget.parameters(),\n                         {\"n_components\": 10})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, ProjectPCA)\n        self.assertEqual(p.n_components, 10)\n\n        widget.setParameters({\"n_components\": 5})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, ProjectPCA)\n        self.assertEqual(p.n_components, 5)\n\n\nclass TestCUREditor(WidgetTest):\n    def test_editor(self):\n        widget = owpreprocess.CUR()\n        self.assertEqual(widget.parameters(),\n                         {\"rank\": 10, \"max_error\": 1})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, ProjectCUR)\n        self.assertEqual(p.rank, 10)\n        self.assertEqual(p.max_error, 1)\n\n        widget.setParameters({\"rank\": 9999})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, ProjectCUR)\n        self.assertEqual(p.rank, 9999)\n\n        widget.setParameters({\"rank\": 5, \"max_error\": 0.5})\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, ProjectCUR)\n        self.assertEqual(p.rank, 5)\n        self.assertEqual(p.max_error, 0.5)\n\nclass TestRemoveSparseEditor(WidgetTest):\n\n    def test_editor(self):\n        widget = owpreprocess.RemoveSparseEditor()\n        self.assertEqual(\n            widget.parameters(),\n            dict(fixedThresh=50, percThresh=5, filter0=True,\n                 useFixedThreshold=False))\n\n        p = widget.createinstance(widget.parameters())\n        widget.filterSettingsClicked()\n        self.assertTrue(widget.percSpin.isEnabled())\n        self.assertFalse(widget.fixedSpin.isEnabled())\n        self.assertIsInstance(p, RemoveSparse)\n        self.assertEqual(p.filter0, True)\n        self.assertEqual(p.threshold, 0.05)\n\n        widget.setParameters(\n            dict(useFixedThreshold=True, fixedThresh=30, filter0=False))\n        p = widget.createinstance(widget.parameters())\n        self.assertIsInstance(p, RemoveSparse)\n        self.assertEqual(p.threshold, 30)\n        self.assertFalse(p.filter0)\n"
    ],
    "test_file_names": [
        "test_1.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Orange installation attempts...\nAttempting installation with Python 3.7...\nrunning build_ext\nrunning build_src\nbuild_src\nbuilding extension \"Orange.classification._simple_tree\" sources\nbuilding extension \"Orange.classification._tree_scorers\" sources\nbuilding extension \"Orange.data._valuecount\" sources\nbuilding extension \"Orange.data._contingency\" sources\nbuilding extension \"Orange.data._io\" sources\nbuilding extension \"Orange.data._variable\" sources\nbuilding extension \"Orange.distance._distance\" sources\nbuilding extension \"Orange.preprocess._discretize\" sources\nbuilding extension \"Orange.preprocess._relieff\" sources\nbuilding extension \"Orange.projection._som\" sources\nbuilding extension \"Orange.widgets.utils._grid_density\" sources\nbuild_src: building npy-pkg config files\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmp_j8x607e/tmp\ncreating /tmp/tmp_j8x607e/tmp/tmp_j8x607e\ncompile options: '-MMD -MF /tmp/tmp_j8x607e/file.c.d -c'\ncc: /tmp/tmp_j8x607e/file.c\ncustomize UnixCCompiler using build_ext\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmpbtyv9yeb/tmp\ncreating /tmp/tmpbtyv9yeb/tmp/tmpbtyv9yeb\ncompile options: '-MMD -MF /tmp/tmpbtyv9yeb/file.c.d -c'\ncc: /tmp/tmpbtyv9yeb/file.c\ncustomize UnixCCompiler using build_ext\nbuilding 'Orange.classification._simple_tree' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange\ncreating build/temp.linux-x86_64-3.7/Orange/classification\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/classification/_simple_tree.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/classification/_simple_tree.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/classification/_simple_tree.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.classification._tree_scorers' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/classification/_tree_scorers.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/classification/_tree_scorers.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/classification/_tree_scorers.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._valuecount' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/data\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_valuecount.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_valuecount.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/data/_valuecount.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._contingency' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_contingency.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_contingency.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/data/_contingency.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._io' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_io.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_io.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/data/_io.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._variable' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/data/_variable.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_variable.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/data/_variable.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.distance._distance' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/distance\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/distance/_distance.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/distance/_distance.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/distance/_distance.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._discretize' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/preprocess\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/preprocess/_discretize.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/preprocess/_discretize.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/preprocess/_discretize.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._relieff' extension\ncompiling C++ sources\nC compiler: c++ -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\nc++: Orange/preprocess/_relieff.cpp\nc++ -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/preprocess/_relieff.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/preprocess/_relieff.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.projection._som' extension\ncompiling C sources\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/projection\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\ncc: Orange/projection/_som.c\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/projection/_som.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/projection/_som.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.widgets.utils._grid_density' extension\ncompiling C++ sources\nC compiler: c++ -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating build/temp.linux-x86_64-3.7/Orange/widgets\ncreating build/temp.linux-x86_64-3.7/Orange/widgets/utils\ncompile options: '-I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c'\nc++: Orange/widgets/utils/_grid_density.cpp\nc++ -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o Orange/widgets/utils/_grid_density.cpython-37m-x86_64-linux-gnu.so\nrunning develop\nrunning build_scripts\nrunning egg_info\nrunning build_src\nbuild_src\nbuilding extension \"Orange.classification._simple_tree\" sources\nbuilding extension \"Orange.classification._tree_scorers\" sources\nbuilding extension \"Orange.data._valuecount\" sources\nbuilding extension \"Orange.data._contingency\" sources\nbuilding extension \"Orange.data._io\" sources\nbuilding extension \"Orange.data._variable\" sources\nbuilding extension \"Orange.distance._distance\" sources\nbuilding extension \"Orange.preprocess._discretize\" sources\nbuilding extension \"Orange.preprocess._relieff\" sources\nbuilding extension \"Orange.projection._som\" sources\nbuilding extension \"Orange.widgets.utils._grid_density\" sources\nbuild_src: building npy-pkg config files\ncreating Orange3.egg-info\nwriting Orange3.egg-info/PKG-INFO\nwriting dependency_links to Orange3.egg-info/dependency_links.txt\nwriting entry points to Orange3.egg-info/entry_points.txt\nwriting requirements to Orange3.egg-info/requires.txt\nwriting top-level names to Orange3.egg-info/top_level.txt\nwriting manifest file 'Orange3.egg-info/SOURCES.txt'\nreading manifest file 'Orange3.egg-info/SOURCES.txt'\nreading manifest template 'MANIFEST.in'\nwarning: no files found matching 'README*' under directory 'Orange'\nwarning: no files found matching 'LICENSE' under directory 'Orange'\nwarning: no files found matching '*.basket' under directory 'Orange/datasets'\nwarning: no files found matching '*.js' under directory 'Orange/widgets'\nwarning: no files found matching '*.css' under directory 'Orange/widgets'\nwarning: no files found matching '*.html' under directory 'Orange/widgets'\nwarning: no files found matching 'COPYING'\nadding license file 'LICENSE'\nwriting manifest file 'Orange3.egg-info/SOURCES.txt'\nrunning build_ext\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmpfn0ptj4s/tmp\ncreating /tmp/tmpfn0ptj4s/tmp/tmpfn0ptj4s\ncompile options: '-MMD -MF /tmp/tmpfn0ptj4s/file.c.d -c'\ncc: /tmp/tmpfn0ptj4s/file.c\ncustomize UnixCCompiler using build_ext\ncustomize UnixCCompiler\nC compiler: cc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC\n\ncreating /tmp/tmp_kw3zv_j/tmp\ncreating /tmp/tmp_kw3zv_j/tmp/tmp_kw3zv_j\ncompile options: '-MMD -MF /tmp/tmp_kw3zv_j/file.c.d -c'\ncc: /tmp/tmp_kw3zv_j/file.c\ncustomize UnixCCompiler using build_ext\nCreating /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/Orange3.egg-link (link to .)\nAdding Orange3 3.25.0.dev0+7939273 to easy-install.pth file\nInstalling orange-canvas script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/bin\n\nInstalled /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a\nProcessing dependencies for Orange3==3.25.0.dev0+7939273\nSearching for XlsxWriter==3.2.0\nBest match: XlsxWriter 3.2.0\nAdding XlsxWriter 3.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for xlrd==2.0.1\nBest match: xlrd 2.0.1\nAdding xlrd 2.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for setuptools==59.8.0\nBest match: setuptools 59.8.0\nAdding setuptools 59.8.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for serverfiles==0.3.1\nBest match: serverfiles 0.3.1\nAdding serverfiles 0.3.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for scipy==1.7.3\nBest match: scipy 1.7.3\nAdding scipy 1.7.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for scikit-learn==1.0.2\nBest match: scikit-learn 1.0.2\nAdding scikit-learn 1.0.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for requests==2.31.0\nBest match: requests 2.31.0\nAdding requests 2.31.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for PyYAML==6.0.1\nBest match: PyYAML 6.0.1\nAdding PyYAML 6.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for python-louvain==0.16\nBest match: python-louvain 0.16\nAdding python-louvain 0.16 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for pyqtgraph==0.12.4\nBest match: pyqtgraph 0.12.4\nAdding pyqtgraph 0.12.4 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for pip==24.0\nBest match: pip 24.0\nAdding pip 24.0 to easy-install.pth file\nInstalling pip script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/bin\nInstalling pip3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/bin\nInstalling pip3.10 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for pandas==1.3.5\nBest match: pandas 1.3.5\nAdding pandas 1.3.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for orange-widget-base==4.22.0\nBest match: orange-widget-base 4.22.0\nAdding orange-widget-base 4.22.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for orange-canvas-core==0.1.35\nBest match: orange-canvas-core 0.1.35\nAdding orange-canvas-core 0.1.35 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for openpyxl==3.1.3\nBest match: openpyxl 3.1.3\nAdding openpyxl 3.1.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for openTSNE==1.0.1\nBest match: openTSNE 1.0.1\nAdding openTSNE 1.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for numpy==1.17.5\nBest match: numpy 1.17.5\nAdding numpy 1.17.5 to easy-install.pth file\nInstalling f2py script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/bin\nInstalling f2py3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/bin\nInstalling f2py3.7 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for networkx==2.6.3\nBest match: networkx 2.6.3\nAdding networkx 2.6.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for matplotlib==3.5.3\nBest match: matplotlib 3.5.3\nAdding matplotlib 3.5.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for keyrings.alt==4.2.0\nBest match: keyrings.alt 4.2.0\nAdding keyrings.alt 4.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for keyring==24.1.1\nBest match: keyring 24.1.1\nAdding keyring 24.1.1 to easy-install.pth file\nInstalling keyring script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for joblib==1.3.2\nBest match: joblib 1.3.2\nAdding joblib 1.3.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for docutils==0.20.1\nBest match: docutils 0.20.1\nAdding docutils 0.20.1 to easy-install.pth file\nInstalling docutils script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for chardet==5.2.0\nBest match: chardet 5.2.0\nAdding chardet 5.2.0 to easy-install.pth file\nInstalling chardetect script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for Bottleneck==1.4.0\nBest match: Bottleneck 1.4.0\nAdding Bottleneck 1.4.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for AnyQt==0.2.0\nBest match: AnyQt 0.2.0\nAdding AnyQt 0.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for threadpoolctl==3.1.0\nBest match: threadpoolctl 3.1.0\nAdding threadpoolctl 3.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for charset-normalizer==3.4.1\nBest match: charset-normalizer 3.4.1\nAdding charset-normalizer 3.4.1 to easy-install.pth file\nInstalling normalizer script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for urllib3==2.0.7\nBest match: urllib3 2.0.7\nAdding urllib3 2.0.7 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for certifi==2024.12.14\nBest match: certifi 2024.12.14\nAdding certifi 2024.12.14 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for idna==3.10\nBest match: idna 3.10\nAdding idna 3.10 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for pytz==2024.2\nBest match: pytz 2024.2\nAdding pytz 2024.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for python-dateutil==2.9.0.post0\nBest match: python-dateutil 2.9.0.post0\nAdding python-dateutil 2.9.0.post0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for typing-extensions==4.7.1\nBest match: typing-extensions 4.7.1\nAdding typing-extensions 4.7.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for commonmark==0.9.1\nBest match: commonmark 0.9.1\nAdding commonmark 0.9.1 to easy-install.pth file\nInstalling cmark script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for requests-cache==1.1.1\nBest match: requests-cache 1.1.1\nAdding requests-cache 1.1.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for qasync==0.25.0\nBest match: qasync 0.25.0\nAdding qasync 0.25.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for packaging==24.0\nBest match: packaging 24.0\nAdding packaging 24.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for dictdiffer==0.9.0\nBest match: dictdiffer 0.9.0\nAdding dictdiffer 0.9.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for importlib-metadata==6.7.0\nBest match: importlib-metadata 6.7.0\nAdding importlib-metadata 6.7.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for et-xmlfile==1.1.0\nBest match: et-xmlfile 1.1.0\nAdding et-xmlfile 1.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for pyparsing==3.1.4\nBest match: pyparsing 3.1.4\nAdding pyparsing 3.1.4 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for cycler==0.11.0\nBest match: cycler 0.11.0\nAdding cycler 0.11.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for kiwisolver==1.4.5\nBest match: kiwisolver 1.4.5\nAdding kiwisolver 1.4.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for fonttools==4.38.0\nBest match: fonttools 4.38.0\nAdding fonttools 4.38.0 to easy-install.pth file\nInstalling fonttools script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/bin\nInstalling pyftmerge script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/bin\nInstalling pyftsubset script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/bin\nInstalling ttx script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for Pillow==9.5.0\nBest match: Pillow 9.5.0\nAdding Pillow 9.5.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for jaraco.classes==3.2.3\nBest match: jaraco.classes 3.2.3\nAdding jaraco.classes 3.2.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for SecretStorage==3.3.3\nBest match: SecretStorage 3.3.3\nAdding SecretStorage 3.3.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for jeepney==0.8.0\nBest match: jeepney 0.8.0\nAdding jeepney 0.8.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for importlib-resources==5.12.0\nBest match: importlib-resources 5.12.0\nAdding importlib-resources 5.12.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for six==1.17.0\nBest match: six 1.17.0\nAdding six 1.17.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for attrs==24.2.0\nBest match: attrs 24.2.0\nAdding attrs 24.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for url-normalize==1.4.3\nBest match: url-normalize 1.4.3\nAdding url-normalize 1.4.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for cattrs==23.1.2\nBest match: cattrs 23.1.2\nAdding cattrs 23.1.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for platformdirs==4.0.0\nBest match: platformdirs 4.0.0\nAdding platformdirs 4.0.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for zipp==3.15.0\nBest match: zipp 3.15.0\nAdding zipp 3.15.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for more-itertools==9.1.0\nBest match: more-itertools 9.1.0\nAdding more-itertools 9.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for cryptography==44.0.0\nBest match: cryptography 44.0.0\nAdding cryptography 44.0.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for exceptiongroup==1.2.2\nBest match: exceptiongroup 1.2.2\nAdding exceptiongroup 1.2.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for cffi==1.15.1\nBest match: cffi 1.15.1\nAdding cffi 1.15.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nSearching for pycparser==2.21\nBest match: pycparser 2.21\nAdding pycparser 2.21 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages\nFinished processing dependencies for Orange3==3.25.0.dev0+7939273\nVerifying Orange installation...\n✅ Orange installation successful!\nSuccessfully installed orange using Python 3.7\n",
    "setup_res_stderr": "Using CPython 3.7.9\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 18 packages in 93ms\nPrepared 18 packages in 8ms\nInstalled 18 packages in 207ms\n + cython==0.29.37\n + exceptiongroup==1.2.2\n + importlib-metadata==6.7.0\n + iniconfig==2.0.0\n + numpy==1.17.5\n + packaging==24.0\n + pluggy==1.2.0\n + pyqt5==5.15.10\n + pyqt5-qt5==5.15.16\n + pyqt5-sip==12.13.0\n + pyqtwebengine==5.15.6\n + pyqtwebengine-qt5==5.15.16\n + pytest==7.4.4\n + setuptools==59.8.0\n + tomli==2.0.1\n + typing-extensions==4.7.1\n + wheel==0.42.0\n + zipp==3.15.0\nResolved 40 packages in 246ms\nInstalled 35 packages in 243ms\n + bottleneck==1.4.0\n + certifi==2024.12.14\n + cffi==1.15.1\n + chardet==5.2.0\n + charset-normalizer==3.4.1\n + cryptography==44.0.0\n + et-xmlfile==1.1.0\n + idna==3.10\n + importlib-resources==5.12.0\n + jaraco-classes==3.2.3\n + jeepney==0.8.0\n + joblib==1.3.2\n + keyring==24.1.1\n + keyrings-alt==4.2.0\n + more-itertools==9.1.0\n + networkx==2.6.3\n + openpyxl==3.1.3\n + opentsne==1.0.1\n + pandas==1.3.5\n + pip==24.0\n + pycparser==2.21\n + python-dateutil==2.9.0.post0\n + python-louvain==0.16\n + pytz==2024.2\n + pyyaml==6.0.1\n + requests==2.31.0\n + scikit-learn==1.0.2\n + scipy==1.7.3\n + secretstorage==3.3.3\n + serverfiles==0.3.1\n + six==1.17.0\n + threadpoolctl==3.1.0\n + urllib3==2.0.7\n + xlrd==2.0.1\n + xlsxwriter==3.2.0\nResolved 34 packages in 167ms\nInstalled 19 packages in 269ms\n + anyqt==0.2.0\n + attrs==24.2.0\n + cattrs==23.1.2\n + commonmark==0.9.1\n + cycler==0.11.0\n + dictdiffer==0.9.0\n + docutils==0.20.1\n + fonttools==4.38.0\n + kiwisolver==1.4.5\n + matplotlib==3.5.3\n + orange-canvas-core==0.1.35\n + orange-widget-base==4.22.0\n + pillow==9.5.0\n + platformdirs==4.0.0\n + pyparsing==3.1.4\n + pyqtgraph==0.12.4\n + qasync==0.25.0\n + requests-cache==1.1.1\n + url-normalize==1.4.3\nResolved 2 packages in 3ms\nInstalled 2 packages in 25ms\n + psycopg2==2.9.9\n + pymssql==2.3.2\nAudited 1 package in 2ms\nAudited 2 packages in 2ms\nOrange/classification/_simple_tree.c: In function ‘gain_ratio_c’:\nOrange/classification/_simple_tree.c:63:9: warning: implicit declaration of function ‘qsort_r’; did you mean ‘qsort’? [-Wimplicit-function-declaration]\n   63 |         qsort_r(base, nel, size, compar, thunk)\n      |         ^~~~~~~\nOrange/classification/_simple_tree.c:147:9: note: in expansion of macro ‘QSORT_R_FUNC’\n  147 |         QSORT_R_FUNC(examples, size, sizeof(struct Example), (void*) &compar_attr, compar_examples);\n      |         ^~~~~~~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/classification/_tree_scorers.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_2find_threshold_entropy’:\nOrange/classification/_tree_scorers.c:3115:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 3115 |   __pyx_t_7 = ((__pyx_v_N <= __pyx_v_min_leaf) != 0);\n      |                           ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_6find_threshold_MSE’:\nOrange/classification/_tree_scorers.c:4619:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 4619 |   __pyx_t_1 = ((__pyx_v_N <= __pyx_v_min_leaf) != 0);\n      |                           ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_8find_binarization_MSE’:\nOrange/classification/_tree_scorers.c:5396:43: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5396 |           for (__pyx_t_22 = 0; __pyx_t_22 < __pyx_t_21; __pyx_t_22+=1) {\n      |                                           ^\nOrange/classification/_tree_scorers.c:5517:39: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5517 |           __pyx_t_27 = ((__pyx_v_left >= __pyx_v_min_leaf) != 0);\n      |                                       ^~\nOrange/classification/_tree_scorers.c:5523:53: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5523 |           __pyx_t_27 = (((__pyx_v_N - __pyx_v_left) >= __pyx_v_min_leaf) != 0);\n      |                                                     ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_14compute_predictions_csr’:\nOrange/classification/_tree_scorers.c:7051:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7051 |         for (__pyx_t_11 = 0; __pyx_t_11 < __pyx_t_10; __pyx_t_11+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_16compute_predictions_csc’:\nOrange/classification/_tree_scorers.c:7741:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7741 |         for (__pyx_t_11 = 0; __pyx_t_11 < __pyx_t_10; __pyx_t_11+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c:7801:143: warning: comparison of integer expressions of different signedness: ‘__pyx_t_5numpy_int32_t’ {aka ‘int’} and ‘unsigned int’ [-Wsign-compare]\n 7801 |               __pyx_t_17 = (((*((__pyx_t_5numpy_int32_t *) ( /* dim=0 */ (__pyx_v_indices.data + __pyx_t_18 * __pyx_v_indices.strides[0]) ))) != __pyx_v_i) != 0);\n      |                                                                                                                                               ^~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_valuecount.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_contingency.c:598:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/data/_io.c: In function ‘__pyx_pf_6Orange_4data_3_io_sparse_read_float.constprop’:\nOrange/data/_io.c:3881:24: warning: ‘__pyx_v_decs’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3881 |           __pyx_v_decs = (__pyx_v_decs / 10.0);\n      |           ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:4070:24: warning: ‘__pyx_v_value’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4070 |           __pyx_t_10 = PyFloat_FromDouble(__pyx_v_value); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 221, __pyx_L5_error)\n      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:480:40: warning: ‘__pyx_v_attr_index’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n  480 |   #define PyInt_FromLong               PyLong_FromLong\n      |                                        ^~~~~~~~~~~~~~~\nOrange/data/_io.c:1301:7: note: ‘__pyx_v_attr_index’ was declared here\n 1301 |   int __pyx_v_attr_index;\n      |       ^~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:2935:25: warning: ‘__pyx_v_atomp’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 2935 |           __pyx_v_atomp = (__pyx_v_atomp + 1);\n      |           ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/distance/_distance.c:598:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_discretize.c:567:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_relieff.cpp:581:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/projection/_som.c:598:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_5update_hex’:\nOrange/projection/_som.c:3655:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3655 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:3654:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3654 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_7get_winners_sparse’:\nOrange/projection/_som.c:4547:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4547 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_38, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_39, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4114:7: note: ‘__pyx_v_win_y’ was declared here\n 4114 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4536:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4536 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_36, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_37, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4113:7: note: ‘__pyx_v_win_x’ was declared here\n 4113 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pf_6Orange_10projection_4_som_8update_sparse.constprop’:\nOrange/projection/_som.c:4746:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4746 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4745:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4745 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_3update’:\nOrange/projection/_som.c:3141:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3141 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_1get_winners’:\nOrange/projection/_som.c:2964:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 2964 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_27, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_28, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2665:7: note: ‘__pyx_v_win_y’ was declared here\n 2665 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:2953:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 2953 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_25, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_26, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2664:7: note: ‘__pyx_v_win_x’ was declared here\n 2664 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pf_6Orange_10projection_4_som_10update_sparse_hex.constprop’:\nOrange/projection/_som.c:5515:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5515 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:5514:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5514 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/setuptools/command/install.py:37: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n  setuptools.SetuptoolsDeprecationWarning,\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/setuptools/command/easy_install.py:159: EasyInstallDeprecationWarning: easy_install command is deprecated. Use build and pip and other standards-based tools.\n  EasyInstallDeprecationWarning,\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/setuptools/command/install.py:37: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n  setuptools.SetuptoolsDeprecationWarning,\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a/.venv/lib/python3.7/site-packages/setuptools/command/egg_info.py:628: SetuptoolsDeprecationWarning: Custom 'build_py' does not implement 'get_data_files_without_manifest'.\nPlease extend command classes from setuptools instead of distutils.\n  SetuptoolsDeprecationWarning\n",
    "new_commit_res_code": 0,
    "new_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a\ncollected 48 items\n\nr2e_tests/test_1.py sss..........sss.sss.sss.sss.sss.sss.sss.sss.sss     [100%]\n\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::TestOWPreprocess::test_data_column_nans\nPASSED r2e_tests/test_1.py::TestOWPreprocess::test_image_export\nPASSED r2e_tests/test_1.py::TestOWPreprocess::test_minimum_size\nPASSED r2e_tests/test_1.py::TestOWPreprocess::test_msg_base_class\nPASSED r2e_tests/test_1.py::TestOWPreprocess::test_normalize\nPASSED r2e_tests/test_1.py::TestOWPreprocess::test_randomize\nPASSED r2e_tests/test_1.py::TestOWPreprocess::test_remove_sparse\nPASSED r2e_tests/test_1.py::TestOWPreprocess::test_select_features\nPASSED r2e_tests/test_1.py::TestOWPreprocess::test_summary\nPASSED r2e_tests/test_1.py::TestDiscretizeEditor::test_editor\nPASSED r2e_tests/test_1.py::TestContinuizeEditor::test_editor\nPASSED r2e_tests/test_1.py::TestImputeEditor::test_editor\nPASSED r2e_tests/test_1.py::TestFeatureSelectEditor::test_editor\nPASSED r2e_tests/test_1.py::TestRandomFeatureSelectEditor::test_editor\nPASSED r2e_tests/test_1.py::TestRandomizeEditor::test_editor\nPASSED r2e_tests/test_1.py::TestPCAEditor::test_editor\nPASSED r2e_tests/test_1.py::TestCUREditor::test_editor\nPASSED r2e_tests/test_1.py::TestRemoveSparseEditor::test_editor\nSKIPPED [10] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:244: image exporting not tested as .widget was not set\nSKIPPED [10] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:238: minimum size not tested as .widget was not set\nSKIPPED [10] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:250: msg base class not tested as .widget was not set\n======================== 18 passed, 30 skipped in 3.41s ========================\nWidgets left: 0    Max widgets: 639 \n\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_7939273cd469f04d1e8d7dec7a3e4575af6f348a\ncollected 48 items\n\nr2e_tests/test_1.py sss........F.sss.sss.sss.sss.sss.sss.sss.sss.sss     [100%]\n\n=================================== FAILURES ===================================\n________________________ TestOWPreprocess.test_summary _________________________\n\nself = <r2e_tests.test_1.TestOWPreprocess testMethod=test_summary>\n\n    def test_summary(self):\n        \"\"\"Check if status bar is updated when data is received\"\"\"\n        data = Table(\"iris\")\n        input_sum = self.widget.info.set_input_summary = Mock()\n        output_sum = self.widget.info.set_output_summary = Mock()\n    \n        self.send_signal(self.widget.Inputs.data, data)\n>       input_sum.assert_called_with(int(StateInfo.format_number(len(data))))\n\nr2e_tests/test_1.py:150: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\n_mock_self = <Mock id='133979742971600'>, args = (150,), kwargs = {}\nexpected = 'mock(150)'\n\n    def assert_called_with(_mock_self, *args, **kwargs):\n        \"\"\"assert that the mock was called with the specified arguments.\n    \n        Raises an AssertionError if the args and keyword args passed in are\n        different to the last call to the mock.\"\"\"\n        self = _mock_self\n        if self.call_args is None:\n            expected = self._format_mock_call_signature(args, kwargs)\n>           raise AssertionError('Expected call: %s\\nNot called' % (expected,))\nE           AssertionError: Expected call: mock(150)\nE           Not called\n\n../../../../.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib/python3.7/unittest/mock.py:869: AssertionError\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::TestOWPreprocess::test_data_column_nans\nPASSED r2e_tests/test_1.py::TestOWPreprocess::test_image_export\nPASSED r2e_tests/test_1.py::TestOWPreprocess::test_minimum_size\nPASSED r2e_tests/test_1.py::TestOWPreprocess::test_msg_base_class\nPASSED r2e_tests/test_1.py::TestOWPreprocess::test_normalize\nPASSED r2e_tests/test_1.py::TestOWPreprocess::test_randomize\nPASSED r2e_tests/test_1.py::TestOWPreprocess::test_remove_sparse\nPASSED r2e_tests/test_1.py::TestOWPreprocess::test_select_features\nPASSED r2e_tests/test_1.py::TestDiscretizeEditor::test_editor\nPASSED r2e_tests/test_1.py::TestContinuizeEditor::test_editor\nPASSED r2e_tests/test_1.py::TestImputeEditor::test_editor\nPASSED r2e_tests/test_1.py::TestFeatureSelectEditor::test_editor\nPASSED r2e_tests/test_1.py::TestRandomFeatureSelectEditor::test_editor\nPASSED r2e_tests/test_1.py::TestRandomizeEditor::test_editor\nPASSED r2e_tests/test_1.py::TestPCAEditor::test_editor\nPASSED r2e_tests/test_1.py::TestCUREditor::test_editor\nPASSED r2e_tests/test_1.py::TestRemoveSparseEditor::test_editor\nSKIPPED [10] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:244: image exporting not tested as .widget was not set\nSKIPPED [10] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:238: minimum size not tested as .widget was not set\nSKIPPED [10] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:250: msg base class not tested as .widget was not set\nFAILED r2e_tests/test_1.py::TestOWPreprocess::test_summary - AssertionError: ...\n=================== 1 failed, 17 passed, 30 skipped in 2.85s ===================\nWidgets left: 0    Max widgets: 639 \n\n",
    "old_commit_res_stderr": ""
}
Orange/widgets/data/owpreprocess.py Orange/widgets/data/tests/test_owpreprocess.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Status bar does not display data summaries when input data is set

**Description:**
When input data is provided to the OWPreprocess widget, the status bar is expected to update with summaries of the input and output data. However, currently, these summaries are not displayed, which leads to tests failing to verify the correct behavior of the widget.

**Example Buggy Code:**
```python
def test_summary(self):
    data = Table("iris")
    self.send_signal(self.widget.Inputs.data, data)
    # Expected summaries are not updated in the status bar
```

**Expected Behavior:**
Upon receiving new input data, the status bar should display the number of input instances and the number of instances after preprocessing. For example, it should show something like "Input: 150 instances" and "Output: 150 instances" when data is successfully processed.

**Actual Behavior:**
The status bar remains without the expected summaries, making it unclear whether the data has been correctly received and processed. This absence of information causes related tests to fail, as they rely on these summaries to verify the widget's functionality.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### Orange/widgets/data/owpreprocess.py
from collections import OrderedDict
import pkg_resources

import numpy

from AnyQt.QtWidgets import (
    QWidget, QButtonGroup, QGroupBox, QRadioButton, QSlider,
    QDoubleSpinBox, QComboBox, QSpinBox, QListView, QLabel,
    QScrollArea, QVBoxLayout, QHBoxLayout, QFormLayout,
    QSizePolicy, QApplication, QCheckBox
)

from AnyQt.QtGui import (
    QIcon, QStandardItemModel, QStandardItem
)

from AnyQt.QtCore import (
    Qt, QEvent, QSize, QMimeData, QTimer
)

from AnyQt.QtCore import pyqtSignal as Signal, pyqtSlot as Slot

import Orange.data
from Orange import preprocess
from Orange.preprocess import Continuize, ProjectPCA, RemoveNaNRows, \
    ProjectCUR, Scale as _Scale, Randomize as _Randomize, RemoveSparse
from Orange.widgets import widget, gui
from Orange.widgets.settings import Setting
from Orange.widgets.utils.overlay import OverlayWidget
from Orange.widgets.utils.sql import check_sql_input
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.widget import Input, Output
from Orange.preprocess import Normalize
from Orange.widgets.data.utils.preprocess import (
    BaseEditor, blocked, StandardItemModel, DescriptionRole,
    ParametersRole, Controller, SequenceFlow
)

class _NoneDisc(preprocess.discretize.Discretization):
    """Discretize all variables into None.

    Used in combination with preprocess.Discretize to remove
    all discrete features from the domain.

    """
    _reprable_module = True

    def __call__(self, data, variable):
        return None


class DiscretizeEditor(BaseEditor):
    """
    Editor for preprocess.Discretize.
    """
    #: Discretize methods
    NoDisc, EqualWidth, EqualFreq, Drop, EntropyMDL = 0, 1, 2, 3, 4
    Discretizers = {
        NoDisc: (None, {}),
        EqualWidth: (preprocess.discretize.EqualWidth, {"n": 4}),
        EqualFreq:  (preprocess.discretize.EqualFreq, {"n": 4}),
        Drop: (_NoneDisc, {}),
        EntropyMDL: (preprocess.discretize.EntropyMDL, {"force": False})
    }
    Names = {
        NoDisc: "None",
        EqualWidth: "Equal width discretization",
        EqualFreq: "Equal frequency discretization",
        Drop: "Remove numeric features",
        EntropyMDL: "Entropy-MDL discretization"
    }

    def __init__(self, parent=None, **kwargs):
        BaseEditor.__init__(self, parent, **kwargs)
        self.__method = DiscretizeEditor.EqualFreq
        self.__nintervals = 4

        layout = QVBoxLayout()
        self.setLayout(layout)
        self.__group = group = QButtonGroup(self, exclusive=True)

        for method in [self.EntropyMDL, self.EqualFreq, self.EqualWidth,
                       self.Drop]:
            rb = QRadioButton(
                self, text=self.Names[method],
                checked=self.__method == method
            )
            layout.addWidget(rb)
            group.addButton(rb, method)

        group.buttonClicked.connect(self.__on_buttonClicked)

        self.__slbox = slbox = QGroupBox(
            title="Number of intervals (for equal width/frequency)",
            flat=True
        )
        slbox.setLayout(QHBoxLayout())
        self.__slider = slider = QSlider(
            orientation=Qt.Horizontal,
            minimum=2, maximum=10, value=self.__nintervals,
            enabled=self.__method in [self.EqualFreq, self.EqualWidth],
            pageStep=1, tickPosition=QSlider.TicksBelow
        )
        slider.valueChanged.connect(self.__on_valueChanged)
        slbox.layout().addWidget(slider)
        self.__slabel = slabel = QLabel()
        slbox.layout().addWidget(slabel)

        container = QHBoxLayout()
        container.setContentsMargins(13, 0, 0, 0)
        container.addWidget(slbox)
        self.layout().insertLayout(3, container)
        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)

    def setMethod(self, method):
        if self.__method != method:
            self.__method = method
            b = self.__group.button(method)
            b.setChecked(True)
            self.__slider.setEnabled(
                method in [self.EqualFreq, self.EqualWidth]
            )
            self.changed.emit()

    def method(self):
        return self.__method

    def intervals(self):
        return self.__nintervals

    def setIntervals(self, n):
        n = numpy.clip(n, self.__slider.minimum(), self.__slider.maximum())
        n = int(n)
        if self.__nintervals != n:
            self.__nintervals = n
            # blocking signals in order to differentiate between
            # changed by user (notified through __on_valueChanged) or
            # changed programmatically (this)
            with blocked(self.__slider):
                self.__slider.setValue(n)
                self.__slabel.setText(str(self.__slider.value()))
            self.changed.emit()

    def setParameters(self, params):
        method = params.get("method", self.EqualFreq)
        nintervals = params.get("n", 5)
        self.setMethod(method)
        if method in [self.EqualFreq, self.EqualWidth]:
            self.setIntervals(nintervals)

    def parameters(self):
        if self.__method in [self.EqualFreq, self.EqualWidth]:
            return {"method": self.__method, "n": self.__nintervals}
        else:
            return {"method": self.__method}

    def __on_buttonClicked(self):
        # on user 'method' button click
        method = self.__group.checkedId()
        if method != self.__method:
            self.setMethod(self.__group.checkedId())
            self.edited.emit()

    def __on_valueChanged(self):
        # on user n intervals slider change.
        self.__nintervals = self.__slider.value()
        self.changed.emit()
        self.edited.emit()
        self.__slabel.setText(str(self.__slider.value()))

    @staticmethod
    def createinstance(params):
        params = dict(params)
        method = params.pop("method", DiscretizeEditor.EqualFreq)
        method, defaults = DiscretizeEditor.Discretizers[method]

        if method is None:
            return None

        resolved = dict(defaults)
        # update only keys in defaults?
        resolved.update(params)
        return preprocess.Discretize(method(**params), remove_const=False)

    def __repr__(self):
        n_int = ", Number of intervals: {}".format(self.__nintervals) \
            if self.__method in [self.EqualFreq, self.EqualWidth] else ""
        return "{}{}".format(self.Names[self.__method], n_int)


class ContinuizeEditor(BaseEditor):
    _Type = type(Continuize.FirstAsBase)

    Continuizers = OrderedDict([
        (Continuize.FrequentAsBase, "Most frequent is base"),
        (Continuize.Indicators, "One feature per value"),
        (Continuize.RemoveMultinomial, "Remove non-binary features"),
        (Continuize.Remove, "Remove categorical features"),
        (Continuize.AsOrdinal, "Treat as ordinal"),
        (Continuize.AsNormalizedOrdinal, "Divide by number of values")
    ])

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.setLayout(QVBoxLayout())
        self.__treatment = Continuize.Indicators
        self.__group = group = QButtonGroup(exclusive=True)
        group.buttonClicked.connect(self.__on_buttonClicked)

        for treatment, text in ContinuizeEditor.Continuizers.items():
            rb = QRadioButton(
                text=text,
                checked=self.__treatment == treatment)
            group.addButton(rb, enum_to_index(ContinuizeEditor._Type,
                                              treatment))
            self.layout().addWidget(rb)

        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)

    def setTreatment(self, treatment):
        buttonid = enum_to_index(ContinuizeEditor._Type, treatment)
        b = self.__group.button(buttonid)
        if b is not None:
            b.setChecked(True)
            self.__treatment = treatment
            self.changed.emit()

    def treatment(self):
        return self.__treatment

    def setParameters(self, params):
        treatment = params.get("multinomial_treatment", Continuize.Indicators)
        self.setTreatment(treatment)

    def parameters(self):
        return {"multinomial_treatment": self.__treatment}

    def __on_buttonClicked(self):
        self.__treatment = index_to_enum(
            ContinuizeEditor._Type, self.__group.checkedId())
        self.changed.emit()
        self.edited.emit()

    @staticmethod
    def createinstance(params):
        params = dict(params)
        treatment = params.pop("multinomial_treatment", Continuize.Indicators)
        return Continuize(multinomial_treatment=treatment)

    def __repr__(self):
        return self.Continuizers[self.__treatment]

class RemoveSparseEditor(BaseEditor):

    options = ["missing", "zeros"]

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.fixedThresh = 50
        self.percThresh = 5
        self.useFixedThreshold = False
        self.filter0 = True
        self.setLayout(QVBoxLayout())

        self.layout().addWidget(QLabel("Remove features with too many"))
        options = ["missing values",
                   "zeros"]
        self.filter_buttons = QButtonGroup(exclusive=True)
        self.filter_buttons.buttonClicked.connect(self.filterByClicked)
        for idx, option, in enumerate(options):
            btn = QRadioButton(self, text=option, checked=idx == 0)
            self.filter_buttons.addButton(btn, id=idx)
            self.layout().addWidget(btn)

        self.layout().addSpacing(20)

        filter_settings = QGroupBox(title='Threshold:', flat=True)
        filter_settings.setLayout(QFormLayout())
        self.settings_buttons = QButtonGroup(exclusive=True)
        self.settings_buttons.buttonClicked.connect(self.filterSettingsClicked)

        btn_perc = QRadioButton(self, text='Percentage', checked=not self.useFixedThreshold)
        self.settings_buttons.addButton(btn_perc, id=0)
        self.percSpin = QSpinBox(minimum=0, maximum=100, value=self.percThresh,
                                 enabled=not self.useFixedThreshold)
        self.percSpin.valueChanged[int].connect(self.setPercThresh)
        self.percSpin.editingFinished.connect(self.edited)

        btn_fix = QRadioButton(self, text='Fixed', checked=self.useFixedThreshold)
        self.settings_buttons.addButton(btn_fix, id=1)
        self.fixedSpin = QSpinBox(minimum=0, maximum=1000000, value=self.fixedThresh,
                                  enabled=self.useFixedThreshold)
        self.fixedSpin.valueChanged[int].connect(self.setFixedThresh)
        self.fixedSpin.editingFinished.connect(self.edited)
        filter_settings.layout().addRow(btn_fix, self.fixedSpin)
        filter_settings.layout().addRow(btn_perc, self.percSpin)

        self.layout().addWidget(filter_settings)

    def filterSettingsClicked(self):
        self.setUseFixedThreshold(self.settings_buttons.checkedId())
        self.percSpin.setEnabled(not self.useFixedThreshold)
        self.fixedSpin.setEnabled(self.useFixedThreshold)
        self.edited.emit()

    def filterByClicked(self):
        self.setFilter0(self.filter_buttons.checkedId())

    def setFilter0(self, id_):
        if self.filter0 != id_:
            self.filter0 = id_
            self.edited.emit()

    def setFixedThresh(self, thresh):
        if self.fixedThresh != thresh:
            self.fixedThresh = thresh
            self.fixedSpin.setValue(thresh)
            self.edited.emit()

    def setPercThresh(self, thresh):
        if self.percThresh != thresh:
            self.percThresh = thresh
            self.percSpin.setValue(thresh)
            self.edited.emit()

    def setUseFixedThreshold(self, val):
        if self.useFixedThreshold != val:
            self.useFixedThreshold = val
            self.edited.emit()

    def parameters(self):
        return {'fixedThresh': self.fixedThresh,
                'percThresh' : self.percThresh,
                'useFixedThreshold' : self.useFixedThreshold,
                'filter0' : self.filter0}

    def setParameters(self, params):
        self.setPercThresh(params.get('percThresh', 5))
        self.setFixedThresh(params.get('fixedThresh', 50))
        self.setUseFixedThreshold(params.get('useFixedThreshold', False))
        self.setFilter0(params.get('filter0', True))

    @staticmethod
    def createinstance(params):
        params = dict(params)
        filter0 = params.pop('filter0', True)
        useFixedThreshold = params.pop('useFixedThreshold', True)
        if useFixedThreshold:
            threshold = params.pop('fixedThresh', 50)
        else:
            threshold = params.pop('percThresh', 5) / 100
        return RemoveSparse(threshold, filter0)

class ImputeEditor(BaseEditor):
    (NoImputation, Constant, Average,
     Model, Random, DropRows, DropColumns) = 0, 1, 2, 3, 4, 5, 6

    Imputers = {
        NoImputation: (None, {}),
        #         Constant: (None, {"value": 0})
        Average: (preprocess.impute.Average(), {}),
        #         Model: (preprocess.impute.Model, {}),
        Random: (preprocess.impute.Random(), {}),
        DropRows: (None, {})
    }
    Names = {
        NoImputation: "Don't impute.",
        Constant: "Replace with constant",
        Average: "Average/Most frequent",
        Model: "Model based imputer",
        Random: "Replace with random value",
        DropRows: "Remove rows with missing values.",
    }

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.setLayout(QVBoxLayout())

        self.__method = ImputeEditor.Average
        self.__group = group = QButtonGroup(self, exclusive=True)
        group.buttonClicked.connect(self.__on_buttonClicked)

        for methodid in [self.Average, self.Random, self.DropRows]:
            text = self.Names[methodid]
            rb = QRadioButton(text=text, checked=self.__method == methodid)
            group.addButton(rb, methodid)
            self.layout().addWidget(rb)

    def setMethod(self, method):
        b = self.__group.button(method)
        if b is not None:
            b.setChecked(True)
            self.__method = method
            self.changed.emit()

    def setParameters(self, params):
        method = params.get("method", ImputeEditor.Average)
        self.setMethod(method)

    def parameters(self):
        return {"method": self.__method}

    def __on_buttonClicked(self):
        self.__method = self.__group.checkedId()
        self.changed.emit()
        self.edited.emit()

    @staticmethod
    def createinstance(params):
        params = dict(params)
        method = params.pop("method", ImputeEditor.Average)
        if method == ImputeEditor.NoImputation:
            return None
        elif method == ImputeEditor.Average:
            return preprocess.Impute()
        elif method == ImputeEditor.Model:
            return preprocess.Impute(method=preprocess.impute.Model())
        elif method == ImputeEditor.DropRows:
            return RemoveNaNRows()
        elif method == ImputeEditor.DropColumns:
            return preprocess.RemoveNaNColumns()
        else:
            method, defaults = ImputeEditor.Imputers[method]
            defaults = dict(defaults)
            defaults.update(params)
            return preprocess.Impute(method=method)

    def __repr__(self):
        return self.Names[self.__method]


class UnivariateFeatureSelect(QWidget):
    changed = Signal()
    edited = Signal()

    #: Strategy
    Fixed, Proportion, FDR, FPR, FWE = 1, 2, 3, 4, 5

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.setLayout(QVBoxLayout())

        self.__scoreidx = 0
        self.__strategy = UnivariateFeatureSelect.Fixed
        self.__k = 10
        self.__p = 75.0

        box = QGroupBox(title="Score", flat=True)
        box.setLayout(QVBoxLayout())
        self.__cb = cb = QComboBox(self, )
        self.__cb.currentIndexChanged.connect(self.setScoreIndex)
        self.__cb.activated.connect(self.edited)
        box.layout().addWidget(cb)

        self.layout().addWidget(box)

        box = QGroupBox(title="Number of features", flat=True)
        self.__group = group = QButtonGroup(self, exclusive=True)
        self.__spins = {}

        form = QFormLayout()
        fixedrb = QRadioButton("Fixed:", checked=True)
        group.addButton(fixedrb, UnivariateFeatureSelect.Fixed)
        kspin = QSpinBox(
            minimum=1, maximum=1000000, value=self.__k,
            enabled=self.__strategy == UnivariateFeatureSelect.Fixed
        )
        kspin.valueChanged[int].connect(self.setK)
        kspin.editingFinished.connect(self.edited)
        self.__spins[UnivariateFeatureSelect.Fixed] = kspin
        form.addRow(fixedrb, kspin)

        percrb = QRadioButton("Proportion:")
        group.addButton(percrb, UnivariateFeatureSelect.Proportion)
        pspin = QDoubleSpinBox(
            minimum=1.0, maximum=100.0, singleStep=0.5,
            value=self.__p, suffix="%",
            enabled=self.__strategy == UnivariateFeatureSelect.Proportion
        )

        pspin.valueChanged[float].connect(self.setP)
        pspin.editingFinished.connect(self.edited)
        self.__spins[UnivariateFeatureSelect.Proportion] = pspin
        form.addRow(percrb, pspin)

#         form.addRow(QRadioButton("FDR"), QDoubleSpinBox())
#         form.addRow(QRadioButton("FPR"), QDoubleSpinBox())
#         form.addRow(QRadioButton("FWE"), QDoubleSpinBox())

        self.__group.buttonClicked.connect(self.__on_buttonClicked)
        box.setLayout(form)
        self.layout().addWidget(box)

    def setScoreIndex(self, scoreindex):
        if self.__scoreidx != scoreindex:
            self.__scoreidx = scoreindex
            self.__cb.setCurrentIndex(scoreindex)
            self.changed.emit()

    def scoreIndex(self):
        return self.__scoreidx

    def setStrategy(self, strategy):
        if self.__strategy != strategy:
            self.__strategy = strategy
            b = self.__group.button(strategy)
            b.setChecked(True)
            for st, rb in self.__spins.items():
                rb.setEnabled(st == strategy)
            self.changed.emit()

    def setK(self, k):
        if self.__k != k:
            self.__k = k
            spin = self.__spins[UnivariateFeatureSelect.Fixed]
            spin.setValue(k)
            if self.__strategy == UnivariateFeatureSelect.Fixed:
                self.changed.emit()

    def setP(self, p):
        if self.__p != p:
            self.__p = p
            spin = self.__spins[UnivariateFeatureSelect.Proportion]
            spin.setValue(p)
            if self.__strategy == UnivariateFeatureSelect.Proportion:
                self.changed.emit()

    def setItems(self, itemlist):
        for item in itemlist:
            self.__cb.addItem(item["text"])

    def __on_buttonClicked(self):
        strategy = self.__group.checkedId()
        self.setStrategy(strategy)
        self.edited.emit()

    def setParameters(self, params):
        score = params.get("score", 0)
        strategy = params.get("strategy", UnivariateFeatureSelect.Fixed)
        self.setScoreIndex(score)
        self.setStrategy(strategy)
        if strategy == UnivariateFeatureSelect.Fixed:
            self.setK(params.get("k", 10))
        else:
            self.setP(params.get("p", 75))

    def parameters(self):
        score = self.__scoreidx
        strategy = self.__strategy
        p = self.__p
        k = self.__k

        return {"score": score, "strategy": strategy, "p": p, "k": k}


class FeatureSelectEditor(BaseEditor):

    MEASURES = [
        ("Information Gain", preprocess.score.InfoGain),
        ("Gain ratio", preprocess.score.GainRatio),
        ("Gini index", preprocess.score.Gini),
        ("ReliefF", preprocess.score.ReliefF),
        ("Fast Correlation Based Filter", preprocess.score.FCBF),
        ("ANOVA", preprocess.score.ANOVA),
        ("Chi2", preprocess.score.Chi2),
        ("RReliefF", preprocess.score.RReliefF),
        ("Univariate Linear Regression", preprocess.score.UnivariateLinearRegression)
    ]

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setLayout(QVBoxLayout())
        self.layout().setContentsMargins(0, 0, 0, 0)

        self.__score = 0
        self.__selecionidx = 0

        self.__uni_fs = UnivariateFeatureSelect()
        self.__uni_fs.setItems(
            [{"text": "Information Gain", "tooltip": ""},
             {"text": "Gain Ratio"},
             {"text": "Gini Index"},
             {"text": "ReliefF"},
             {"text": "Fast Correlation Based Filter"},
             {"text": "ANOVA"},
             {"text": "Chi2"},
             {"text": "RReliefF"},
             {"text": "Univariate Linear Regression"}
            ]
        )
        self.layout().addWidget(self.__uni_fs)
        self.__uni_fs.changed.connect(self.changed)
        self.__uni_fs.edited.connect(self.edited)

    def setParameters(self, params):
        self.__uni_fs.setParameters(params)

    def parameters(self):
        return self.__uni_fs.parameters()

    @staticmethod
    def createinstance(params):
        params = dict(params)
        score = params.pop("score", 0)
        score = FeatureSelectEditor.MEASURES[score][1]
        strategy = params.get("strategy", UnivariateFeatureSelect.Fixed)
        k = params.get("k", 10)
        p = params.get("p", 75.0)
        if strategy == UnivariateFeatureSelect.Fixed:
            return preprocess.fss.SelectBestFeatures(score(), k=k)
        elif strategy == UnivariateFeatureSelect.Proportion:
            return preprocess.fss.SelectBestFeatures(score(), k=p / 100)
        else:
            raise NotImplementedError

    def __repr__(self):
        params = self.__uni_fs.parameters()
        return "Score: {}, Strategy (Fixed): {}".format(
            self.MEASURES[params["score"]][0], params["k"])

# TODO: Model based FS (random forest variable importance, ...), RFE
# Unsupervised (min variance, constant, ...)??

class RandomFeatureSelectEditor(BaseEditor):
    #: Strategy
    Fixed, Percentage = 1, 2

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.setLayout(QVBoxLayout())

        self.__strategy = RandomFeatureSelectEditor.Fixed
        self.__k = 10
        self.__p = 75.0

        box = QGroupBox(title="Number of features", flat=True)
        self.__group = group = QButtonGroup(self, exclusive=True)
        self.__spins = {}

        form = QFormLayout()
        fixedrb = QRadioButton("Fixed", checked=True)
        group.addButton(fixedrb, RandomFeatureSelectEditor.Fixed)
        kspin = QSpinBox(
            minimum=1, maximum=1000000, value=self.__k,
            enabled=self.__strategy == RandomFeatureSelectEditor.Fixed
        )
        kspin.valueChanged[int].connect(self.setK)
        kspin.editingFinished.connect(self.edited)
        self.__spins[RandomFeatureSelectEditor.Fixed] = kspin
        form.addRow(fixedrb, kspin)

        percrb = QRadioButton("Percentage")
        group.addButton(percrb, RandomFeatureSelectEditor.Percentage)
        pspin = QDoubleSpinBox(
            minimum=0.0, maximum=100.0, singleStep=0.5,
            value=self.__p, suffix="%",
            enabled=self.__strategy == RandomFeatureSelectEditor.Percentage
        )
        pspin.valueChanged[float].connect(self.setP)
        pspin.editingFinished.connect(self.edited)
        self.__spins[RandomFeatureSelectEditor.Percentage] = pspin
        form.addRow(percrb, pspin)

        self.__group.buttonClicked.connect(self.__on_buttonClicked)
        box.setLayout(form)
        self.layout().addWidget(box)

    def setStrategy(self, strategy):
        if self.__strategy != strategy:
            self.__strategy = strategy
            b = self.__group.button(strategy)
            b.setChecked(True)
            for st, rb in self.__spins.items():
                rb.setEnabled(st == strategy)
            self.changed.emit()

    def setK(self, k):
        if self.__k != k:
            self.__k = k
            spin = self.__spins[RandomFeatureSelectEditor.Fixed]
            spin.setValue(k)
            if self.__strategy == RandomFeatureSelectEditor.Fixed:
                self.changed.emit()

    def setP(self, p):
        if self.__p != p:
            self.__p = p
            spin = self.__spins[RandomFeatureSelectEditor.Percentage]
            spin.setValue(p)
            if self.__strategy == RandomFeatureSelectEditor.Percentage:
                self.changed.emit()

    def __on_buttonClicked(self):
        strategy = self.__group.checkedId()
        self.setStrategy(strategy)
        self.edited.emit()

    def setParameters(self, params):
        strategy = params.get("strategy", RandomFeatureSelectEditor.Fixed)
        self.setStrategy(strategy)
        if strategy == RandomFeatureSelectEditor.Fixed:
            self.setK(params.get("k", 10))
        else:
            self.setP(params.get("p", 75.0))

    def parameters(self):
        strategy = self.__strategy
        p = self.__p
        k = self.__k

        return {"strategy": strategy, "p": p, "k": k}

    @staticmethod
    def createinstance(params):
        params = dict(params)
        strategy = params.get("strategy", RandomFeatureSelectEditor.Fixed)
        k = params.get("k", 10)
        p = params.get("p", 75.0)
        if strategy == RandomFeatureSelectEditor.Fixed:
            return preprocess.fss.SelectRandomFeatures(k=k)
        elif strategy == RandomFeatureSelectEditor.Percentage:
            return preprocess.fss.SelectRandomFeatures(k=p/100)
        else:
            # further implementations
            raise NotImplementedError


def index_to_enum(enum, i):
    """Enums, by default, are not int-comparable, so use an ad-hoc mapping of
    int to enum value at that position"""
    return list(enum)[i]


def enum_to_index(enum, key):
    """Enums, by default, are not int-comparable, so use an ad-hoc mapping of
    enum key to its int position"""
    return list(enum).index(key)

class Scale(BaseEditor):
    CenterByMean, ScaleBySD, NormalizeBySD, NormalizeBySpan_ZeroBased, \
        NormalizeSpan_NonZeroBased = 0, 1, 2, 3, 4

    Names = {
        NormalizeBySD: "Standardize to μ=0, σ²=1",
        CenterByMean: "Center to μ=0",
        ScaleBySD: "Scale to σ²=1",
        NormalizeSpan_NonZeroBased: "Normalize to interval [-1, 1]",
        NormalizeBySpan_ZeroBased: "Normalize to interval [0, 1]",
    }

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.setLayout(QVBoxLayout())

        self.__method = Scale.NormalizeBySD
        self.__group = group = QButtonGroup(self, exclusive=True)
        group.buttonClicked.connect(self.__on_buttonClicked)

        for methodid in [self.NormalizeBySD, self.CenterByMean, self.ScaleBySD,
                         self.NormalizeSpan_NonZeroBased,
                         self.NormalizeBySpan_ZeroBased]:
            text = self.Names[methodid]
            rb = QRadioButton(text=text, checked=self.__method == methodid)
            group.addButton(rb, methodid)
            self.layout().addWidget(rb)

    def setMethod(self, method):
        b = self.__group.button(method)
        if b is not None:
            b.setChecked(True)
            self.__method = method
            self.changed.emit()

    def setParameters(self, params):
        method = params.get("method", Scale.NormalizeBySD)
        self.setMethod(method)

    def parameters(self):
        return {"method": self.__method}

    def __on_buttonClicked(self):
        self.__method = self.__group.checkedId()
        self.changed.emit()
        self.edited.emit()

    @staticmethod
    def createinstance(params):
        method = params.get("method", Scale.NormalizeBySD)
        if method == Scale.CenterByMean:
            return _Scale(_Scale.CenteringType.Mean,
                          _Scale.ScalingType.NoScaling)
        elif method == Scale.ScaleBySD:
            return _Scale(_Scale.CenteringType.NoCentering,
                          _Scale.ScalingType.Std)
        elif method == Scale.NormalizeBySD:
            return Normalize(norm_type=Normalize.NormalizeBySD)
        elif method == Scale.NormalizeBySpan_ZeroBased:
            return Normalize(norm_type=Normalize.NormalizeBySpan)
        else:  # method == Scale.NormalizeSpan_NonZeroBased
            return Normalize(norm_type=Normalize.NormalizeBySpan,
                             zero_based=False)

    def __repr__(self):
        return self.Names[self.__method]


class Randomize(BaseEditor):
    RandomizeClasses, RandomizeAttributes, RandomizeMetas = _Randomize.Type

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.setLayout(QVBoxLayout())

        form = QFormLayout()
        self.__rand_type_cb = QComboBox()
        self.__rand_type_cb.addItems(["Classes",
                                      "Features",
                                      "Meta data"])

        self.__rand_type_cb.currentIndexChanged.connect(self.changed)
        self.__rand_type_cb.activated.connect(self.edited)

        self.__rand_seed_ch = QCheckBox()
        self.__rand_seed_ch.clicked.connect(self.edited)

        form.addRow("Randomize:", self.__rand_type_cb)
        form.addRow("Replicable shuffling:", self.__rand_seed_ch)
        self.layout().addLayout(form)

    def setParameters(self, params):
        rand_type = params.get("rand_type", Randomize.RandomizeClasses)
        self.__rand_type_cb.setCurrentIndex(
            enum_to_index(_Randomize.Type, rand_type))
        self.__rand_seed_ch.setChecked(params.get("rand_seed", 1) or 0)

    def parameters(self):
        return {"rand_type": index_to_enum(_Randomize.Type,
                                           self.__rand_type_cb.currentIndex()),
                "rand_seed": 1 if self.__rand_seed_ch.isChecked() else None}

    @staticmethod
    def createinstance(params):
        rand_type = params.get("rand_type", Randomize.RandomizeClasses)
        rand_seed = params.get("rand_seed", 1)
        return _Randomize(rand_type=rand_type, rand_seed=rand_seed)

    def __repr__(self):
        return "{}, {}".format(self.__rand_type_cb.currentText(),
                               "Replicable" if self.__rand_seed_ch.isChecked()
                               else "Not replicable")


class PCA(BaseEditor):

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.setLayout(QVBoxLayout())

        self.n_components = 10

        form = QFormLayout()
        self.cspin = QSpinBox(minimum=1, value=self.n_components)
        self.cspin.valueChanged[int].connect(self.setC)
        self.cspin.editingFinished.connect(self.edited)

        form.addRow("Components:", self.cspin)
        self.layout().addLayout(form)

    def setParameters(self, params):
        self.n_components = params.get("n_components", 10)

    def parameters(self):
        return {"n_components": self.n_components}

    def setC(self, n_components):
        if self.n_components != n_components:
            self.n_components = n_components
            self.cspin.setValue(n_components)
            self.changed.emit()

    @staticmethod
    def createinstance(params):
        n_components = params.get("n_components", 10)
        return ProjectPCA(n_components=n_components)

    def __repr__(self):
        return "Components: {}".format(self.cspin.value())


class CUR(BaseEditor):

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.setLayout(QVBoxLayout())

        self.rank = 10
        self.max_error = 1

        form = QFormLayout()
        self.rspin = QSpinBox(minimum=2, maximum=1000000, value=self.rank)
        self.rspin.valueChanged[int].connect(self.setR)
        self.rspin.editingFinished.connect(self.edited)
        self.espin = QDoubleSpinBox(
            minimum=0.1, maximum=100.0, singleStep=0.1,
            value=self.max_error)
        self.espin.valueChanged[float].connect(self.setE)
        self.espin.editingFinished.connect(self.edited)

        form.addRow("Rank:", self.rspin)
        form.addRow("Relative error:", self.espin)
        self.layout().addLayout(form)

    def setParameters(self, params):
        self.setR(params.get("rank", 10))
        self.setE(params.get("max_error", 1))

    def parameters(self):
        return {"rank": self.rank, "max_error": self.max_error}

    def setR(self, rank):
        if self.rank != rank:
            self.rank = rank
            self.rspin.setValue(rank)
            self.changed.emit()

    def setE(self, max_error):
        if self.max_error != max_error:
            self.max_error = max_error
            self.espin.setValue(max_error)
            self.changed.emit()

    @staticmethod
    def createinstance(params):
        rank = params.get("rank", 10)
        max_error = params.get("max_error", 1)
        return ProjectCUR(rank=rank, max_error=max_error)

    def __repr__(self):
        return "Rank: {}, Relative error: {}".format(self.rspin.value(),
                                                     self.espin.value())


# This is intended for future improvements.
# I.e. it should be possible to add/register preprocessor actions
# through entry points (for use by add-ons). Maybe it should be a
# general framework (this is not the only place where such
# functionality is desired (for instance in Orange v2.* Rank widget
# already defines its own entry point).
class Description:
    """
    A description of an action/function.
    """
    def __init__(self, title, icon=None, summary=None, input_=None, output=None,
                 requires=None, note=None, related=None, keywords=None,
                 helptopic=None):
        self.title = title
        self.icon = icon
        self.summary = summary
        self.input = input_
        self.output = output
        self.requires = requires
        self.note = note
        self.related = related
        self.keywords = keywords
        self.helptopic = helptopic


class PreprocessAction:
    def __init__(self, name, qualname, category, description, viewclass):
        self.name = name
        self.qualname = qualname
        self.category = category
        self.description = description
        self.viewclass = viewclass


def icon_path(basename):
    return pkg_resources.resource_filename(__name__, "icons/" + basename)


PREPROCESS_ACTIONS = [
    PreprocessAction(
        "Discretize", "orange.preprocess.discretize", "Discretization",
        Description("Discretize Continuous Variables",
                    icon_path("Discretize.svg")),
        DiscretizeEditor
    ),
    PreprocessAction(
        "Continuize", "orange.preprocess.continuize", "Continuization",
        Description("Continuize Discrete Variables",
                    icon_path("Continuize.svg")),
        ContinuizeEditor
    ),
    PreprocessAction(
        "Impute", "orange.preprocess.impute", "Impute",
        Description("Impute Missing Values",
                    icon_path("Impute.svg")),
        ImputeEditor
    ),
    PreprocessAction(
        "Feature Selection", "orange.preprocess.fss", "Feature Selection",
        Description("Select Relevant Features",
                    icon_path("SelectColumns.svg")),
        FeatureSelectEditor
    ),
    PreprocessAction(
        "Random Feature Selection", "orange.preprocess.randomfss",
        "Random Feature Selection",
        Description("Select Random Features",
                    icon_path("SelectColumnsRandom.svg")),
        RandomFeatureSelectEditor
    ),
    PreprocessAction(
        "Normalize", "orange.preprocess.scale", "Scale",
        Description("Normalize Features",
                    icon_path("Normalize.svg")),
        Scale
    ),
    PreprocessAction(
        "Randomize", "orange.preprocess.randomize", "Randomization",
        Description("Randomize",
                    icon_path("Random.svg")),
        Randomize
    ),
    PreprocessAction(
        "Remove Sparse", "orange.preprocess.remove_sparse", "Feature Selection",
        Description("Remove Sparse Features",
                    icon_path("PurgeDomain.svg")),
        RemoveSparseEditor
    ),
    PreprocessAction(
        "PCA", "orange.preprocess.pca", "PCA",
        Description("Principal Component Analysis",
                    icon_path("PCA.svg")),
        PCA
    ),
    PreprocessAction(
        "CUR", "orange.preprocess.cur", "CUR",
        Description("CUR Matrix Decomposition",
                    icon_path("SelectColumns.svg")),
        CUR
    )
]


# TODO: Extend with entry points here
# PREPROCESS_ACTIONS += iter_entry_points("Orange.widgets.data.owpreprocess")

# ####
# The actual owwidget
# ####

# Note:
# The preprocessors are drag/dropped onto a sequence widget, where
# they can be reordered/removed/edited.
#
# Model <-> Adapter/Controller <-> View
#
# * `Model`: the current constructed preprocessor model.
# * the source (of drag/drop) is an item model displayed in a list
#   view (source list).
# * the drag/drop is controlled by the controller/adapter,


class OWPreprocess(widget.OWWidget):
    name = "Preprocess"
    description = "Construct a data preprocessing pipeline."
    icon = "icons/Preprocess.svg"
    priority = 2105
    keywords = ["process"]

    settings_version = 2

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

    class Outputs:
        preprocessor = Output("Preprocessor", preprocess.preprocess.Preprocess, dynamic=False)
        preprocessed_data = Output("Preprocessed Data", Orange.data.Table)

    storedsettings = Setting({})
    autocommit = Setting(True)
    PREPROCESSORS = PREPROCESS_ACTIONS
    CONTROLLER = Controller

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

        self.data = None
        self._invalidated = False

        # List of available preprocessors (DescriptionRole : Description)
        self.preprocessors = QStandardItemModel()

        def mimeData(indexlist):
            assert len(indexlist) == 1
            index = indexlist[0]
            qname = index.data(DescriptionRole).qualname
            m = QMimeData()
            m.setData("application/x-qwidget-ref", qname.encode("utf-8"))
            return m
        # TODO: Fix this (subclass even if just to pass a function
        # for mimeData delegate)
        self.preprocessors.mimeData = mimeData

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

        self.preprocessorsView = view = QListView(
            selectionMode=QListView.SingleSelection,
            dragEnabled=True,
            dragDropMode=QListView.DragOnly
        )
        view.setModel(self.preprocessors)
        view.activated.connect(self.__activated)

        box.layout().addWidget(view)

        ####
        self._qname2ppdef = {ppdef.qualname: ppdef for
                             ppdef in self.PREPROCESSORS}

        # List of 'selected' preprocessors and their parameters.
        self.preprocessormodel = None

        self.flow_view = SequenceFlow()
        self.controler = self.CONTROLLER(self.flow_view, parent=self)

        self.overlay = OverlayWidget(self)
        self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents)
        self.overlay.setWidget(self.flow_view)
        self.overlay.setLayout(QVBoxLayout())
        self.overlay.layout().addWidget(
            QLabel("Drag items from the list on the left", wordWrap=True))

        self.scroll_area = QScrollArea(
            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn
        )
        self.scroll_area.viewport().setAcceptDrops(True)
        self.scroll_area.setWidget(self.flow_view)
        self.scroll_area.setWidgetResizable(True)
        self.mainArea.layout().addWidget(self.scroll_area)
        self.flow_view.installEventFilter(self)

        box = gui.vBox(self.controlArea, "Output")
        gui.auto_send(box, self, "autocommit", box=False)

        self._initialize()

    def _initialize(self):
        for pp_def in self.PREPROCESSORS:
            description = pp_def.description
            if description.icon:
                icon = QIcon(description.icon)
            else:
                icon = QIcon()
            item = QStandardItem(icon, description.title)
            item.setToolTip(description.summary or "")
            item.setData(pp_def, DescriptionRole)
            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable |
                          Qt.ItemIsDragEnabled)
            self.preprocessors.appendRow([item])

        model = self.load(self.storedsettings)

        self.set_model(model)

        if not model.rowCount():
            # enforce default width constraint if no preprocessors
            # are instantiated (if the model is not empty the constraints
            # will be triggered by LayoutRequest event on the `flow_view`)
            self.__update_size_constraint()

        self.apply()

    def load(self, saved):
        """Load a preprocessor list from a dict."""
        preprocessors = saved.get("preprocessors", [])
        model = StandardItemModel()

        def dropMimeData(data, action, row, _column, _parent):
            if data.hasFormat("application/x-qwidget-ref") and \
                    action == Qt.CopyAction:
                qname = bytes(data.data("application/x-qwidget-ref")).decode()

                ppdef = self._qname2ppdef[qname]
                item = QStandardItem(ppdef.description.title)
                item.setData({}, ParametersRole)
                item.setData(ppdef.description.title, Qt.DisplayRole)
                item.setData(ppdef, DescriptionRole)
                self.preprocessormodel.insertRow(row, [item])
                return True
            else:
                return False

        model.dropMimeData = dropMimeData

        for qualname, params in preprocessors:
            pp_def = self._qname2ppdef[qualname]
            description = pp_def.description
            item = QStandardItem(description.title)
            if description.icon:
                icon = QIcon(description.icon)
            else:
                icon = QIcon()
            item.setIcon(icon)
            item.setToolTip(description.summary)
            item.setData(pp_def, DescriptionRole)
            item.setData(params, ParametersRole)

            model.appendRow(item)
        return model

    @staticmethod
    def save(model):
        """Save the preprocessor list to a dict."""
        d = {"name": ""}
        preprocessors = []
        for i in range(model.rowCount()):
            item = model.item(i)
            pp_def = item.data(DescriptionRole)
            params = item.data(ParametersRole)
            preprocessors.append((pp_def.qualname, params))

        d["preprocessors"] = preprocessors
        return d


    def set_model(self, ppmodel):
        if self.preprocessormodel:
            self.preprocessormodel.dataChanged.disconnect(self.__on_modelchanged)
            self.preprocessormodel.rowsInserted.disconnect(self.__on_modelchanged)
            self.preprocessormodel.rowsRemoved.disconnect(self.__on_modelchanged)
            self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged)
            self.preprocessormodel.deleteLater()

        self.preprocessormodel = ppmodel
        self.controler.setModel(ppmodel)
        if ppmodel is not None:
            self.preprocessormodel.dataChanged.connect(self.__on_modelchanged)
            self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged)
            self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged)
            self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged)

        self.__update_overlay()

    def __update_overlay(self):
        if self.preprocessormodel is None or \
                self.preprocessormodel.rowCount() == 0:
            self.overlay.setWidget(self.flow_view)
            self.overlay.show()
        else:
            self.overlay.setWidget(None)
            self.overlay.hide()

    def __on_modelchanged(self):
        self.__update_overlay()
        self.commit()

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

    def handleNewSignals(self):
        self.apply()

    def __activated(self, index):
        item = self.preprocessors.itemFromIndex(index)
        action = item.data(DescriptionRole)
        item = QStandardItem()
        item.setData({}, ParametersRole)
        item.setData(action.description.title, Qt.DisplayRole)
        item.setData(action, DescriptionRole)
        self.preprocessormodel.appendRow([item])

    def buildpreproc(self):
        plist = []
        for i in range(self.preprocessormodel.rowCount()):
            item = self.preprocessormodel.item(i)
            desc = item.data(DescriptionRole)
            params = item.data(ParametersRole)

            if not isinstance(params, dict):
                params = {}

            create = desc.viewclass.createinstance
            plist.append(create(params))

        if len(plist) == 1:
            return plist[0]
        else:
            return preprocess.preprocess.PreprocessorList(plist)

    def apply(self):
        # Sync the model into storedsettings on every apply.
        self.storeSpecificSettings()
        preprocessor = self.buildpreproc()

        if self.data is not None:
            self.error()
            try:
                data = preprocessor(self.data)
            except (ValueError, ZeroDivisionError) as e:
                self.error(str(e))
                return
        else:
            data = None

        self.Outputs.preprocessor.send(preprocessor)
        self.Outputs.preprocessed_data.send(data)

    def commit(self):
        if not self._invalidated:
            self._invalidated = True
            QApplication.postEvent(self, QEvent(QEvent.User))

    def customEvent(self, event):
        if event.type() == QEvent.User and self._invalidated:
            self._invalidated = False
            self.apply()

    def eventFilter(self, receiver, event):
        if receiver is self.flow_view and event.type() == QEvent.LayoutRequest:
            QTimer.singleShot(0, self.__update_size_constraint)

        return super().eventFilter(receiver, event)

    def storeSpecificSettings(self):
        """Reimplemented."""
        self.storedsettings = self.save(self.preprocessormodel)
        super().storeSpecificSettings()

    def saveSettings(self):
        """Reimplemented."""
        self.storedsettings = self.save(self.preprocessormodel)
        super().saveSettings()

    @classmethod
    def migrate_settings(cls, settings, version):
        if version < 2:
            for action, params in settings["storedsettings"]["preprocessors"]:
                if action == "orange.preprocess.scale":
                    scale = center = None
                    if "center" in params:
                        center = params.pop("center").name
                    if "scale" in params:
                        scale = params.pop("scale").name
                    migratable = {
                        ("Mean", "NoScaling"): Scale.CenterByMean,
                        ("NoCentering", "Std"): Scale.ScaleBySD,
                        ("Mean", "Std"): Scale.NormalizeBySD,
                        ("NoCentering", "Span"): Scale.NormalizeBySpan_ZeroBased
                    }
                    params["method"] = \
                        migratable.get((center, scale), Scale.NormalizeBySD)

    def onDeleteWidget(self):
        self.data = None
        self.set_model(None)
        super().onDeleteWidget()

    @Slot()
    def __update_size_constraint(self):
        # Update minimum width constraint on the scroll area containing
        # the 'instantiated' preprocessor list (to avoid the horizontal
        # scroll bar).
        sh = self.flow_view.minimumSizeHint()
        scroll_width = self.scroll_area.verticalScrollBar().width()
        self.scroll_area.setMinimumWidth(
            min(max(sh.width() + scroll_width + 2, self.controlArea.width()),
                520))

    def sizeHint(self):
        sh = super().sizeHint()
        return sh.expandedTo(QSize(sh.width() + 300, 500))

    def send_report(self):
        pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w)
              for i, w in enumerate(self.controler.view.widgets())]
        if pp:
            self.report_items("Settings", pp)

if __name__ == "__main__":  # pragma: no cover
    WidgetPreview(OWPreprocess).run(Orange.data.Table("brown-selected"))

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
[ISSUE]
**Title:** Status bar does not display data summaries when input data is set

**Description:**
When input data is provided to the OWPreprocess widget, the status bar is expected to update with summaries of the input and output data. However, currently, these summaries are not displayed, which leads to tests failing to verify the correct behavior of the widget.

**Example Buggy Code:**
```python
def test_summary(self):
    data = Table("iris")
    self.send_signal(self.widget.Inputs.data, data)
    # Expected summaries are not updated in the status bar
```

**Expected Behavior:**
Upon receiving new input data, the status bar should display the number of input instances and the number of instances after preprocessing. For example, it should show something like "Input: 150 instances" and "Output: 150 instances" when data is successfully processed.

**Actual Behavior:**
The status bar remains without the expected summaries, making it unclear whether the data has been correctly received and processed. This absence of information causes related tests to fail, as they rely on these summaries to verify the widget's functionality.

[/ISSUE]
{
    "TestOWPreprocess.test_data_column_nans": "PASSED",
    "TestOWPreprocess.test_image_export": "PASSED",
    "TestOWPreprocess.test_minimum_size": "PASSED",
    "TestOWPreprocess.test_msg_base_class": "PASSED",
    "TestOWPreprocess.test_normalize": "PASSED",
    "TestOWPreprocess.test_randomize": "PASSED",
    "TestOWPreprocess.test_remove_sparse": "PASSED",
    "TestOWPreprocess.test_select_features": "PASSED",
    "TestOWPreprocess.test_summary": "PASSED",
    "TestDiscretizeEditor.test_editor": "PASSED",
    "TestContinuizeEditor.test_editor": "PASSED",
    "TestImputeEditor.test_editor": "PASSED",
    "TestFeatureSelectEditor.test_editor": "PASSED",
    "TestRandomFeatureSelectEditor.test_editor": "PASSED",
    "TestRandomizeEditor.test_editor": "PASSED",
    "TestPCAEditor.test_editor": "PASSED",
    "TestCUREditor.test_editor": "PASSED",
    "TestRemoveSparseEditor.test_editor": "PASSED"
}
diff --git a/Orange/widgets/data/owpreprocess.py b/Orange/widgets/data/owpreprocess.py
index bf776df70..238be791e 100644
--- a/Orange/widgets/data/owpreprocess.py
+++ b/Orange/widgets/data/owpreprocess.py
@@ -1144,6 +1144,9 @@ class OWPreprocess(widget.OWWidget):
         box = gui.vBox(self.controlArea, "Output")
         gui.auto_send(box, self, "autocommit", box=False)
 
+        self.info.set_input_summary(self.info.NoInput)
+        self.info.set_output_summary(self.info.NoOutput)
+
         self._initialize()
 
     def _initialize(self):
@@ -1261,6 +1264,10 @@ class OWPreprocess(widget.OWWidget):
     def set_data(self, data=None):
         """Set the input dataset."""
         self.data = data
+        if data is not None:
+            self.info.set_input_summary(len(data))
+        else:
+            self.info.set_input_summary(self.info.NoInput)
 
     def handleNewSignals(self):
         self.apply()
@@ -1304,8 +1311,10 @@ class OWPreprocess(widget.OWWidget):
             except (ValueError, ZeroDivisionError) as e:
                 self.error(str(e))
                 return
+            self.info.set_output_summary(len(data))
         else:
             data = None
+            self.info.set_output_summary(self.info.NoOutput)
 
         self.Outputs.preprocessor.send(preprocessor)
         self.Outputs.preprocessed_data.send(data)

{"Orange/widgets/data/owpreprocess.py": "from collections import OrderedDict\nimport pkg_resources\n\nimport numpy\n\nfrom AnyQt.QtWidgets import (\n    QWidget, QButtonGroup, QGroupBox, QRadioButton, QSlider,\n    QDoubleSpinBox, QComboBox, QSpinBox, QListView, QLabel,\n    QScrollArea, QVBoxLayout, QHBoxLayout, QFormLayout,\n    QSizePolicy, QApplication, QCheckBox\n)\n\nfrom AnyQt.QtGui import (\n    QIcon, QStandardItemModel, QStandardItem\n)\n\nfrom AnyQt.QtCore import (\n    Qt, QEvent, QSize, QMimeData, QTimer\n)\n\nfrom AnyQt.QtCore import pyqtSignal as Signal, pyqtSlot as Slot\n\nimport Orange.data\nfrom Orange import preprocess\nfrom Orange.preprocess import Continuize, ProjectPCA, RemoveNaNRows, \\\n    ProjectCUR, Scale as _Scale, Randomize as _Randomize, RemoveSparse\nfrom Orange.widgets import widget, gui\nfrom Orange.widgets.settings import Setting\nfrom Orange.widgets.utils.overlay import OverlayWidget\nfrom Orange.widgets.utils.sql import check_sql_input\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.widget import Input, Output\nfrom Orange.preprocess import Normalize\nfrom Orange.widgets.data.utils.preprocess import (\n    BaseEditor, blocked, StandardItemModel, DescriptionRole,\n    ParametersRole, Controller, SequenceFlow\n)\n\nclass _NoneDisc(preprocess.discretize.Discretization):\n    \"\"\"Discretize all variables into None.\n\n    Used in combination with preprocess.Discretize to remove\n    all discrete features from the domain.\n\n    \"\"\"\n    _reprable_module = True\n\n    def __call__(self, data, variable):\n        return None\n\n\nclass DiscretizeEditor(BaseEditor):\n    \"\"\"\n    Editor for preprocess.Discretize.\n    \"\"\"\n    #: Discretize methods\n    NoDisc, EqualWidth, EqualFreq, Drop, EntropyMDL = 0, 1, 2, 3, 4\n    Discretizers = {\n        NoDisc: (None, {}),\n        EqualWidth: (preprocess.discretize.EqualWidth, {\"n\": 4}),\n        EqualFreq:  (preprocess.discretize.EqualFreq, {\"n\": 4}),\n        Drop: (_NoneDisc, {}),\n        EntropyMDL: (preprocess.discretize.EntropyMDL, {\"force\": False})\n    }\n    Names = {\n        NoDisc: \"None\",\n        EqualWidth: \"Equal width discretization\",\n        EqualFreq: \"Equal frequency discretization\",\n        Drop: \"Remove numeric features\",\n        EntropyMDL: \"Entropy-MDL discretization\"\n    }\n\n    def __init__(self, parent=None, **kwargs):\n        BaseEditor.__init__(self, parent, **kwargs)\n        self.__method = DiscretizeEditor.EqualFreq\n        self.__nintervals = 4\n\n        layout = QVBoxLayout()\n        self.setLayout(layout)\n        self.__group = group = QButtonGroup(self, exclusive=True)\n\n        for method in [self.EntropyMDL, self.EqualFreq, self.EqualWidth,\n                       self.Drop]:\n            rb = QRadioButton(\n                self, text=self.Names[method],\n                checked=self.__method == method\n            )\n            layout.addWidget(rb)\n            group.addButton(rb, method)\n\n        group.buttonClicked.connect(self.__on_buttonClicked)\n\n        self.__slbox = slbox = QGroupBox(\n            title=\"Number of intervals (for equal width/frequency)\",\n            flat=True\n        )\n        slbox.setLayout(QHBoxLayout())\n        self.__slider = slider = QSlider(\n            orientation=Qt.Horizontal,\n            minimum=2, maximum=10, value=self.__nintervals,\n            enabled=self.__method in [self.EqualFreq, self.EqualWidth],\n            pageStep=1, tickPosition=QSlider.TicksBelow\n        )\n        slider.valueChanged.connect(self.__on_valueChanged)\n        slbox.layout().addWidget(slider)\n        self.__slabel = slabel = QLabel()\n        slbox.layout().addWidget(slabel)\n\n        container = QHBoxLayout()\n        container.setContentsMargins(13, 0, 0, 0)\n        container.addWidget(slbox)\n        self.layout().insertLayout(3, container)\n        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)\n\n    def setMethod(self, method):\n        if self.__method != method:\n            self.__method = method\n            b = self.__group.button(method)\n            b.setChecked(True)\n            self.__slider.setEnabled(\n                method in [self.EqualFreq, self.EqualWidth]\n            )\n            self.changed.emit()\n\n    def method(self):\n        return self.__method\n\n    def intervals(self):\n        return self.__nintervals\n\n    def setIntervals(self, n):\n        n = numpy.clip(n, self.__slider.minimum(), self.__slider.maximum())\n        n = int(n)\n        if self.__nintervals != n:\n            self.__nintervals = n\n            # blocking signals in order to differentiate between\n            # changed by user (notified through __on_valueChanged) or\n            # changed programmatically (this)\n            with blocked(self.__slider):\n                self.__slider.setValue(n)\n                self.__slabel.setText(str(self.__slider.value()))\n            self.changed.emit()\n\n    def setParameters(self, params):\n        method = params.get(\"method\", self.EqualFreq)\n        nintervals = params.get(\"n\", 5)\n        self.setMethod(method)\n        if method in [self.EqualFreq, self.EqualWidth]:\n            self.setIntervals(nintervals)\n\n    def parameters(self):\n        if self.__method in [self.EqualFreq, self.EqualWidth]:\n            return {\"method\": self.__method, \"n\": self.__nintervals}\n        else:\n            return {\"method\": self.__method}\n\n    def __on_buttonClicked(self):\n        # on user 'method' button click\n        method = self.__group.checkedId()\n        if method != self.__method:\n            self.setMethod(self.__group.checkedId())\n            self.edited.emit()\n\n    def __on_valueChanged(self):\n        # on user n intervals slider change.\n        self.__nintervals = self.__slider.value()\n        self.changed.emit()\n        self.edited.emit()\n        self.__slabel.setText(str(self.__slider.value()))\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        method = params.pop(\"method\", DiscretizeEditor.EqualFreq)\n        method, defaults = DiscretizeEditor.Discretizers[method]\n\n        if method is None:\n            return None\n\n        resolved = dict(defaults)\n        # update only keys in defaults?\n        resolved.update(params)\n        return preprocess.Discretize(method(**params), remove_const=False)\n\n    def __repr__(self):\n        n_int = \", Number of intervals: {}\".format(self.__nintervals) \\\n            if self.__method in [self.EqualFreq, self.EqualWidth] else \"\"\n        return \"{}{}\".format(self.Names[self.__method], n_int)\n\n\nclass ContinuizeEditor(BaseEditor):\n    _Type = type(Continuize.FirstAsBase)\n\n    Continuizers = OrderedDict([\n        (Continuize.FrequentAsBase, \"Most frequent is base\"),\n        (Continuize.Indicators, \"One feature per value\"),\n        (Continuize.RemoveMultinomial, \"Remove non-binary features\"),\n        (Continuize.Remove, \"Remove categorical features\"),\n        (Continuize.AsOrdinal, \"Treat as ordinal\"),\n        (Continuize.AsNormalizedOrdinal, \"Divide by number of values\")\n    ])\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n        self.__treatment = Continuize.Indicators\n        self.__group = group = QButtonGroup(exclusive=True)\n        group.buttonClicked.connect(self.__on_buttonClicked)\n\n        for treatment, text in ContinuizeEditor.Continuizers.items():\n            rb = QRadioButton(\n                text=text,\n                checked=self.__treatment == treatment)\n            group.addButton(rb, enum_to_index(ContinuizeEditor._Type,\n                                              treatment))\n            self.layout().addWidget(rb)\n\n        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)\n\n    def setTreatment(self, treatment):\n        buttonid = enum_to_index(ContinuizeEditor._Type, treatment)\n        b = self.__group.button(buttonid)\n        if b is not None:\n            b.setChecked(True)\n            self.__treatment = treatment\n            self.changed.emit()\n\n    def treatment(self):\n        return self.__treatment\n\n    def setParameters(self, params):\n        treatment = params.get(\"multinomial_treatment\", Continuize.Indicators)\n        self.setTreatment(treatment)\n\n    def parameters(self):\n        return {\"multinomial_treatment\": self.__treatment}\n\n    def __on_buttonClicked(self):\n        self.__treatment = index_to_enum(\n            ContinuizeEditor._Type, self.__group.checkedId())\n        self.changed.emit()\n        self.edited.emit()\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        treatment = params.pop(\"multinomial_treatment\", Continuize.Indicators)\n        return Continuize(multinomial_treatment=treatment)\n\n    def __repr__(self):\n        return self.Continuizers[self.__treatment]\n\nclass RemoveSparseEditor(BaseEditor):\n\n    options = [\"missing\", \"zeros\"]\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.fixedThresh = 50\n        self.percThresh = 5\n        self.useFixedThreshold = False\n        self.filter0 = True\n        self.setLayout(QVBoxLayout())\n\n        self.layout().addWidget(QLabel(\"Remove features with too many\"))\n        options = [\"missing values\",\n                   \"zeros\"]\n        self.filter_buttons = QButtonGroup(exclusive=True)\n        self.filter_buttons.buttonClicked.connect(self.filterByClicked)\n        for idx, option, in enumerate(options):\n            btn = QRadioButton(self, text=option, checked=idx == 0)\n            self.filter_buttons.addButton(btn, id=idx)\n            self.layout().addWidget(btn)\n\n        self.layout().addSpacing(20)\n\n        filter_settings = QGroupBox(title='Threshold:', flat=True)\n        filter_settings.setLayout(QFormLayout())\n        self.settings_buttons = QButtonGroup(exclusive=True)\n        self.settings_buttons.buttonClicked.connect(self.filterSettingsClicked)\n\n        btn_perc = QRadioButton(self, text='Percentage', checked=not self.useFixedThreshold)\n        self.settings_buttons.addButton(btn_perc, id=0)\n        self.percSpin = QSpinBox(minimum=0, maximum=100, value=self.percThresh,\n                                 enabled=not self.useFixedThreshold)\n        self.percSpin.valueChanged[int].connect(self.setPercThresh)\n        self.percSpin.editingFinished.connect(self.edited)\n\n        btn_fix = QRadioButton(self, text='Fixed', checked=self.useFixedThreshold)\n        self.settings_buttons.addButton(btn_fix, id=1)\n        self.fixedSpin = QSpinBox(minimum=0, maximum=1000000, value=self.fixedThresh,\n                                  enabled=self.useFixedThreshold)\n        self.fixedSpin.valueChanged[int].connect(self.setFixedThresh)\n        self.fixedSpin.editingFinished.connect(self.edited)\n        filter_settings.layout().addRow(btn_fix, self.fixedSpin)\n        filter_settings.layout().addRow(btn_perc, self.percSpin)\n\n        self.layout().addWidget(filter_settings)\n\n    def filterSettingsClicked(self):\n        self.setUseFixedThreshold(self.settings_buttons.checkedId())\n        self.percSpin.setEnabled(not self.useFixedThreshold)\n        self.fixedSpin.setEnabled(self.useFixedThreshold)\n        self.edited.emit()\n\n    def filterByClicked(self):\n        self.setFilter0(self.filter_buttons.checkedId())\n\n    def setFilter0(self, id_):\n        if self.filter0 != id_:\n            self.filter0 = id_\n            self.edited.emit()\n\n    def setFixedThresh(self, thresh):\n        if self.fixedThresh != thresh:\n            self.fixedThresh = thresh\n            self.fixedSpin.setValue(thresh)\n            self.edited.emit()\n\n    def setPercThresh(self, thresh):\n        if self.percThresh != thresh:\n            self.percThresh = thresh\n            self.percSpin.setValue(thresh)\n            self.edited.emit()\n\n    def setUseFixedThreshold(self, val):\n        if self.useFixedThreshold != val:\n            self.useFixedThreshold = val\n            self.edited.emit()\n\n    def parameters(self):\n        return {'fixedThresh': self.fixedThresh,\n                'percThresh' : self.percThresh,\n                'useFixedThreshold' : self.useFixedThreshold,\n                'filter0' : self.filter0}\n\n    def setParameters(self, params):\n        self.setPercThresh(params.get('percThresh', 5))\n        self.setFixedThresh(params.get('fixedThresh', 50))\n        self.setUseFixedThreshold(params.get('useFixedThreshold', False))\n        self.setFilter0(params.get('filter0', True))\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        filter0 = params.pop('filter0', True)\n        useFixedThreshold = params.pop('useFixedThreshold', True)\n        if useFixedThreshold:\n            threshold = params.pop('fixedThresh', 50)\n        else:\n            threshold = params.pop('percThresh', 5) / 100\n        return RemoveSparse(threshold, filter0)\n\nclass ImputeEditor(BaseEditor):\n    (NoImputation, Constant, Average,\n     Model, Random, DropRows, DropColumns) = 0, 1, 2, 3, 4, 5, 6\n\n    Imputers = {\n        NoImputation: (None, {}),\n        #         Constant: (None, {\"value\": 0})\n        Average: (preprocess.impute.Average(), {}),\n        #         Model: (preprocess.impute.Model, {}),\n        Random: (preprocess.impute.Random(), {}),\n        DropRows: (None, {})\n    }\n    Names = {\n        NoImputation: \"Don't impute.\",\n        Constant: \"Replace with constant\",\n        Average: \"Average/Most frequent\",\n        Model: \"Model based imputer\",\n        Random: \"Replace with random value\",\n        DropRows: \"Remove rows with missing values.\",\n    }\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.__method = ImputeEditor.Average\n        self.__group = group = QButtonGroup(self, exclusive=True)\n        group.buttonClicked.connect(self.__on_buttonClicked)\n\n        for methodid in [self.Average, self.Random, self.DropRows]:\n            text = self.Names[methodid]\n            rb = QRadioButton(text=text, checked=self.__method == methodid)\n            group.addButton(rb, methodid)\n            self.layout().addWidget(rb)\n\n    def setMethod(self, method):\n        b = self.__group.button(method)\n        if b is not None:\n            b.setChecked(True)\n            self.__method = method\n            self.changed.emit()\n\n    def setParameters(self, params):\n        method = params.get(\"method\", ImputeEditor.Average)\n        self.setMethod(method)\n\n    def parameters(self):\n        return {\"method\": self.__method}\n\n    def __on_buttonClicked(self):\n        self.__method = self.__group.checkedId()\n        self.changed.emit()\n        self.edited.emit()\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        method = params.pop(\"method\", ImputeEditor.Average)\n        if method == ImputeEditor.NoImputation:\n            return None\n        elif method == ImputeEditor.Average:\n            return preprocess.Impute()\n        elif method == ImputeEditor.Model:\n            return preprocess.Impute(method=preprocess.impute.Model())\n        elif method == ImputeEditor.DropRows:\n            return RemoveNaNRows()\n        elif method == ImputeEditor.DropColumns:\n            return preprocess.RemoveNaNColumns()\n        else:\n            method, defaults = ImputeEditor.Imputers[method]\n            defaults = dict(defaults)\n            defaults.update(params)\n            return preprocess.Impute(method=method)\n\n    def __repr__(self):\n        return self.Names[self.__method]\n\n\nclass UnivariateFeatureSelect(QWidget):\n    changed = Signal()\n    edited = Signal()\n\n    #: Strategy\n    Fixed, Proportion, FDR, FPR, FWE = 1, 2, 3, 4, 5\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.__scoreidx = 0\n        self.__strategy = UnivariateFeatureSelect.Fixed\n        self.__k = 10\n        self.__p = 75.0\n\n        box = QGroupBox(title=\"Score\", flat=True)\n        box.setLayout(QVBoxLayout())\n        self.__cb = cb = QComboBox(self, )\n        self.__cb.currentIndexChanged.connect(self.setScoreIndex)\n        self.__cb.activated.connect(self.edited)\n        box.layout().addWidget(cb)\n\n        self.layout().addWidget(box)\n\n        box = QGroupBox(title=\"Number of features\", flat=True)\n        self.__group = group = QButtonGroup(self, exclusive=True)\n        self.__spins = {}\n\n        form = QFormLayout()\n        fixedrb = QRadioButton(\"Fixed:\", checked=True)\n        group.addButton(fixedrb, UnivariateFeatureSelect.Fixed)\n        kspin = QSpinBox(\n            minimum=1, maximum=1000000, value=self.__k,\n            enabled=self.__strategy == UnivariateFeatureSelect.Fixed\n        )\n        kspin.valueChanged[int].connect(self.setK)\n        kspin.editingFinished.connect(self.edited)\n        self.__spins[UnivariateFeatureSelect.Fixed] = kspin\n        form.addRow(fixedrb, kspin)\n\n        percrb = QRadioButton(\"Proportion:\")\n        group.addButton(percrb, UnivariateFeatureSelect.Proportion)\n        pspin = QDoubleSpinBox(\n            minimum=1.0, maximum=100.0, singleStep=0.5,\n            value=self.__p, suffix=\"%\",\n            enabled=self.__strategy == UnivariateFeatureSelect.Proportion\n        )\n\n        pspin.valueChanged[float].connect(self.setP)\n        pspin.editingFinished.connect(self.edited)\n        self.__spins[UnivariateFeatureSelect.Proportion] = pspin\n        form.addRow(percrb, pspin)\n\n#         form.addRow(QRadioButton(\"FDR\"), QDoubleSpinBox())\n#         form.addRow(QRadioButton(\"FPR\"), QDoubleSpinBox())\n#         form.addRow(QRadioButton(\"FWE\"), QDoubleSpinBox())\n\n        self.__group.buttonClicked.connect(self.__on_buttonClicked)\n        box.setLayout(form)\n        self.layout().addWidget(box)\n\n    def setScoreIndex(self, scoreindex):\n        if self.__scoreidx != scoreindex:\n            self.__scoreidx = scoreindex\n            self.__cb.setCurrentIndex(scoreindex)\n            self.changed.emit()\n\n    def scoreIndex(self):\n        return self.__scoreidx\n\n    def setStrategy(self, strategy):\n        if self.__strategy != strategy:\n            self.__strategy = strategy\n            b = self.__group.button(strategy)\n            b.setChecked(True)\n            for st, rb in self.__spins.items():\n                rb.setEnabled(st == strategy)\n            self.changed.emit()\n\n    def setK(self, k):\n        if self.__k != k:\n            self.__k = k\n            spin = self.__spins[UnivariateFeatureSelect.Fixed]\n            spin.setValue(k)\n            if self.__strategy == UnivariateFeatureSelect.Fixed:\n                self.changed.emit()\n\n    def setP(self, p):\n        if self.__p != p:\n            self.__p = p\n            spin = self.__spins[UnivariateFeatureSelect.Proportion]\n            spin.setValue(p)\n            if self.__strategy == UnivariateFeatureSelect.Proportion:\n                self.changed.emit()\n\n    def setItems(self, itemlist):\n        for item in itemlist:\n            self.__cb.addItem(item[\"text\"])\n\n    def __on_buttonClicked(self):\n        strategy = self.__group.checkedId()\n        self.setStrategy(strategy)\n        self.edited.emit()\n\n    def setParameters(self, params):\n        score = params.get(\"score\", 0)\n        strategy = params.get(\"strategy\", UnivariateFeatureSelect.Fixed)\n        self.setScoreIndex(score)\n        self.setStrategy(strategy)\n        if strategy == UnivariateFeatureSelect.Fixed:\n            self.setK(params.get(\"k\", 10))\n        else:\n            self.setP(params.get(\"p\", 75))\n\n    def parameters(self):\n        score = self.__scoreidx\n        strategy = self.__strategy\n        p = self.__p\n        k = self.__k\n\n        return {\"score\": score, \"strategy\": strategy, \"p\": p, \"k\": k}\n\n\nclass FeatureSelectEditor(BaseEditor):\n\n    MEASURES = [\n        (\"Information Gain\", preprocess.score.InfoGain),\n        (\"Gain ratio\", preprocess.score.GainRatio),\n        (\"Gini index\", preprocess.score.Gini),\n        (\"ReliefF\", preprocess.score.ReliefF),\n        (\"Fast Correlation Based Filter\", preprocess.score.FCBF),\n        (\"ANOVA\", preprocess.score.ANOVA),\n        (\"Chi2\", preprocess.score.Chi2),\n        (\"RReliefF\", preprocess.score.RReliefF),\n        (\"Univariate Linear Regression\", preprocess.score.UnivariateLinearRegression)\n    ]\n\n    def __init__(self, parent=None):\n        super().__init__(parent)\n        self.setLayout(QVBoxLayout())\n        self.layout().setContentsMargins(0, 0, 0, 0)\n\n        self.__score = 0\n        self.__selecionidx = 0\n\n        self.__uni_fs = UnivariateFeatureSelect()\n        self.__uni_fs.setItems(\n            [{\"text\": \"Information Gain\", \"tooltip\": \"\"},\n             {\"text\": \"Gain Ratio\"},\n             {\"text\": \"Gini Index\"},\n             {\"text\": \"ReliefF\"},\n             {\"text\": \"Fast Correlation Based Filter\"},\n             {\"text\": \"ANOVA\"},\n             {\"text\": \"Chi2\"},\n             {\"text\": \"RReliefF\"},\n             {\"text\": \"Univariate Linear Regression\"}\n            ]\n        )\n        self.layout().addWidget(self.__uni_fs)\n        self.__uni_fs.changed.connect(self.changed)\n        self.__uni_fs.edited.connect(self.edited)\n\n    def setParameters(self, params):\n        self.__uni_fs.setParameters(params)\n\n    def parameters(self):\n        return self.__uni_fs.parameters()\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        score = params.pop(\"score\", 0)\n        score = FeatureSelectEditor.MEASURES[score][1]\n        strategy = params.get(\"strategy\", UnivariateFeatureSelect.Fixed)\n        k = params.get(\"k\", 10)\n        p = params.get(\"p\", 75.0)\n        if strategy == UnivariateFeatureSelect.Fixed:\n            return preprocess.fss.SelectBestFeatures(score(), k=k)\n        elif strategy == UnivariateFeatureSelect.Proportion:\n            return preprocess.fss.SelectBestFeatures(score(), k=p / 100)\n        else:\n            raise NotImplementedError\n\n    def __repr__(self):\n        params = self.__uni_fs.parameters()\n        return \"Score: {}, Strategy (Fixed): {}\".format(\n            self.MEASURES[params[\"score\"]][0], params[\"k\"])\n\n# TODO: Model based FS (random forest variable importance, ...), RFE\n# Unsupervised (min variance, constant, ...)??\n\nclass RandomFeatureSelectEditor(BaseEditor):\n    #: Strategy\n    Fixed, Percentage = 1, 2\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.__strategy = RandomFeatureSelectEditor.Fixed\n        self.__k = 10\n        self.__p = 75.0\n\n        box = QGroupBox(title=\"Number of features\", flat=True)\n        self.__group = group = QButtonGroup(self, exclusive=True)\n        self.__spins = {}\n\n        form = QFormLayout()\n        fixedrb = QRadioButton(\"Fixed\", checked=True)\n        group.addButton(fixedrb, RandomFeatureSelectEditor.Fixed)\n        kspin = QSpinBox(\n            minimum=1, maximum=1000000, value=self.__k,\n            enabled=self.__strategy == RandomFeatureSelectEditor.Fixed\n        )\n        kspin.valueChanged[int].connect(self.setK)\n        kspin.editingFinished.connect(self.edited)\n        self.__spins[RandomFeatureSelectEditor.Fixed] = kspin\n        form.addRow(fixedrb, kspin)\n\n        percrb = QRadioButton(\"Percentage\")\n        group.addButton(percrb, RandomFeatureSelectEditor.Percentage)\n        pspin = QDoubleSpinBox(\n            minimum=0.0, maximum=100.0, singleStep=0.5,\n            value=self.__p, suffix=\"%\",\n            enabled=self.__strategy == RandomFeatureSelectEditor.Percentage\n        )\n        pspin.valueChanged[float].connect(self.setP)\n        pspin.editingFinished.connect(self.edited)\n        self.__spins[RandomFeatureSelectEditor.Percentage] = pspin\n        form.addRow(percrb, pspin)\n\n        self.__group.buttonClicked.connect(self.__on_buttonClicked)\n        box.setLayout(form)\n        self.layout().addWidget(box)\n\n    def setStrategy(self, strategy):\n        if self.__strategy != strategy:\n            self.__strategy = strategy\n            b = self.__group.button(strategy)\n            b.setChecked(True)\n            for st, rb in self.__spins.items():\n                rb.setEnabled(st == strategy)\n            self.changed.emit()\n\n    def setK(self, k):\n        if self.__k != k:\n            self.__k = k\n            spin = self.__spins[RandomFeatureSelectEditor.Fixed]\n            spin.setValue(k)\n            if self.__strategy == RandomFeatureSelectEditor.Fixed:\n                self.changed.emit()\n\n    def setP(self, p):\n        if self.__p != p:\n            self.__p = p\n            spin = self.__spins[RandomFeatureSelectEditor.Percentage]\n            spin.setValue(p)\n            if self.__strategy == RandomFeatureSelectEditor.Percentage:\n                self.changed.emit()\n\n    def __on_buttonClicked(self):\n        strategy = self.__group.checkedId()\n        self.setStrategy(strategy)\n        self.edited.emit()\n\n    def setParameters(self, params):\n        strategy = params.get(\"strategy\", RandomFeatureSelectEditor.Fixed)\n        self.setStrategy(strategy)\n        if strategy == RandomFeatureSelectEditor.Fixed:\n            self.setK(params.get(\"k\", 10))\n        else:\n            self.setP(params.get(\"p\", 75.0))\n\n    def parameters(self):\n        strategy = self.__strategy\n        p = self.__p\n        k = self.__k\n\n        return {\"strategy\": strategy, \"p\": p, \"k\": k}\n\n    @staticmethod\n    def createinstance(params):\n        params = dict(params)\n        strategy = params.get(\"strategy\", RandomFeatureSelectEditor.Fixed)\n        k = params.get(\"k\", 10)\n        p = params.get(\"p\", 75.0)\n        if strategy == RandomFeatureSelectEditor.Fixed:\n            return preprocess.fss.SelectRandomFeatures(k=k)\n        elif strategy == RandomFeatureSelectEditor.Percentage:\n            return preprocess.fss.SelectRandomFeatures(k=p/100)\n        else:\n            # further implementations\n            raise NotImplementedError\n\n\ndef index_to_enum(enum, i):\n    \"\"\"Enums, by default, are not int-comparable, so use an ad-hoc mapping of\n    int to enum value at that position\"\"\"\n    return list(enum)[i]\n\n\ndef enum_to_index(enum, key):\n    \"\"\"Enums, by default, are not int-comparable, so use an ad-hoc mapping of\n    enum key to its int position\"\"\"\n    return list(enum).index(key)\n\nclass Scale(BaseEditor):\n    CenterByMean, ScaleBySD, NormalizeBySD, NormalizeBySpan_ZeroBased, \\\n        NormalizeSpan_NonZeroBased = 0, 1, 2, 3, 4\n\n    Names = {\n        NormalizeBySD: \"Standardize to \u03bc=0, \u03c3\u00b2=1\",\n        CenterByMean: \"Center to \u03bc=0\",\n        ScaleBySD: \"Scale to \u03c3\u00b2=1\",\n        NormalizeSpan_NonZeroBased: \"Normalize to interval [-1, 1]\",\n        NormalizeBySpan_ZeroBased: \"Normalize to interval [0, 1]\",\n    }\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.__method = Scale.NormalizeBySD\n        self.__group = group = QButtonGroup(self, exclusive=True)\n        group.buttonClicked.connect(self.__on_buttonClicked)\n\n        for methodid in [self.NormalizeBySD, self.CenterByMean, self.ScaleBySD,\n                         self.NormalizeSpan_NonZeroBased,\n                         self.NormalizeBySpan_ZeroBased]:\n            text = self.Names[methodid]\n            rb = QRadioButton(text=text, checked=self.__method == methodid)\n            group.addButton(rb, methodid)\n            self.layout().addWidget(rb)\n\n    def setMethod(self, method):\n        b = self.__group.button(method)\n        if b is not None:\n            b.setChecked(True)\n            self.__method = method\n            self.changed.emit()\n\n    def setParameters(self, params):\n        method = params.get(\"method\", Scale.NormalizeBySD)\n        self.setMethod(method)\n\n    def parameters(self):\n        return {\"method\": self.__method}\n\n    def __on_buttonClicked(self):\n        self.__method = self.__group.checkedId()\n        self.changed.emit()\n        self.edited.emit()\n\n    @staticmethod\n    def createinstance(params):\n        method = params.get(\"method\", Scale.NormalizeBySD)\n        if method == Scale.CenterByMean:\n            return _Scale(_Scale.CenteringType.Mean,\n                          _Scale.ScalingType.NoScaling)\n        elif method == Scale.ScaleBySD:\n            return _Scale(_Scale.CenteringType.NoCentering,\n                          _Scale.ScalingType.Std)\n        elif method == Scale.NormalizeBySD:\n            return Normalize(norm_type=Normalize.NormalizeBySD)\n        elif method == Scale.NormalizeBySpan_ZeroBased:\n            return Normalize(norm_type=Normalize.NormalizeBySpan)\n        else:  # method == Scale.NormalizeSpan_NonZeroBased\n            return Normalize(norm_type=Normalize.NormalizeBySpan,\n                             zero_based=False)\n\n    def __repr__(self):\n        return self.Names[self.__method]\n\n\nclass Randomize(BaseEditor):\n    RandomizeClasses, RandomizeAttributes, RandomizeMetas = _Randomize.Type\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        form = QFormLayout()\n        self.__rand_type_cb = QComboBox()\n        self.__rand_type_cb.addItems([\"Classes\",\n                                      \"Features\",\n                                      \"Meta data\"])\n\n        self.__rand_type_cb.currentIndexChanged.connect(self.changed)\n        self.__rand_type_cb.activated.connect(self.edited)\n\n        self.__rand_seed_ch = QCheckBox()\n        self.__rand_seed_ch.clicked.connect(self.edited)\n\n        form.addRow(\"Randomize:\", self.__rand_type_cb)\n        form.addRow(\"Replicable shuffling:\", self.__rand_seed_ch)\n        self.layout().addLayout(form)\n\n    def setParameters(self, params):\n        rand_type = params.get(\"rand_type\", Randomize.RandomizeClasses)\n        self.__rand_type_cb.setCurrentIndex(\n            enum_to_index(_Randomize.Type, rand_type))\n        self.__rand_seed_ch.setChecked(params.get(\"rand_seed\", 1) or 0)\n\n    def parameters(self):\n        return {\"rand_type\": index_to_enum(_Randomize.Type,\n                                           self.__rand_type_cb.currentIndex()),\n                \"rand_seed\": 1 if self.__rand_seed_ch.isChecked() else None}\n\n    @staticmethod\n    def createinstance(params):\n        rand_type = params.get(\"rand_type\", Randomize.RandomizeClasses)\n        rand_seed = params.get(\"rand_seed\", 1)\n        return _Randomize(rand_type=rand_type, rand_seed=rand_seed)\n\n    def __repr__(self):\n        return \"{}, {}\".format(self.__rand_type_cb.currentText(),\n                               \"Replicable\" if self.__rand_seed_ch.isChecked()\n                               else \"Not replicable\")\n\n\nclass PCA(BaseEditor):\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.n_components = 10\n\n        form = QFormLayout()\n        self.cspin = QSpinBox(minimum=1, value=self.n_components)\n        self.cspin.valueChanged[int].connect(self.setC)\n        self.cspin.editingFinished.connect(self.edited)\n\n        form.addRow(\"Components:\", self.cspin)\n        self.layout().addLayout(form)\n\n    def setParameters(self, params):\n        self.n_components = params.get(\"n_components\", 10)\n\n    def parameters(self):\n        return {\"n_components\": self.n_components}\n\n    def setC(self, n_components):\n        if self.n_components != n_components:\n            self.n_components = n_components\n            self.cspin.setValue(n_components)\n            self.changed.emit()\n\n    @staticmethod\n    def createinstance(params):\n        n_components = params.get(\"n_components\", 10)\n        return ProjectPCA(n_components=n_components)\n\n    def __repr__(self):\n        return \"Components: {}\".format(self.cspin.value())\n\n\nclass CUR(BaseEditor):\n\n    def __init__(self, parent=None, **kwargs):\n        super().__init__(parent, **kwargs)\n        self.setLayout(QVBoxLayout())\n\n        self.rank = 10\n        self.max_error = 1\n\n        form = QFormLayout()\n        self.rspin = QSpinBox(minimum=2, maximum=1000000, value=self.rank)\n        self.rspin.valueChanged[int].connect(self.setR)\n        self.rspin.editingFinished.connect(self.edited)\n        self.espin = QDoubleSpinBox(\n            minimum=0.1, maximum=100.0, singleStep=0.1,\n            value=self.max_error)\n        self.espin.valueChanged[float].connect(self.setE)\n        self.espin.editingFinished.connect(self.edited)\n\n        form.addRow(\"Rank:\", self.rspin)\n        form.addRow(\"Relative error:\", self.espin)\n        self.layout().addLayout(form)\n\n    def setParameters(self, params):\n        self.setR(params.get(\"rank\", 10))\n        self.setE(params.get(\"max_error\", 1))\n\n    def parameters(self):\n        return {\"rank\": self.rank, \"max_error\": self.max_error}\n\n    def setR(self, rank):\n        if self.rank != rank:\n            self.rank = rank\n            self.rspin.setValue(rank)\n            self.changed.emit()\n\n    def setE(self, max_error):\n        if self.max_error != max_error:\n            self.max_error = max_error\n            self.espin.setValue(max_error)\n            self.changed.emit()\n\n    @staticmethod\n    def createinstance(params):\n        rank = params.get(\"rank\", 10)\n        max_error = params.get(\"max_error\", 1)\n        return ProjectCUR(rank=rank, max_error=max_error)\n\n    def __repr__(self):\n        return \"Rank: {}, Relative error: {}\".format(self.rspin.value(),\n                                                     self.espin.value())\n\n\n# This is intended for future improvements.\n# I.e. it should be possible to add/register preprocessor actions\n# through entry points (for use by add-ons). Maybe it should be a\n# general framework (this is not the only place where such\n# functionality is desired (for instance in Orange v2.* Rank widget\n# already defines its own entry point).\nclass Description:\n    \"\"\"\n    A description of an action/function.\n    \"\"\"\n    def __init__(self, title, icon=None, summary=None, input_=None, output=None,\n                 requires=None, note=None, related=None, keywords=None,\n                 helptopic=None):\n        self.title = title\n        self.icon = icon\n        self.summary = summary\n        self.input = input_\n        self.output = output\n        self.requires = requires\n        self.note = note\n        self.related = related\n        self.keywords = keywords\n        self.helptopic = helptopic\n\n\nclass PreprocessAction:\n    def __init__(self, name, qualname, category, description, viewclass):\n        self.name = name\n        self.qualname = qualname\n        self.category = category\n        self.description = description\n        self.viewclass = viewclass\n\n\ndef icon_path(basename):\n    return pkg_resources.resource_filename(__name__, \"icons/\" + basename)\n\n\nPREPROCESS_ACTIONS = [\n    PreprocessAction(\n        \"Discretize\", \"orange.preprocess.discretize\", \"Discretization\",\n        Description(\"Discretize Continuous Variables\",\n                    icon_path(\"Discretize.svg\")),\n        DiscretizeEditor\n    ),\n    PreprocessAction(\n        \"Continuize\", \"orange.preprocess.continuize\", \"Continuization\",\n        Description(\"Continuize Discrete Variables\",\n                    icon_path(\"Continuize.svg\")),\n        ContinuizeEditor\n    ),\n    PreprocessAction(\n        \"Impute\", \"orange.preprocess.impute\", \"Impute\",\n        Description(\"Impute Missing Values\",\n                    icon_path(\"Impute.svg\")),\n        ImputeEditor\n    ),\n    PreprocessAction(\n        \"Feature Selection\", \"orange.preprocess.fss\", \"Feature Selection\",\n        Description(\"Select Relevant Features\",\n                    icon_path(\"SelectColumns.svg\")),\n        FeatureSelectEditor\n    ),\n    PreprocessAction(\n        \"Random Feature Selection\", \"orange.preprocess.randomfss\",\n        \"Random Feature Selection\",\n        Description(\"Select Random Features\",\n                    icon_path(\"SelectColumnsRandom.svg\")),\n        RandomFeatureSelectEditor\n    ),\n    PreprocessAction(\n        \"Normalize\", \"orange.preprocess.scale\", \"Scale\",\n        Description(\"Normalize Features\",\n                    icon_path(\"Normalize.svg\")),\n        Scale\n    ),\n    PreprocessAction(\n        \"Randomize\", \"orange.preprocess.randomize\", \"Randomization\",\n        Description(\"Randomize\",\n                    icon_path(\"Random.svg\")),\n        Randomize\n    ),\n    PreprocessAction(\n        \"Remove Sparse\", \"orange.preprocess.remove_sparse\", \"Feature Selection\",\n        Description(\"Remove Sparse Features\",\n                    icon_path(\"PurgeDomain.svg\")),\n        RemoveSparseEditor\n    ),\n    PreprocessAction(\n        \"PCA\", \"orange.preprocess.pca\", \"PCA\",\n        Description(\"Principal Component Analysis\",\n                    icon_path(\"PCA.svg\")),\n        PCA\n    ),\n    PreprocessAction(\n        \"CUR\", \"orange.preprocess.cur\", \"CUR\",\n        Description(\"CUR Matrix Decomposition\",\n                    icon_path(\"SelectColumns.svg\")),\n        CUR\n    )\n]\n\n\n# TODO: Extend with entry points here\n# PREPROCESS_ACTIONS += iter_entry_points(\"Orange.widgets.data.owpreprocess\")\n\n# ####\n# The actual owwidget\n# ####\n\n# Note:\n# The preprocessors are drag/dropped onto a sequence widget, where\n# they can be reordered/removed/edited.\n#\n# Model <-> Adapter/Controller <-> View\n#\n# * `Model`: the current constructed preprocessor model.\n# * the source (of drag/drop) is an item model displayed in a list\n#   view (source list).\n# * the drag/drop is controlled by the controller/adapter,\n\n\nclass OWPreprocess(widget.OWWidget):\n    name = \"Preprocess\"\n    description = \"Construct a data preprocessing pipeline.\"\n    icon = \"icons/Preprocess.svg\"\n    priority = 2105\n    keywords = [\"process\"]\n\n    settings_version = 2\n\n    class Inputs:\n        data = Input(\"Data\", Orange.data.Table)\n\n    class Outputs:\n        preprocessor = Output(\"Preprocessor\", preprocess.preprocess.Preprocess, dynamic=False)\n        preprocessed_data = Output(\"Preprocessed Data\", Orange.data.Table)\n\n    storedsettings = Setting({})\n    autocommit = Setting(True)\n    PREPROCESSORS = PREPROCESS_ACTIONS\n    CONTROLLER = Controller\n\n    def __init__(self):\n        super().__init__()\n\n        self.data = None\n        self._invalidated = False\n\n        # List of available preprocessors (DescriptionRole : Description)\n        self.preprocessors = QStandardItemModel()\n\n        def mimeData(indexlist):\n            assert len(indexlist) == 1\n            index = indexlist[0]\n            qname = index.data(DescriptionRole).qualname\n            m = QMimeData()\n            m.setData(\"application/x-qwidget-ref\", qname.encode(\"utf-8\"))\n            return m\n        # TODO: Fix this (subclass even if just to pass a function\n        # for mimeData delegate)\n        self.preprocessors.mimeData = mimeData\n\n        box = gui.vBox(self.controlArea, \"Preprocessors\")\n\n        self.preprocessorsView = view = QListView(\n            selectionMode=QListView.SingleSelection,\n            dragEnabled=True,\n            dragDropMode=QListView.DragOnly\n        )\n        view.setModel(self.preprocessors)\n        view.activated.connect(self.__activated)\n\n        box.layout().addWidget(view)\n\n        ####\n        self._qname2ppdef = {ppdef.qualname: ppdef for\n                             ppdef in self.PREPROCESSORS}\n\n        # List of 'selected' preprocessors and their parameters.\n        self.preprocessormodel = None\n\n        self.flow_view = SequenceFlow()\n        self.controler = self.CONTROLLER(self.flow_view, parent=self)\n\n        self.overlay = OverlayWidget(self)\n        self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents)\n        self.overlay.setWidget(self.flow_view)\n        self.overlay.setLayout(QVBoxLayout())\n        self.overlay.layout().addWidget(\n            QLabel(\"Drag items from the list on the left\", wordWrap=True))\n\n        self.scroll_area = QScrollArea(\n            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn\n        )\n        self.scroll_area.viewport().setAcceptDrops(True)\n        self.scroll_area.setWidget(self.flow_view)\n        self.scroll_area.setWidgetResizable(True)\n        self.mainArea.layout().addWidget(self.scroll_area)\n        self.flow_view.installEventFilter(self)\n\n        box = gui.vBox(self.controlArea, \"Output\")\n        gui.auto_send(box, self, \"autocommit\", box=False)\n\n        self._initialize()\n\n    def _initialize(self):\n        for pp_def in self.PREPROCESSORS:\n            description = pp_def.description\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item = QStandardItem(icon, description.title)\n            item.setToolTip(description.summary or \"\")\n            item.setData(pp_def, DescriptionRole)\n            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable |\n                          Qt.ItemIsDragEnabled)\n            self.preprocessors.appendRow([item])\n\n        model = self.load(self.storedsettings)\n\n        self.set_model(model)\n\n        if not model.rowCount():\n            # enforce default width constraint if no preprocessors\n            # are instantiated (if the model is not empty the constraints\n            # will be triggered by LayoutRequest event on the `flow_view`)\n            self.__update_size_constraint()\n\n        self.apply()\n\n    def load(self, saved):\n        \"\"\"Load a preprocessor list from a dict.\"\"\"\n        preprocessors = saved.get(\"preprocessors\", [])\n        model = StandardItemModel()\n\n        def dropMimeData(data, action, row, _column, _parent):\n            if data.hasFormat(\"application/x-qwidget-ref\") and \\\n                    action == Qt.CopyAction:\n                qname = bytes(data.data(\"application/x-qwidget-ref\")).decode()\n\n                ppdef = self._qname2ppdef[qname]\n                item = QStandardItem(ppdef.description.title)\n                item.setData({}, ParametersRole)\n                item.setData(ppdef.description.title, Qt.DisplayRole)\n                item.setData(ppdef, DescriptionRole)\n                self.preprocessormodel.insertRow(row, [item])\n                return True\n            else:\n                return False\n\n        model.dropMimeData = dropMimeData\n\n        for qualname, params in preprocessors:\n            pp_def = self._qname2ppdef[qualname]\n            description = pp_def.description\n            item = QStandardItem(description.title)\n            if description.icon:\n                icon = QIcon(description.icon)\n            else:\n                icon = QIcon()\n            item.setIcon(icon)\n            item.setToolTip(description.summary)\n            item.setData(pp_def, DescriptionRole)\n            item.setData(params, ParametersRole)\n\n            model.appendRow(item)\n        return model\n\n    @staticmethod\n    def save(model):\n        \"\"\"Save the preprocessor list to a dict.\"\"\"\n        d = {\"name\": \"\"}\n        preprocessors = []\n        for i in range(model.rowCount()):\n            item = model.item(i)\n            pp_def = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n            preprocessors.append((pp_def.qualname, params))\n\n        d[\"preprocessors\"] = preprocessors\n        return d\n\n\n    def set_model(self, ppmodel):\n        if self.preprocessormodel:\n            self.preprocessormodel.dataChanged.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged)\n            self.preprocessormodel.deleteLater()\n\n        self.preprocessormodel = ppmodel\n        self.controler.setModel(ppmodel)\n        if ppmodel is not None:\n            self.preprocessormodel.dataChanged.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged)\n            self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged)\n\n        self.__update_overlay()\n\n    def __update_overlay(self):\n        if self.preprocessormodel is None or \\\n                self.preprocessormodel.rowCount() == 0:\n            self.overlay.setWidget(self.flow_view)\n            self.overlay.show()\n        else:\n            self.overlay.setWidget(None)\n            self.overlay.hide()\n\n    def __on_modelchanged(self):\n        self.__update_overlay()\n        self.commit()\n\n    @Inputs.data\n    @check_sql_input\n    def set_data(self, data=None):\n        \"\"\"Set the input dataset.\"\"\"\n        self.data = data\n\n    def handleNewSignals(self):\n        self.apply()\n\n    def __activated(self, index):\n        item = self.preprocessors.itemFromIndex(index)\n        action = item.data(DescriptionRole)\n        item = QStandardItem()\n        item.setData({}, ParametersRole)\n        item.setData(action.description.title, Qt.DisplayRole)\n        item.setData(action, DescriptionRole)\n        self.preprocessormodel.appendRow([item])\n\n    def buildpreproc(self):\n        plist = []\n        for i in range(self.preprocessormodel.rowCount()):\n            item = self.preprocessormodel.item(i)\n            desc = item.data(DescriptionRole)\n            params = item.data(ParametersRole)\n\n            if not isinstance(params, dict):\n                params = {}\n\n            create = desc.viewclass.createinstance\n            plist.append(create(params))\n\n        if len(plist) == 1:\n            return plist[0]\n        else:\n            return preprocess.preprocess.PreprocessorList(plist)\n\n    def apply(self):\n        # Sync the model into storedsettings on every apply.\n        self.storeSpecificSettings()\n        preprocessor = self.buildpreproc()\n\n        if self.data is not None:\n            self.error()\n            try:\n                data = preprocessor(self.data)\n            except (ValueError, ZeroDivisionError) as e:\n                self.error(str(e))\n                return\n        else:\n            data = None\n\n        self.Outputs.preprocessor.send(preprocessor)\n        self.Outputs.preprocessed_data.send(data)\n\n    def commit(self):\n        if not self._invalidated:\n            self._invalidated = True\n            QApplication.postEvent(self, QEvent(QEvent.User))\n\n    def customEvent(self, event):\n        if event.type() == QEvent.User and self._invalidated:\n            self._invalidated = False\n            self.apply()\n\n    def eventFilter(self, receiver, event):\n        if receiver is self.flow_view and event.type() == QEvent.LayoutRequest:\n            QTimer.singleShot(0, self.__update_size_constraint)\n\n        return super().eventFilter(receiver, event)\n\n    def storeSpecificSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().storeSpecificSettings()\n\n    def saveSettings(self):\n        \"\"\"Reimplemented.\"\"\"\n        self.storedsettings = self.save(self.preprocessormodel)\n        super().saveSettings()\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version < 2:\n            for action, params in settings[\"storedsettings\"][\"preprocessors\"]:\n                if action == \"orange.preprocess.scale\":\n                    scale = center = None\n                    if \"center\" in params:\n                        center = params.pop(\"center\").name\n                    if \"scale\" in params:\n                        scale = params.pop(\"scale\").name\n                    migratable = {\n                        (\"Mean\", \"NoScaling\"): Scale.CenterByMean,\n                        (\"NoCentering\", \"Std\"): Scale.ScaleBySD,\n                        (\"Mean\", \"Std\"): Scale.NormalizeBySD,\n                        (\"NoCentering\", \"Span\"): Scale.NormalizeBySpan_ZeroBased\n                    }\n                    params[\"method\"] = \\\n                        migratable.get((center, scale), Scale.NormalizeBySD)\n\n    def onDeleteWidget(self):\n        self.data = None\n        self.set_model(None)\n        super().onDeleteWidget()\n\n    @Slot()\n    def __update_size_constraint(self):\n        # Update minimum width constraint on the scroll area containing\n        # the 'instantiated' preprocessor list (to avoid the horizontal\n        # scroll bar).\n        sh = self.flow_view.minimumSizeHint()\n        scroll_width = self.scroll_area.verticalScrollBar().width()\n        self.scroll_area.setMinimumWidth(\n            min(max(sh.width() + scroll_width + 2, self.controlArea.width()),\n                520))\n\n    def sizeHint(self):\n        sh = super().sizeHint()\n        return sh.expandedTo(QSize(sh.width() + 300, 500))\n\n    def send_report(self):\n        pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w)\n              for i, w in enumerate(self.controler.view.widgets())]\n        if pp:\n            self.report_items(\"Settings\", pp)\n\nif __name__ == \"__main__\":  # pragma: no cover\n    WidgetPreview(OWPreprocess).run(Orange.data.Table(\"brown-selected\"))\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Status bar does not display data summaries when input data is set

**Description:**
When input data is provided to the OWPreprocess widget, the status bar is expected to update with summaries of the input and output data. However, currently, these summaries are not displayed, which leads to tests failing to verify the correct behavior of the widget.

**Example Buggy Code:**
```python
def test_summary(self):
    data = Table("iris")
    self.send_signal(self.widget.Inputs.data, data)
    # Expected summaries are not updated in the status bar
```

**Expected Behavior:**
Upon receiving new input data, the status bar should display the number of input instances and the number of instances after preprocessing. For example, it should show something like "Input: 150 instances" and "Output: 150 instances" when data is successfully processed.

**Actual Behavior:**
The status bar remains without the expected summaries, making it unclear whether the data has been correctly received and processed. This absence of information causes related tests to fail, as they rely on these summaries to verify the widget's functionality.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### Orange/widgets/data/owpreprocess.py
from collections import OrderedDict
import pkg_resources

import numpy

from AnyQt.QtWidgets import (
    QWidget, QButtonGroup, QGroupBox, QRadioButton, QSlider,
    QDoubleSpinBox, QComboBox, QSpinBox, QListView, QLabel,
    QScrollArea, QVBoxLayout, QHBoxLayout, QFormLayout,
    QSizePolicy, QApplication, QCheckBox
)

from AnyQt.QtGui import (
    QIcon, QStandardItemModel, QStandardItem
)

from AnyQt.QtCore import (
    Qt, QEvent, QSize, QMimeData, QTimer
)

from AnyQt.QtCore import pyqtSignal as Signal, pyqtSlot as Slot

import Orange.data
from Orange import preprocess
from Orange.preprocess import Continuize, ProjectPCA, RemoveNaNRows, \
    ProjectCUR, Scale as _Scale, Randomize as _Randomize, RemoveSparse
from Orange.widgets import widget, gui
from Orange.widgets.settings import Setting
from Orange.widgets.utils.overlay import OverlayWidget
from Orange.widgets.utils.sql import check_sql_input
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.widget import Input, Output
from Orange.preprocess import Normalize
from Orange.widgets.data.utils.preprocess import (
    BaseEditor, blocked, StandardItemModel, DescriptionRole,
    ParametersRole, Controller, SequenceFlow
)

class _NoneDisc(preprocess.discretize.Discretization):
    """Discretize all variables into None.

    Used in combination with preprocess.Discretize to remove
    all discrete features from the domain.

    """
    _reprable_module = True

    def __call__(self, data, variable):
        return None


class DiscretizeEditor(BaseEditor):
    """
    Editor for preprocess.Discretize.
    """
    #: Discretize methods
    NoDisc, EqualWidth, EqualFreq, Drop, EntropyMDL = 0, 1, 2, 3, 4
    Discretizers = {
        NoDisc: (None, {}),
        EqualWidth: (preprocess.discretize.EqualWidth, {"n": 4}),
        EqualFreq:  (preprocess.discretize.EqualFreq, {"n": 4}),
        Drop: (_NoneDisc, {}),
        EntropyMDL: (preprocess.discretize.EntropyMDL, {"force": False})
    }
    Names = {
        NoDisc: "None",
        EqualWidth: "Equal width discretization",
        EqualFreq: "Equal frequency discretization",
        Drop: "Remove numeric features",
        EntropyMDL: "Entropy-MDL discretization"
    }

    def __init__(self, parent=None, **kwargs):
        BaseEditor.__init__(self, parent, **kwargs)
        self.__method = DiscretizeEditor.EqualFreq
        self.__nintervals = 4

        layout = QVBoxLayout()
        self.setLayout(layout)
        self.__group = group = QButtonGroup(self, exclusive=True)

        for method in [self.EntropyMDL, self.EqualFreq, self.EqualWidth,
                       self.Drop]:
            rb = QRadioButton(
                self, text=self.Names[method],
                checked=self.__method == method
            )
            layout.addWidget(rb)
            group.addButton(rb, method)

        group.buttonClicked.connect(self.__on_buttonClicked)

        self.__slbox = slbox = QGroupBox(
            title="Number of intervals (for equal width/frequency)",
            flat=True
        )
        slbox.setLayout(QHBoxLayout())
        self.__slider = slider = QSlider(
            orientation=Qt.Horizontal,
            minimum=2, maximum=10, value=self.__nintervals,
            enabled=self.__method in [self.EqualFreq, self.EqualWidth],
            pageStep=1, tickPosition=QSlider.TicksBelow
        )
        slider.valueChanged.connect(self.__on_valueChanged)
        slbox.layout().addWidget(slider)
        self.__slabel = slabel = QLabel()
        slbox.layout().addWidget(slabel)

        container = QHBoxLayout()
        container.setContentsMargins(13, 0, 0, 0)
        container.addWidget(slbox)
        self.layout().insertLayout(3, container)
        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)

    def setMethod(self, method):
        if self.__method != method:
            self.__method = method
            b = self.__group.button(method)
            b.setChecked(True)
            self.__slider.setEnabled(
                method in [self.EqualFreq, self.EqualWidth]
            )
            self.changed.emit()

    def method(self):
        return self.__method

    def intervals(self):
        return self.__nintervals

    def setIntervals(self, n):
        n = numpy.clip(n, self.__slider.minimum(), self.__slider.maximum())
        n = int(n)
        if self.__nintervals != n:
            self.__nintervals = n
            # blocking signals in order to differentiate between
            # changed by user (notified through __on_valueChanged) or
            # changed programmatically (this)
            with blocked(self.__slider):
                self.__slider.setValue(n)
                self.__slabel.setText(str(self.__slider.value()))
            self.changed.emit()

    def setParameters(self, params):
        method = params.get("method", self.EqualFreq)
        nintervals = params.get("n", 5)
        self.setMethod(method)
        if method in [self.EqualFreq, self.EqualWidth]:
            self.setIntervals(nintervals)

    def parameters(self):
        if self.__method in [self.EqualFreq, self.EqualWidth]:
            return {"method": self.__method, "n": self.__nintervals}
        else:
            return {"method": self.__method}

    def __on_buttonClicked(self):
        # on user 'method' button click
        method = self.__group.checkedId()
        if method != self.__method:
            self.setMethod(self.__group.checkedId())
            self.edited.emit()

    def __on_valueChanged(self):
        # on user n intervals slider change.
        self.__nintervals = self.__slider.value()
        self.changed.emit()
        self.edited.emit()
        self.__slabel.setText(str(self.__slider.value()))

    @staticmethod
    def createinstance(params):
        params = dict(params)
        method = params.pop("method", DiscretizeEditor.EqualFreq)
        method, defaults = DiscretizeEditor.Discretizers[method]

        if method is None:
            return None

        resolved = dict(defaults)
        # update only keys in defaults?
        resolved.update(params)
        return preprocess.Discretize(method(**params), remove_const=False)

    def __repr__(self):
        n_int = ", Number of intervals: {}".format(self.__nintervals) \
            if self.__method in [self.EqualFreq, self.EqualWidth] else ""
        return "{}{}".format(self.Names[self.__method], n_int)


class ContinuizeEditor(BaseEditor):
    _Type = type(Continuize.FirstAsBase)

    Continuizers = OrderedDict([
        (Continuize.FrequentAsBase, "Most frequent is base"),
        (Continuize.Indicators, "One feature per value"),
        (Continuize.RemoveMultinomial, "Remove non-binary features"),
        (Continuize.Remove, "Remove categorical features"),
        (Continuize.AsOrdinal, "Treat as ordinal"),
        (Continuize.AsNormalizedOrdinal, "Divide by number of values")
    ])

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.setLayout(QVBoxLayout())
        self.__treatment = Continuize.Indicators
        self.__group = group = QButtonGroup(exclusive=True)
        group.buttonClicked.connect(self.__on_buttonClicked)

        for treatment, text in ContinuizeEditor.Continuizers.items():
            rb = QRadioButton(
                text=text,
                checked=self.__treatment == treatment)
            group.addButton(rb, enum_to_index(ContinuizeEditor._Type,
                                              treatment))
            self.layout().addWidget(rb)

        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)

    def setTreatment(self, treatment):
        buttonid = enum_to_index(ContinuizeEditor._Type, treatment)
        b = self.__group.button(buttonid)
        if b is not None:
            b.setChecked(True)
            self.__treatment = treatment
            self.changed.emit()

    def treatment(self):
        return self.__treatment

    def setParameters(self, params):
        treatment = params.get("multinomial_treatment", Continuize.Indicators)
        self.setTreatment(treatment)

    def parameters(self):
        return {"multinomial_treatment": self.__treatment}

    def __on_buttonClicked(self):
        self.__treatment = index_to_enum(
            ContinuizeEditor._Type, self.__group.checkedId())
        self.changed.emit()
        self.edited.emit()

    @staticmethod
    def createinstance(params):
        params = dict(params)
        treatment = params.pop("multinomial_treatment", Continuize.Indicators)
        return Continuize(multinomial_treatment=treatment)

    def __repr__(self):
        return self.Continuizers[self.__treatment]

class RemoveSparseEditor(BaseEditor):

    options = ["missing", "zeros"]

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.fixedThresh = 50
        self.percThresh = 5
        self.useFixedThreshold = False
        self.filter0 = True
        self.setLayout(QVBoxLayout())

        self.layout().addWidget(QLabel("Remove features with too many"))
        options = ["missing values",
                   "zeros"]
        self.filter_buttons = QButtonGroup(exclusive=True)
        self.filter_buttons.buttonClicked.connect(self.filterByClicked)
        for idx, option, in enumerate(options):
            btn = QRadioButton(self, text=option, checked=idx == 0)
            self.filter_buttons.addButton(btn, id=idx)
            self.layout().addWidget(btn)

        self.layout().addSpacing(20)

        filter_settings = QGroupBox(title='Threshold:', flat=True)
        filter_settings.setLayout(QFormLayout())
        self.settings_buttons = QButtonGroup(exclusive=True)
        self.settings_buttons.buttonClicked.connect(self.filterSettingsClicked)

        btn_perc = QRadioButton(self, text='Percentage', checked=not self.useFixedThreshold)
        self.settings_buttons.addButton(btn_perc, id=0)
        self.percSpin = QSpinBox(minimum=0, maximum=100, value=self.percThresh,
                                 enabled=not self.useFixedThreshold)
        self.percSpin.valueChanged[int].connect(self.setPercThresh)
        self.percSpin.editingFinished.connect(self.edited)

        btn_fix = QRadioButton(self, text='Fixed', checked=self.useFixedThreshold)
        self.settings_buttons.addButton(btn_fix, id=1)
        self.fixedSpin = QSpinBox(minimum=0, maximum=1000000, value=self.fixedThresh,
                                  enabled=self.useFixedThreshold)
        self.fixedSpin.valueChanged[int].connect(self.setFixedThresh)
        self.fixedSpin.editingFinished.connect(self.edited)
        filter_settings.layout().addRow(btn_fix, self.fixedSpin)
        filter_settings.layout().addRow(btn_perc, self.percSpin)

        self.layout().addWidget(filter_settings)

    def filterSettingsClicked(self):
        self.setUseFixedThreshold(self.settings_buttons.checkedId())
        self.percSpin.setEnabled(not self.useFixedThreshold)
        self.fixedSpin.setEnabled(self.useFixedThreshold)
        self.edited.emit()

    def filterByClicked(self):
        self.setFilter0(self.filter_buttons.checkedId())

    def setFilter0(self, id_):
        if self.filter0 != id_:
            self.filter0 = id_
            self.edited.emit()

    def setFixedThresh(self, thresh):
        if self.fixedThresh != thresh:
            self.fixedThresh = thresh
            self.fixedSpin.setValue(thresh)
            self.edited.emit()

    def setPercThresh(self, thresh):
        if self.percThresh != thresh:
            self.percThresh = thresh
            self.percSpin.setValue(thresh)
            self.edited.emit()

    def setUseFixedThreshold(self, val):
        if self.useFixedThreshold != val:
            self.useFixedThreshold = val
            self.edited.emit()

    def parameters(self):
        return {'fixedThresh': self.fixedThresh,
                'percThresh' : self.percThresh,
                'useFixedThreshold' : self.useFixedThreshold,
                'filter0' : self.filter0}

    def setParameters(self, params):
        self.setPercThresh(params.get('percThresh', 5))
        self.setFixedThresh(params.get('fixedThresh', 50))
        self.setUseFixedThreshold(params.get('useFixedThreshold', False))
        self.setFilter0(params.get('filter0', True))

    @staticmethod
    def createinstance(params):
        params = dict(params)
        filter0 = params.pop('filter0', True)
        useFixedThreshold = params.pop('useFixedThreshold', True)
        if useFixedThreshold:
            threshold = params.pop('fixedThresh', 50)
        else:
            threshold = params.pop('percThresh', 5) / 100
        return RemoveSparse(threshold, filter0)

class ImputeEditor(BaseEditor):
    (NoImputation, Constant, Average,
     Model, Random, DropRows, DropColumns) = 0, 1, 2, 3, 4, 5, 6

    Imputers = {
        NoImputation: (None, {}),
        #         Constant: (None, {"value": 0})
        Average: (preprocess.impute.Average(), {}),
        #         Model: (preprocess.impute.Model, {}),
        Random: (preprocess.impute.Random(), {}),
        DropRows: (None, {})
    }
    Names = {
        NoImputation: "Don't impute.",
        Constant: "Replace with constant",
        Average: "Average/Most frequent",
        Model: "Model based imputer",
        Random: "Replace with random value",
        DropRows: "Remove rows with missing values.",
    }

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.setLayout(QVBoxLayout())

        self.__method = ImputeEditor.Average
        self.__group = group = QButtonGroup(self, exclusive=True)
        group.buttonClicked.connect(self.__on_buttonClicked)

        for methodid in [self.Average, self.Random, self.DropRows]:
            text = self.Names[methodid]
            rb = QRadioButton(text=text, checked=self.__method == methodid)
            group.addButton(rb, methodid)
            self.layout().addWidget(rb)

    def setMethod(self, method):
        b = self.__group.button(method)
        if b is not None:
            b.setChecked(True)
            self.__method = method
            self.changed.emit()

    def setParameters(self, params):
        method = params.get("method", ImputeEditor.Average)
        self.setMethod(method)

    def parameters(self):
        return {"method": self.__method}

    def __on_buttonClicked(self):
        self.__method = self.__group.checkedId()
        self.changed.emit()
        self.edited.emit()

    @staticmethod
    def createinstance(params):
        params = dict(params)
        method = params.pop("method", ImputeEditor.Average)
        if method == ImputeEditor.NoImputation:
            return None
        elif method == ImputeEditor.Average:
            return preprocess.Impute()
        elif method == ImputeEditor.Model:
            return preprocess.Impute(method=preprocess.impute.Model())
        elif method == ImputeEditor.DropRows:
            return RemoveNaNRows()
        elif method == ImputeEditor.DropColumns:
            return preprocess.RemoveNaNColumns()
        else:
            method, defaults = ImputeEditor.Imputers[method]
            defaults = dict(defaults)
            defaults.update(params)
            return preprocess.Impute(method=method)

    def __repr__(self):
        return self.Names[self.__method]


class UnivariateFeatureSelect(QWidget):
    changed = Signal()
    edited = Signal()

    #: Strategy
    Fixed, Proportion, FDR, FPR, FWE = 1, 2, 3, 4, 5

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.setLayout(QVBoxLayout())

        self.__scoreidx = 0
        self.__strategy = UnivariateFeatureSelect.Fixed
        self.__k = 10
        self.__p = 75.0

        box = QGroupBox(title="Score", flat=True)
        box.setLayout(QVBoxLayout())
        self.__cb = cb = QComboBox(self, )
        self.__cb.currentIndexChanged.connect(self.setScoreIndex)
        self.__cb.activated.connect(self.edited)
        box.layout().addWidget(cb)

        self.layout().addWidget(box)

        box = QGroupBox(title="Number of features", flat=True)
        self.__group = group = QButtonGroup(self, exclusive=True)
        self.__spins = {}

        form = QFormLayout()
        fixedrb = QRadioButton("Fixed:", checked=True)
        group.addButton(fixedrb, UnivariateFeatureSelect.Fixed)
        kspin = QSpinBox(
            minimum=1, maximum=1000000, value=self.__k,
            enabled=self.__strategy == UnivariateFeatureSelect.Fixed
        )
        kspin.valueChanged[int].connect(self.setK)
        kspin.editingFinished.connect(self.edited)
        self.__spins[UnivariateFeatureSelect.Fixed] = kspin
        form.addRow(fixedrb, kspin)

        percrb = QRadioButton("Proportion:")
        group.addButton(percrb, UnivariateFeatureSelect.Proportion)
        pspin = QDoubleSpinBox(
            minimum=1.0, maximum=100.0, singleStep=0.5,
            value=self.__p, suffix="%",
            enabled=self.__strategy == UnivariateFeatureSelect.Proportion
        )

        pspin.valueChanged[float].connect(self.setP)
        pspin.editingFinished.connect(self.edited)
        self.__spins[UnivariateFeatureSelect.Proportion] = pspin
        form.addRow(percrb, pspin)

#         form.addRow(QRadioButton("FDR"), QDoubleSpinBox())
#         form.addRow(QRadioButton("FPR"), QDoubleSpinBox())
#         form.addRow(QRadioButton("FWE"), QDoubleSpinBox())

        self.__group.buttonClicked.connect(self.__on_buttonClicked)
        box.setLayout(form)
        self.layout().addWidget(box)

    def setScoreIndex(self, scoreindex):
        if self.__scoreidx != scoreindex:
            self.__scoreidx = scoreindex
            self.__cb.setCurrentIndex(scoreindex)
            self.changed.emit()

    def scoreIndex(self):
        return self.__scoreidx

    def setStrategy(self, strategy):
        if self.__strategy != strategy:
            self.__strategy = strategy
            b = self.__group.button(strategy)
            b.setChecked(True)
            for st, rb in self.__spins.items():
                rb.setEnabled(st == strategy)
            self.changed.emit()

    def setK(self, k):
        if self.__k != k:
            self.__k = k
            spin = self.__spins[UnivariateFeatureSelect.Fixed]
            spin.setValue(k)
            if self.__strategy == UnivariateFeatureSelect.Fixed:
                self.changed.emit()

    def setP(self, p):
        if self.__p != p:
            self.__p = p
            spin = self.__spins[UnivariateFeatureSelect.Proportion]
            spin.setValue(p)
            if self.__strategy == UnivariateFeatureSelect.Proportion:
                self.changed.emit()

    def setItems(self, itemlist):
        for item in itemlist:
            self.__cb.addItem(item["text"])

    def __on_buttonClicked(self):
        strategy = self.__group.checkedId()
        self.setStrategy(strategy)
        self.edited.emit()

    def setParameters(self, params):
        score = params.get("score", 0)
        strategy = params.get("strategy", UnivariateFeatureSelect.Fixed)
        self.setScoreIndex(score)
        self.setStrategy(strategy)
        if strategy == UnivariateFeatureSelect.Fixed:
            self.setK(params.get("k", 10))
        else:
            self.setP(params.get("p", 75))

    def parameters(self):
        score = self.__scoreidx
        strategy = self.__strategy
        p = self.__p
        k = self.__k

        return {"score": score, "strategy": strategy, "p": p, "k": k}


class FeatureSelectEditor(BaseEditor):

    MEASURES = [
        ("Information Gain", preprocess.score.InfoGain),
        ("Gain ratio", preprocess.score.GainRatio),
        ("Gini index", preprocess.score.Gini),
        ("ReliefF", preprocess.score.ReliefF),
        ("Fast Correlation Based Filter", preprocess.score.FCBF),
        ("ANOVA", preprocess.score.ANOVA),
        ("Chi2", preprocess.score.Chi2),
        ("RReliefF", preprocess.score.RReliefF),
        ("Univariate Linear Regression", preprocess.score.UnivariateLinearRegression)
    ]

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setLayout(QVBoxLayout())
        self.layout().setContentsMargins(0, 0, 0, 0)

        self.__score = 0
        self.__selecionidx = 0

        self.__uni_fs = UnivariateFeatureSelect()
        self.__uni_fs.setItems(
            [{"text": "Information Gain", "tooltip": ""},
             {"text": "Gain Ratio"},
             {"text": "Gini Index"},
             {"text": "ReliefF"},
             {"text": "Fast Correlation Based Filter"},
             {"text": "ANOVA"},
             {"text": "Chi2"},
             {"text": "RReliefF"},
             {"text": "Univariate Linear Regression"}
            ]
        )
        self.layout().addWidget(self.__uni_fs)
        self.__uni_fs.changed.connect(self.changed)
        self.__uni_fs.edited.connect(self.edited)

    def setParameters(self, params):
        self.__uni_fs.setParameters(params)

    def parameters(self):
        return self.__uni_fs.parameters()

    @staticmethod
    def createinstance(params):
        params = dict(params)
        score = params.pop("score", 0)
        score = FeatureSelectEditor.MEASURES[score][1]
        strategy = params.get("strategy", UnivariateFeatureSelect.Fixed)
        k = params.get("k", 10)
        p = params.get("p", 75.0)
        if strategy == UnivariateFeatureSelect.Fixed:
            return preprocess.fss.SelectBestFeatures(score(), k=k)
        elif strategy == UnivariateFeatureSelect.Proportion:
            return preprocess.fss.SelectBestFeatures(score(), k=p / 100)
        else:
            raise NotImplementedError

    def __repr__(self):
        params = self.__uni_fs.parameters()
        return "Score: {}, Strategy (Fixed): {}".format(
            self.MEASURES[params["score"]][0], params["k"])

# TODO: Model based FS (random forest variable importance, ...), RFE
# Unsupervised (min variance, constant, ...)??

class RandomFeatureSelectEditor(BaseEditor):
    #: Strategy
    Fixed, Percentage = 1, 2

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.setLayout(QVBoxLayout())

        self.__strategy = RandomFeatureSelectEditor.Fixed
        self.__k = 10
        self.__p = 75.0

        box = QGroupBox(title="Number of features", flat=True)
        self.__group = group = QButtonGroup(self, exclusive=True)
        self.__spins = {}

        form = QFormLayout()
        fixedrb = QRadioButton("Fixed", checked=True)
        group.addButton(fixedrb, RandomFeatureSelectEditor.Fixed)
        kspin = QSpinBox(
            minimum=1, maximum=1000000, value=self.__k,
            enabled=self.__strategy == RandomFeatureSelectEditor.Fixed
        )
        kspin.valueChanged[int].connect(self.setK)
        kspin.editingFinished.connect(self.edited)
        self.__spins[RandomFeatureSelectEditor.Fixed] = kspin
        form.addRow(fixedrb, kspin)

        percrb = QRadioButton("Percentage")
        group.addButton(percrb, RandomFeatureSelectEditor.Percentage)
        pspin = QDoubleSpinBox(
            minimum=0.0, maximum=100.0, singleStep=0.5,
            value=self.__p, suffix="%",
            enabled=self.__strategy == RandomFeatureSelectEditor.Percentage
        )
        pspin.valueChanged[float].connect(self.setP)
        pspin.editingFinished.connect(self.edited)
        self.__spins[RandomFeatureSelectEditor.Percentage] = pspin
        form.addRow(percrb, pspin)

        self.__group.buttonClicked.connect(self.__on_buttonClicked)
        box.setLayout(form)
        self.layout().addWidget(box)

    def setStrategy(self, strategy):
        if self.__strategy != strategy:
            self.__strategy = strategy
            b = self.__group.button(strategy)
            b.setChecked(True)
            for st, rb in self.__spins.items():
                rb.setEnabled(st == strategy)
            self.changed.emit()

    def setK(self, k):
        if self.__k != k:
            self.__k = k
            spin = self.__spins[RandomFeatureSelectEditor.Fixed]
            spin.setValue(k)
            if self.__strategy == RandomFeatureSelectEditor.Fixed:
                self.changed.emit()

    def setP(self, p):
        if self.__p != p:
            self.__p = p
            spin = self.__spins[RandomFeatureSelectEditor.Percentage]
            spin.setValue(p)
            if self.__strategy == RandomFeatureSelectEditor.Percentage:
                self.changed.emit()

    def __on_buttonClicked(self):
        strategy = self.__group.checkedId()
        self.setStrategy(strategy)
        self.edited.emit()

    def setParameters(self, params):
        strategy = params.get("strategy", RandomFeatureSelectEditor.Fixed)
        self.setStrategy(strategy)
        if strategy == RandomFeatureSelectEditor.Fixed:
            self.setK(params.get("k", 10))
        else:
            self.setP(params.get("p", 75.0))

    def parameters(self):
        strategy = self.__strategy
        p = self.__p
        k = self.__k

        return {"strategy": strategy, "p": p, "k": k}

    @staticmethod
    def createinstance(params):
        params = dict(params)
        strategy = params.get("strategy", RandomFeatureSelectEditor.Fixed)
        k = params.get("k", 10)
        p = params.get("p", 75.0)
        if strategy == RandomFeatureSelectEditor.Fixed:
            return preprocess.fss.SelectRandomFeatures(k=k)
        elif strategy == RandomFeatureSelectEditor.Percentage:
            return preprocess.fss.SelectRandomFeatures(k=p/100)
        else:
            # further implementations
            raise NotImplementedError


def index_to_enum(enum, i):
    """Enums, by default, are not int-comparable, so use an ad-hoc mapping of
    int to enum value at that position"""
    return list(enum)[i]


def enum_to_index(enum, key):
    """Enums, by default, are not int-comparable, so use an ad-hoc mapping of
    enum key to its int position"""
    return list(enum).index(key)

class Scale(BaseEditor):
    CenterByMean, ScaleBySD, NormalizeBySD, NormalizeBySpan_ZeroBased, \
        NormalizeSpan_NonZeroBased = 0, 1, 2, 3, 4

    Names = {
        NormalizeBySD: "Standardize to μ=0, σ²=1",
        CenterByMean: "Center to μ=0",
        ScaleBySD: "Scale to σ²=1",
        NormalizeSpan_NonZeroBased: "Normalize to interval [-1, 1]",
        NormalizeBySpan_ZeroBased: "Normalize to interval [0, 1]",
    }

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.setLayout(QVBoxLayout())

        self.__method = Scale.NormalizeBySD
        self.__group = group = QButtonGroup(self, exclusive=True)
        group.buttonClicked.connect(self.__on_buttonClicked)

        for methodid in [self.NormalizeBySD, self.CenterByMean, self.ScaleBySD,
                         self.NormalizeSpan_NonZeroBased,
                         self.NormalizeBySpan_ZeroBased]:
            text = self.Names[methodid]
            rb = QRadioButton(text=text, checked=self.__method == methodid)
            group.addButton(rb, methodid)
            self.layout().addWidget(rb)

    def setMethod(self, method):
        b = self.__group.button(method)
        if b is not None:
            b.setChecked(True)
            self.__method = method
            self.changed.emit()

    def setParameters(self, params):
        method = params.get("method", Scale.NormalizeBySD)
        self.setMethod(method)

    def parameters(self):
        return {"method": self.__method}

    def __on_buttonClicked(self):
        self.__method = self.__group.checkedId()
        self.changed.emit()
        self.edited.emit()

    @staticmethod
    def createinstance(params):
        method = params.get("method", Scale.NormalizeBySD)
        if method == Scale.CenterByMean:
            return _Scale(_Scale.CenteringType.Mean,
                          _Scale.ScalingType.NoScaling)
        elif method == Scale.ScaleBySD:
            return _Scale(_Scale.CenteringType.NoCentering,
                          _Scale.ScalingType.Std)
        elif method == Scale.NormalizeBySD:
            return Normalize(norm_type=Normalize.NormalizeBySD)
        elif method == Scale.NormalizeBySpan_ZeroBased:
            return Normalize(norm_type=Normalize.NormalizeBySpan)
        else:  # method == Scale.NormalizeSpan_NonZeroBased
            return Normalize(norm_type=Normalize.NormalizeBySpan,
                             zero_based=False)

    def __repr__(self):
        return self.Names[self.__method]


class Randomize(BaseEditor):
    RandomizeClasses, RandomizeAttributes, RandomizeMetas = _Randomize.Type

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.setLayout(QVBoxLayout())

        form = QFormLayout()
        self.__rand_type_cb = QComboBox()
        self.__rand_type_cb.addItems(["Classes",
                                      "Features",
                                      "Meta data"])

        self.__rand_type_cb.currentIndexChanged.connect(self.changed)
        self.__rand_type_cb.activated.connect(self.edited)

        self.__rand_seed_ch = QCheckBox()
        self.__rand_seed_ch.clicked.connect(self.edited)

        form.addRow("Randomize:", self.__rand_type_cb)
        form.addRow("Replicable shuffling:", self.__rand_seed_ch)
        self.layout().addLayout(form)

    def setParameters(self, params):
        rand_type = params.get("rand_type", Randomize.RandomizeClasses)
        self.__rand_type_cb.setCurrentIndex(
            enum_to_index(_Randomize.Type, rand_type))
        self.__rand_seed_ch.setChecked(params.get("rand_seed", 1) or 0)

    def parameters(self):
        return {"rand_type": index_to_enum(_Randomize.Type,
                                           self.__rand_type_cb.currentIndex()),
                "rand_seed": 1 if self.__rand_seed_ch.isChecked() else None}

    @staticmethod
    def createinstance(params):
        rand_type = params.get("rand_type", Randomize.RandomizeClasses)
        rand_seed = params.get("rand_seed", 1)
        return _Randomize(rand_type=rand_type, rand_seed=rand_seed)

    def __repr__(self):
        return "{}, {}".format(self.__rand_type_cb.currentText(),
                               "Replicable" if self.__rand_seed_ch.isChecked()
                               else "Not replicable")


class PCA(BaseEditor):

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.setLayout(QVBoxLayout())

        self.n_components = 10

        form = QFormLayout()
        self.cspin = QSpinBox(minimum=1, value=self.n_components)
        self.cspin.valueChanged[int].connect(self.setC)
        self.cspin.editingFinished.connect(self.edited)

        form.addRow("Components:", self.cspin)
        self.layout().addLayout(form)

    def setParameters(self, params):
        self.n_components = params.get("n_components", 10)

    def parameters(self):
        return {"n_components": self.n_components}

    def setC(self, n_components):
        if self.n_components != n_components:
            self.n_components = n_components
            self.cspin.setValue(n_components)
            self.changed.emit()

    @staticmethod
    def createinstance(params):
        n_components = params.get("n_components", 10)
        return ProjectPCA(n_components=n_components)

    def __repr__(self):
        return "Components: {}".format(self.cspin.value())


class CUR(BaseEditor):

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.setLayout(QVBoxLayout())

        self.rank = 10
        self.max_error = 1

        form = QFormLayout()
        self.rspin = QSpinBox(minimum=2, maximum=1000000, value=self.rank)
        self.rspin.valueChanged[int].connect(self.setR)
        self.rspin.editingFinished.connect(self.edited)
        self.espin = QDoubleSpinBox(
            minimum=0.1, maximum=100.0, singleStep=0.1,
            value=self.max_error)
        self.espin.valueChanged[float].connect(self.setE)
        self.espin.editingFinished.connect(self.edited)

        form.addRow("Rank:", self.rspin)
        form.addRow("Relative error:", self.espin)
        self.layout().addLayout(form)

    def setParameters(self, params):
        self.setR(params.get("rank", 10))
        self.setE(params.get("max_error", 1))

    def parameters(self):
        return {"rank": self.rank, "max_error": self.max_error}

    def setR(self, rank):
        if self.rank != rank:
            self.rank = rank
            self.rspin.setValue(rank)
            self.changed.emit()

    def setE(self, max_error):
        if self.max_error != max_error:
            self.max_error = max_error
            self.espin.setValue(max_error)
            self.changed.emit()

    @staticmethod
    def createinstance(params):
        rank = params.get("rank", 10)
        max_error = params.get("max_error", 1)
        return ProjectCUR(rank=rank, max_error=max_error)

    def __repr__(self):
        return "Rank: {}, Relative error: {}".format(self.rspin.value(),
                                                     self.espin.value())


# This is intended for future improvements.
# I.e. it should be possible to add/register preprocessor actions
# through entry points (for use by add-ons). Maybe it should be a
# general framework (this is not the only place where such
# functionality is desired (for instance in Orange v2.* Rank widget
# already defines its own entry point).
class Description:
    """
    A description of an action/function.
    """
    def __init__(self, title, icon=None, summary=None, input_=None, output=None,
                 requires=None, note=None, related=None, keywords=None,
                 helptopic=None):
        self.title = title
        self.icon = icon
        self.summary = summary
        self.input = input_
        self.output = output
        self.requires = requires
        self.note = note
        self.related = related
        self.keywords = keywords
        self.helptopic = helptopic


class PreprocessAction:
    def __init__(self, name, qualname, category, description, viewclass):
        self.name = name
        self.qualname = qualname
        self.category = category
        self.description = description
        self.viewclass = viewclass


def icon_path(basename):
    return pkg_resources.resource_filename(__name__, "icons/" + basename)


PREPROCESS_ACTIONS = [
    PreprocessAction(
        "Discretize", "orange.preprocess.discretize", "Discretization",
        Description("Discretize Continuous Variables",
                    icon_path("Discretize.svg")),
        DiscretizeEditor
    ),
    PreprocessAction(
        "Continuize", "orange.preprocess.continuize", "Continuization",
        Description("Continuize Discrete Variables",
                    icon_path("Continuize.svg")),
        ContinuizeEditor
    ),
    PreprocessAction(
        "Impute", "orange.preprocess.impute", "Impute",
        Description("Impute Missing Values",
                    icon_path("Impute.svg")),
        ImputeEditor
    ),
    PreprocessAction(
        "Feature Selection", "orange.preprocess.fss", "Feature Selection",
        Description("Select Relevant Features",
                    icon_path("SelectColumns.svg")),
        FeatureSelectEditor
    ),
    PreprocessAction(
        "Random Feature Selection", "orange.preprocess.randomfss",
        "Random Feature Selection",
        Description("Select Random Features",
                    icon_path("SelectColumnsRandom.svg")),
        RandomFeatureSelectEditor
    ),
    PreprocessAction(
        "Normalize", "orange.preprocess.scale", "Scale",
        Description("Normalize Features",
                    icon_path("Normalize.svg")),
        Scale
    ),
    PreprocessAction(
        "Randomize", "orange.preprocess.randomize", "Randomization",
        Description("Randomize",
                    icon_path("Random.svg")),
        Randomize
    ),
    PreprocessAction(
        "Remove Sparse", "orange.preprocess.remove_sparse", "Feature Selection",
        Description("Remove Sparse Features",
                    icon_path("PurgeDomain.svg")),
        RemoveSparseEditor
    ),
    PreprocessAction(
        "PCA", "orange.preprocess.pca", "PCA",
        Description("Principal Component Analysis",
                    icon_path("PCA.svg")),
        PCA
    ),
    PreprocessAction(
        "CUR", "orange.preprocess.cur", "CUR",
        Description("CUR Matrix Decomposition",
                    icon_path("SelectColumns.svg")),
        CUR
    )
]


# TODO: Extend with entry points here
# PREPROCESS_ACTIONS += iter_entry_points("Orange.widgets.data.owpreprocess")

# ####
# The actual owwidget
# ####

# Note:
# The preprocessors are drag/dropped onto a sequence widget, where
# they can be reordered/removed/edited.
#
# Model <-> Adapter/Controller <-> View
#
# * `Model`: the current constructed preprocessor model.
# * the source (of drag/drop) is an item model displayed in a list
#   view (source list).
# * the drag/drop is controlled by the controller/adapter,


class OWPreprocess(widget.OWWidget):
    name = "Preprocess"
    description = "Construct a data preprocessing pipeline."
    icon = "icons/Preprocess.svg"
    priority = 2105
    keywords = ["process"]

    settings_version = 2

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

    class Outputs:
        preprocessor = Output("Preprocessor", preprocess.preprocess.Preprocess, dynamic=False)
        preprocessed_data = Output("Preprocessed Data", Orange.data.Table)

    storedsettings = Setting({})
    autocommit = Setting(True)
    PREPROCESSORS = PREPROCESS_ACTIONS
    CONTROLLER = Controller

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

        self.data = None
        self._invalidated = False

        # List of available preprocessors (DescriptionRole : Description)
        self.preprocessors = QStandardItemModel()

        def mimeData(indexlist):
            assert len(indexlist) == 1
            index = indexlist[0]
            qname = index.data(DescriptionRole).qualname
            m = QMimeData()
            m.setData("application/x-qwidget-ref", qname.encode("utf-8"))
            return m
        # TODO: Fix this (subclass even if just to pass a function
        # for mimeData delegate)
        self.preprocessors.mimeData = mimeData

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

        self.preprocessorsView = view = QListView(
            selectionMode=QListView.SingleSelection,
            dragEnabled=True,
            dragDropMode=QListView.DragOnly
        )
        view.setModel(self.preprocessors)
        view.activated.connect(self.__activated)

        box.layout().addWidget(view)

        ####
        self._qname2ppdef = {ppdef.qualname: ppdef for
                             ppdef in self.PREPROCESSORS}

        # List of 'selected' preprocessors and their parameters.
        self.preprocessormodel = None

        self.flow_view = SequenceFlow()
        self.controler = self.CONTROLLER(self.flow_view, parent=self)

        self.overlay = OverlayWidget(self)
        self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents)
        self.overlay.setWidget(self.flow_view)
        self.overlay.setLayout(QVBoxLayout())
        self.overlay.layout().addWidget(
            QLabel("Drag items from the list on the left", wordWrap=True))

        self.scroll_area = QScrollArea(
            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn
        )
        self.scroll_area.viewport().setAcceptDrops(True)
        self.scroll_area.setWidget(self.flow_view)
        self.scroll_area.setWidgetResizable(True)
        self.mainArea.layout().addWidget(self.scroll_area)
        self.flow_view.installEventFilter(self)

        box = gui.vBox(self.controlArea, "Output")
        gui.auto_send(box, self, "autocommit", box=False)

        self._initialize()

    def _initialize(self):
        for pp_def in self.PREPROCESSORS:
            description = pp_def.description
            if description.icon:
                icon = QIcon(description.icon)
            else:
                icon = QIcon()
            item = QStandardItem(icon, description.title)
            item.setToolTip(description.summary or "")
            item.setData(pp_def, DescriptionRole)
            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable |
                          Qt.ItemIsDragEnabled)
            self.preprocessors.appendRow([item])

        model = self.load(self.storedsettings)

        self.set_model(model)

        if not model.rowCount():
            # enforce default width constraint if no preprocessors
            # are instantiated (if the model is not empty the constraints
            # will be triggered by LayoutRequest event on the `flow_view`)
            self.__update_size_constraint()

        self.apply()

    def load(self, saved):
        """Load a preprocessor list from a dict."""
        preprocessors = saved.get("preprocessors", [])
        model = StandardItemModel()

        def dropMimeData(data, action, row, _column, _parent):
            if data.hasFormat("application/x-qwidget-ref") and \
                    action == Qt.CopyAction:
                qname = bytes(data.data("application/x-qwidget-ref")).decode()

                ppdef = self._qname2ppdef[qname]
                item = QStandardItem(ppdef.description.title)
                item.setData({}, ParametersRole)
                item.setData(ppdef.description.title, Qt.DisplayRole)
                item.setData(ppdef, DescriptionRole)
                self.preprocessormodel.insertRow(row, [item])
                return True
            else:
                return False

        model.dropMimeData = dropMimeData

        for qualname, params in preprocessors:
            pp_def = self._qname2ppdef[qualname]
            description = pp_def.description
            item = QStandardItem(description.title)
            if description.icon:
                icon = QIcon(description.icon)
            else:
                icon = QIcon()
            item.setIcon(icon)
            item.setToolTip(description.summary)
            item.setData(pp_def, DescriptionRole)
            item.setData(params, ParametersRole)

            model.appendRow(item)
        return model

    @staticmethod
    def save(model):
        """Save the preprocessor list to a dict."""
        d = {"name": ""}
        preprocessors = []
        for i in range(model.rowCount()):
            item = model.item(i)
            pp_def = item.data(DescriptionRole)
            params = item.data(ParametersRole)
            preprocessors.append((pp_def.qualname, params))

        d["preprocessors"] = preprocessors
        return d


    def set_model(self, ppmodel):
        if self.preprocessormodel:
            self.preprocessormodel.dataChanged.disconnect(self.__on_modelchanged)
            self.preprocessormodel.rowsInserted.disconnect(self.__on_modelchanged)
            self.preprocessormodel.rowsRemoved.disconnect(self.__on_modelchanged)
            self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged)
            self.preprocessormodel.deleteLater()

        self.preprocessormodel = ppmodel
        self.controler.setModel(ppmodel)
        if ppmodel is not None:
            self.preprocessormodel.dataChanged.connect(self.__on_modelchanged)
            self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged)
            self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged)
            self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged)

        self.__update_overlay()

    def __update_overlay(self):
        if self.preprocessormodel is None or \
                self.preprocessormodel.rowCount() == 0:
            self.overlay.setWidget(self.flow_view)
            self.overlay.show()
        else:
            self.overlay.setWidget(None)
            self.overlay.hide()

    def __on_modelchanged(self):
        self.__update_overlay()
        self.commit()

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

    def handleNewSignals(self):
        self.apply()

    def __activated(self, index):
        item = self.preprocessors.itemFromIndex(index)
        action = item.data(DescriptionRole)
        item = QStandardItem()
        item.setData({}, ParametersRole)
        item.setData(action.description.title, Qt.DisplayRole)
        item.setData(action, DescriptionRole)
        self.preprocessormodel.appendRow([item])

    def buildpreproc(self):
        plist = []
        for i in range(self.preprocessormodel.rowCount()):
            item = self.preprocessormodel.item(i)
            desc = item.data(DescriptionRole)
            params = item.data(ParametersRole)

            if not isinstance(params, dict):
                params = {}

            create = desc.viewclass.createinstance
            plist.append(create(params))

        if len(plist) == 1:
            return plist[0]
        else:
            return preprocess.preprocess.PreprocessorList(plist)

    def apply(self):
        # Sync the model into storedsettings on every apply.
        self.storeSpecificSettings()
        preprocessor = self.buildpreproc()

        if self.data is not None:
            self.error()
            try:
                data = preprocessor(self.data)
            except (ValueError, ZeroDivisionError) as e:
                self.error(str(e))
                return
        else:
            data = None

        self.Outputs.preprocessor.send(preprocessor)
        self.Outputs.preprocessed_data.send(data)

    def commit(self):
        if not self._invalidated:
            self._invalidated = True
            QApplication.postEvent(self, QEvent(QEvent.User))

    def customEvent(self, event):
        if event.type() == QEvent.User and self._invalidated:
            self._invalidated = False
            self.apply()

    def eventFilter(self, receiver, event):
        if receiver is self.flow_view and event.type() == QEvent.LayoutRequest:
            QTimer.singleShot(0, self.__update_size_constraint)

        return super().eventFilter(receiver, event)

    def storeSpecificSettings(self):
        """Reimplemented."""
        self.storedsettings = self.save(self.preprocessormodel)
        super().storeSpecificSettings()

    def saveSettings(self):
        """Reimplemented."""
        self.storedsettings = self.save(self.preprocessormodel)
        super().saveSettings()

    @classmethod
    def migrate_settings(cls, settings, version):
        if version < 2:
            for action, params in settings["storedsettings"]["preprocessors"]:
                if action == "orange.preprocess.scale":
                    scale = center = None
                    if "center" in params:
                        center = params.pop("center").name
                    if "scale" in params:
                        scale = params.pop("scale").name
                    migratable = {
                        ("Mean", "NoScaling"): Scale.CenterByMean,
                        ("NoCentering", "Std"): Scale.ScaleBySD,
                        ("Mean", "Std"): Scale.NormalizeBySD,
                        ("NoCentering", "Span"): Scale.NormalizeBySpan_ZeroBased
                    }
                    params["method"] = \
                        migratable.get((center, scale), Scale.NormalizeBySD)

    def onDeleteWidget(self):
        self.data = None
        self.set_model(None)
        super().onDeleteWidget()

    @Slot()
    def __update_size_constraint(self):
        # Update minimum width constraint on the scroll area containing
        # the 'instantiated' preprocessor list (to avoid the horizontal
        # scroll bar).
        sh = self.flow_view.minimumSizeHint()
        scroll_width = self.scroll_area.verticalScrollBar().width()
        self.scroll_area.setMinimumWidth(
            min(max(sh.width() + scroll_width + 2, self.controlArea.width()),
                520))

    def sizeHint(self):
        sh = super().sizeHint()
        return sh.expandedTo(QSize(sh.width() + 300, 500))

    def send_report(self):
        pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w)
              for i, w in enumerate(self.controler.view.widgets())]
        if pp:
            self.report_items("Settings", pp)

if __name__ == "__main__":  # pragma: no cover
    WidgetPreview(OWPreprocess).run(Orange.data.Table("brown-selected"))

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user