1import random
2
3from AnyQt.QtCore import Qt
4from AnyQt.QtWidgets import QSizePolicy
5
6from Orange.data import Table
7from Orange.preprocess import Randomize
8from Orange.widgets.settings import Setting
9from Orange.widgets.utils.widgetpreview import WidgetPreview
10from Orange.widgets.widget import OWWidget, Input, Output
11from Orange.widgets import gui
12
13
14class OWRandomize(OWWidget):
15    name = "Randomize"
16    description = "Randomize features, class and/or metas in data table."
17    icon = "icons/Random.svg"
18    priority = 2100
19    keywords = []
20
21    class Inputs:
22        data = Input("Data", Table)
23
24    class Outputs:
25        data = Output("Data", Table)
26
27    resizing_enabled = False
28    want_main_area = False
29
30    shuffle_class = Setting(True)
31    shuffle_attrs = Setting(False)
32    shuffle_metas = Setting(False)
33    scope_prop = Setting(80)
34    random_seed = Setting(False)
35    auto_apply = Setting(True)
36
37    def __init__(self):
38        super().__init__()
39        self.data = None
40
41        # GUI
42        box = gui.hBox(self.controlArea, "Shuffled columns")
43        box.layout().setSpacing(20)
44        self.class_check = gui.checkBox(
45            box, self, "shuffle_class", "Classes",
46            callback=self._shuffle_check_changed)
47        self.attrs_check = gui.checkBox(
48            box, self, "shuffle_attrs", "Features",
49            callback=self._shuffle_check_changed)
50        self.metas_check = gui.checkBox(
51            box, self, "shuffle_metas", "Metas",
52            callback=self._shuffle_check_changed)
53
54        box = gui.vBox(self.controlArea, "Shuffled rows")
55        hbox = gui.hBox(box)
56        gui.widgetLabel(hbox, "None")
57        self.scope_slider = gui.hSlider(
58            hbox, self, "scope_prop", minValue=0, maxValue=100, width=140,
59            createLabel=False, callback=self._scope_slider_changed)
60        gui.widgetLabel(hbox, "All")
61        self.scope_label = gui.widgetLabel(
62            box, "", alignment=Qt.AlignCenter,
63            sizePolicy=(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed))
64        self._set_scope_label()
65        self.replicable_check = gui.checkBox(
66            box, self, "random_seed", "Replicable shuffling",
67            callback=self._shuffle_check_changed)
68
69        gui.auto_apply(self.buttonsArea, self, commit=self.apply)
70
71    @property
72    def parts(self):
73        return [self.shuffle_class, self.shuffle_attrs, self.shuffle_metas]
74
75    def _shuffle_check_changed(self):
76        self.apply()
77
78    def _scope_slider_changed(self):
79        self._set_scope_label()
80        self.apply()
81
82    def _set_scope_label(self):
83        self.scope_label.setText("{}%".format(self.scope_prop))
84
85    @Inputs.data
86    def set_data(self, data):
87        self.data = data
88        self.unconditional_apply()
89
90    def apply(self):
91        data = None
92        if self.data:
93            rand_seed = self.random_seed or None
94            size = int(len(self.data) * self.scope_prop / 100)
95            random.seed(rand_seed)
96            indices = sorted(random.sample(range(len(self.data)), size))
97            type_ = sum(t for t, p in zip(Randomize.Type, self.parts) if p)
98            randomized = Randomize(type_, rand_seed)(self.data[indices])
99            data = self.data.copy()
100            for i, instance in zip(indices, randomized):
101                data[i] = instance
102        self.Outputs.data.send(data)
103
104    def send_report(self):
105        labels = ["classes", "features", "metas"]
106        include = [label for label, i in zip(labels, self.parts) if i]
107        text = "none" if not include else \
108            " and ".join(filter(None, (", ".join(include[:-1]), include[-1])))
109        self.report_items(
110            "Settings",
111            [("Shuffled columns", text),
112             ("Proportion of shuffled rows", "{}%".format(self.scope_prop)),
113             ("Replicable", "yes" if self.random_seed else "no")])
114
115
116if __name__ == "__main__":  # pragma: no cover
117    WidgetPreview(OWRandomize).run(Table("iris"))
118