source: orange/Orange/OrangeWidgets/Prototypes/OWGroupBy.py @ 10156:6726b609e76c

Revision 10156:6726b609e76c, 10.9 KB checked in by markotoplak, 2 years ago (diff)

Fixed changes from data.variable -> feature in some widgets.

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