source: orange/Orange/OrangeWidgets/Prototypes/OWGroupBy.py @ 10941:a8f863fad5b8

Revision 10941:a8f863fad5b8, 11.0 KB checked in by Peter Husen <phusen@…>, 22 months ago (diff)

Changes to GroupBy widget:

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