source: orange/orange/OrangeWidgets/Prototypes/OWGroupBy.py @ 9546:2b6cc6f397fe

Revision 9546:2b6cc6f397fe, 10.9 KB checked in by ales_erjavec <ales.erjavec@…>, 2 years ago (diff)

Renamed widget channel names in line with the new naming rules/convention.
Added backwards compatibility in orngDoc loadDocument to enable loading of schemas saved before the change.

Line 
1"""<name>Group By</name>
2<description>Group instances by selected columns</description>
3<icons>icons/GroupBy.png</icons>
4
5"""
6
7from OWWidget import *
8from OWItemModels import VariableListModel
9
10import Orange
11from Orange.data import Table, Domain, variable, utils
12
13AggregateMethodRole = OWGUI.OrangeUserRole.next()
14
15DISC_METHODS = \
16    [("Modus", ),
17     ("Random", ),
18     ("First", ),
19     ("Last", ),
20     ]
21
22CONT_METHODS = \
23    [("Mean", ),
24     ("Modus", ),
25     ("Geometric Mean", ),
26     ("Harmonic Mean", ),
27     ("Random", ),
28     ("First", ),
29     ("Last", ),
30     ]
31   
32STR_METHODS = \
33    [("Join", ),
34     ("Random", ),
35     ("First", ),
36     ("Last", ),
37     ]
38   
39PYTHON_METHODS = \
40    [("Random", ),
41     ("First", ),
42     ("Last", ),
43     ]
44   
45DEFAULT_METHOD = "First"
46   
47   
48class OWGroupBy(OWWidget):
49    contextHandlers = {"": DomainContextHandler("", ["hints"])}
50    settingsList = []
51    def __init__(self, parent=None, signalManager=None, title="Group By"):
52        OWWidget.__init__(self, parent, signalManager, title, 
53                          wantMainArea=False)
54       
55        self.inputs = [("Data", Table, self.set_data)]
56        self.outputs = [("Data", Table)]
57       
58        self.auto_commit = False
59        self.hints = {}
60       
61        self.state_chaged_flag = False
62       
63        self.loadSettings()
64       
65        #############
66        # Data Models
67        #############
68       
69        self.group_list = VariableListModel(parent=self, 
70                            flags=Qt.ItemIsEnabled | Qt.ItemIsSelectable | \
71                            Qt.ItemIsDragEnabled )
72        self.aggregate_list = VariableAggragateModel(parent=self,
73                            flags=Qt.ItemIsEnabled | Qt.ItemIsSelectable | \
74                            Qt.ItemIsDragEnabled | \
75                            Qt.ItemIsEditable)
76       
77        self.aggregate_delegate = AggregateDelegate()
78       
79        #####
80        # GUI
81        #####
82       
83        box = OWGUI.widgetBox(self.controlArea, "Group By Attributes")
84        self.group_view = QListView()
85        self.group_view.setSelectionMode(QListView.ExtendedSelection)
86        self.group_view.setDragDropMode(QListView.DragDrop)
87        self.group_view.setModel(self.group_list)
88#        self.group_view.setDragDropOverwriteMode(True)
89        self.group_view.setDefaultDropAction(Qt.MoveAction)
90        self.group_view.viewport().setAcceptDrops(True)
91#        self.group_view.setDropIndicatorShown(True)
92        self.group_view.setToolTip("A set of attributes to group by (drag \
93values to 'Aggregate Attributes' to remove them from this group).")
94        box.layout().addWidget(self.group_view)
95       
96        box = OWGUI.widgetBox(self.controlArea, "Aggregate Attributes")
97        self.aggregate_view = AggregateListView()
98        self.aggregate_view.setSelectionMode(QListView.ExtendedSelection)
99        self.aggregate_view.setDragDropMode(QListView.DragDrop)
100        self.aggregate_view.setItemDelegate(self.aggregate_delegate)
101        self.aggregate_view.setModel(self.aggregate_list)
102        self.aggregate_view.setEditTriggers(QListView.SelectedClicked)
103#        self.aggregate_view.setDragDropOverwriteMode(False)
104        self.aggregate_view.setDefaultDropAction(Qt.MoveAction)
105        self.aggregate_view.viewport().setAcceptDrops(True)
106        self.aggregate_view.setToolTip("Aggregated attributes.")
107       
108        box.layout().addWidget(self.aggregate_view)
109       
110        OWGUI.rubber(self.controlArea)
111        box = OWGUI.widgetBox(self.controlArea, "Commit")
112#        cb = OWGUI.checkBox(box, self, "auto_commit", "Commit on any change.",
113#                            tooltip="Send the data on output on any change of settings or inputs.",
114#                            callback=self.commit_if
115#                            )
116        b = OWGUI.button(box, self, "Commit", callback=self.commit, 
117                         tooltip="Send data on output.", 
118                         autoDefault=True)
119#        OWGUI.setStopper(self, b, cb, "state_chaged_flag",
120#                         callback=self.commit)
121       
122    def set_data(self, data=None):
123        """ Set the input data for the widget.
124        """
125        self.update_hints()
126        self.closeContext("")
127        self.clear()
128        if data is not None:
129            self.init_with_data(data)
130            self.openContext("", data)
131            self.init_state_from_hints()
132       
133        self.commit_if()
134       
135    def init_with_data(self, data):
136        """ Init widget state from data
137        """
138        attrs = data.domain.variables + data.domain.get_metas().values()
139#        self.group_list.set_items(attrs)
140        self.all_attrs = attrs
141        self.hints = dict([((a.name, a.varType), ("group_by", "First")) for a in attrs])
142        self.data = data
143       
144    def init_state_from_hints(self):
145        """ Init the group and aggregate from hints (call after openContext)
146        """
147        group = []
148        aggregate = []
149        aggregate_hint = {}
150        for a in self.all_attrs:
151            try:
152                place, hint = self.hints.get((a.name, a.var_type), ("group_by", DEFAULT_METHOD))
153            except Exception:
154                place, hint = ("group_by", DEFAULT_METHOD)
155            if place == "group_by":
156                group.append(a)
157            else:
158                aggregate.append(a)
159            aggregate_hint[a] = hint
160        self.group_list[:] = group
161        self.aggregate_list[:] = aggregate
162       
163        for i, a in enumerate(group):
164            self.group_list.setData(self.group_list.index(i),
165                                    aggregate_hint[a],
166                                    AggregateMethodRole)
167           
168        for i, a in enumerate(aggregate):
169            self.aggregate_list.setData(self.aggregate_list.index(i),
170                                        aggregate_hint[a],
171                                        AggregateMethodRole)
172       
173    def update_hints(self):
174        for i, var in enumerate(self.group_list):
175            self.hints[var.name, var.var_type] = \
176                ("group_by", str(self.group_list.data( \
177                                         self.group_list.index(i),
178                                         AggregateMethodRole).toPyObject()))
179               
180        for i, var in enumerate(self.aggregate_list):
181            self.hints[var.name, var.var_type] = \
182                ("aggregate", str(self.aggregate_list.data( \
183                                        self.aggregate_list.index(i),
184                                        AggregateMethodRole).toPyObject()))
185       
186       
187    def clear(self):
188        """ Clear the widget state.
189        """
190        self.data = None
191        self.group_list[:] = [] #clear()
192        self.aggregate_list[:] = [] #.clear()
193        self.all_attrs = []
194        self.hints = {}
195       
196    def get_aggregates_from_hints(self):
197        aggregates = {}
198        for i, v in enumerate(self.aggregate_list):
199            _, hint = self.hints.get((v.name, v.var_type), ("", DEFAULT_METHOD))
200           
201            aggregates[v] = hint.lower()
202        return aggregates
203   
204    def commit_if(self):
205        if self.auto_commit:
206            self.commit()
207        else:
208            self.state_chaged_flag = True
209           
210    def commit(self):
211        self.update_hints()
212        if self.data is not None:
213            group = list(self.group_list)
214            aggregates = self.get_aggregates_from_hints()
215            print aggregates
216            data = utils.group_by(self.data, group, attr_aggregate=aggregates)
217        else:
218            data = None
219        self.send("Data", data)
220        self.state_chaged_flag = False
221       
222
223def aggregate_options(var):
224    if isinstance(var, variable.Discrete):
225        items = [m[0] for m in DISC_METHODS]
226    elif isinstance(var, variable.Continuous):
227        items = [m[0] for m in CONT_METHODS]
228    elif isinstance(var, variable.String):
229        items = [m[0] for m in STR_METHODS]
230    elif isinstance(var, variable.Python):
231        items = [m[0] for m in PYTHON_METHODS]
232    else:
233        items = []
234    return items
235
236
237class AggregateDelegate(QStyledItemDelegate):
238    def __init__(self, *args, **kwargs):
239        QStyledItemDelegate.__init__(self, *args, **kwargs)
240       
241    def paint(self, painter, option, index):
242        val = index.data(Qt.EditRole).toPyObject()
243        method = index.data(AggregateMethodRole)
244        if method.isValid():
245            met = method.toPyObject()
246        else:
247            met = ""
248        option.text = QString(val.name + str(met))
249        QStyledItemDelegate.paint(self, painter, option, index)
250       
251    def createEditor(self, parent, option, index):
252        editor = QComboBox(parent)
253        editor.setFocusPolicy(Qt.StrongFocus)
254        return editor
255       
256    def setEditorData(self, editor, index):
257        var = index.data(Qt.EditRole).toPyObject() 
258        options = aggregate_options(var)
259        current = index.data(AggregateMethodRole).toPyObject()
260        current_index = options.index(current) if current in options else 0
261        editor.clear()
262        editor.addItems(options)
263        editor.setCurrentIndex(current_index)
264        QObject.connect(editor, SIGNAL("activated(int)"), 
265                    lambda i:self.emit(SIGNAL("commitData(QWidget)"), editor))
266       
267    def setModelData(self, editor, model, index):
268        method = str(editor.currentText())
269        model.setData(index, QVariant(method), AggregateMethodRole)
270       
271       
272class AggregateListView(QListView):
273    def contextMenuEvent(self, event):
274        selection_model = self.selectionModel()
275        model = self.model()
276        rows = selection_model.selectedRows()
277        rows = [r.row() for r in rows]
278        if rows:
279            options = [aggregate_options(model[i]) for i in rows]
280            options = reduce(set.intersection, map(set, options[1:]), set(options[0]))
281            menu = QMenu(self)
282            for option in options:
283                menu.addAction(option)
284            selected = menu.exec_(event.globalPos())
285            if selected:
286                name = selected.text()
287                for i in rows:
288                    model.setData(model.index(i), QVariant(name), AggregateMethodRole)
289                   
290       
291class VariableAggragateModel(VariableListModel):
292    def data(self, index, role=Qt.DisplayRole):
293        i = index.row()
294        if role == Qt.DisplayRole:
295            var_name = self.__getitem__(i).name
296            met = self.data(index, AggregateMethodRole)
297            if met.isValid():
298                met = " (%s)" % str(met.toString())
299            else:
300                met = ""
301            return QVariant(var_name + met)
302        else:
303            return VariableListModel.data(self, index, role=role)
304       
305if __name__ == "__main__":
306    import sys
307    app = QApplication(sys.argv)
308    w = OWGroupBy()
309    w.show()
310    data = Orange.data.Table("lenses.tab")
311    w.set_data(data)
312    w.set_data(None)
313    w.set_data(data)
314    app.exec_()
315    w.set_data(None)
316    w.saveSettings()
317   
Note: See TracBrowser for help on using the repository browser.