source: orange/Orange/OrangeWidgets/Prototypes/OWGroupBy.py @ 10700:2f2afcde5404

Revision 10700:2f2afcde5404, 11.0 KB checked in by Peter Husen <peter@…>, 2 years ago (diff)

Added min, max, sum and count as aggregate options in Group By

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