# source:orange-bioinformatics/_bioinformatics/widgets/OWGenotypeDistances.py@1726:6778e0225b86

Revision 1726:6778e0225b86, 20.6 KB checked in by Ales Erjavec <ales.erjavec@…>, 17 months ago (diff)

Added new icons by Peter Cuhalev and replaced existing ones with expanded paths.

Line
1"""<name>Genotype Distances</name>
2<description>Compute distances between expression profiles of different experimental factors.</description>
3<icon>icons/GenotypeDistances.svg</icon>
4<priority>1050</priority>
5<contact>Ales Erjavec (ales.erjavec(@at@).fri.uni-lj.si)</contact>
6"""
7
8from __future__ import absolute_import
9
10from collections import defaultdict
12import math
13
14import numpy
15
16import Orange
17from Orange.OrangeWidgets import OWGUI
18from Orange.OrangeWidgets.OWItemModels import PyListModel
19from Orange.OrangeWidgets.OWWidget import *
20
21from ..obiExperiments import separate_by, data_type, linearize, dist_pcorr, dist_eucl, dist_spearman
22
23def clone_attr(attr):
24    newattr = attr.clone()
25    def get_value_from(ex, w=None):
26        if attr in ex.domain:
27            v = str(ex[attr])
28        else:
29            v = "?"
30        return newattr(v)
31
32    newattr.get_value_from = get_value_from
33    newattr.source_variable = attr
34    newattr.attributes.update(attr.attributes)
35    return newattr
36
37def name_gen(a):
38    i = 1
39    while True:
40        yield a + " {0}".format(i)
41        i += 1
42
43missing_name_gen = name_gen("!!missing")
44inactive_name_gen = name_gen("!!inactive")
45
46
48    def __init__(self, *args):
50
51    def mouseMoveEvent(self, event):
52        event.ignore()
53
54    def wheelEvent(self, event):
55        event.ignore()
56
57
58class SetContextHandler(ContextHandler):
59    def match(self, context, imperfect, items):
60        items = set(items)
61        saved_items = set(getattr(context, "items", []))
62        if imperfect:
63            return float(len(items.intersection(saved_items)))/(len(items.union(saved_items)) or 1)
64        else:
65            return items == saved_items
66
67    def findOrCreateContext(self, widget, items):
68        index, context, score = self.findMatch(widget, self.findImperfect, items)
69        if context:
70            if index < 0:
72            else:
73                self.moveContextUp(widget, index)
74            return context, False
75        else:
76            context = self.newContext()
77            context.items = items
79            return context, True
80
81class OWGenotypeDistances(OWWidget):
82    contextHandlers = {"": SetContextHandler("")}
83    settingsList = ["auto_commit"]
84
85    DISTANCE_FUNCTIONS = [("Distance from Pearson correlation", dist_pcorr),
86                          ("Euclidean distance", dist_eucl),
87                          ("Distance from Spearman correlation", dist_spearman)]
88
89    def __init__(self, parent=None, signalManager=None, title="Genotype Distances"):
90        OWWidget.__init__(self, parent, signalManager, title)
91
92        self.inputs = [("Example Table", ExampleTable, self.set_data)]
93        self.outputs = [("Distances", Orange.core.SymMatrix), ("Sorted Example Table", ExampleTable)]
94
95        self.distance_measure = 0
96        self.auto_commit = False
97        self.changed_flag = False
98
100
101        ########
102        # GUI
103        ########
104
105        self.info_box = OWGUI.widgetLabel(OWGUI.widgetBox(self.controlArea, "Input",
107                                         "No data on input\n")
108
109        box = OWGUI.widgetBox(self.controlArea, "Separate By",
111        self.separate_view = QListView()
112        self.separate_view.setSelectionMode(QListView.MultiSelection)
114
115        box = OWGUI.widgetBox(self.controlArea, "Sort By",
117        self.relevant_view = QListView()
118        self.relevant_view.setSelectionMode (QListView.MultiSelection)
120
121        self.distance_view = OWGUI.comboBox(self.controlArea, self, "distance_measure",
122                                            box="Distance Measure",
123                                            items=[d[0] for d in self.DISTANCE_FUNCTIONS])
124
125        OWGUI.rubber(self.controlArea)
126
127        box = OWGUI.widgetBox(self.controlArea, "Commit")
128        cb = OWGUI.checkBox(box, self, "auto_commit", "Commit on any change",
129                            tooltip="Compute and send the distances on any change.",
130                            callback=self.commit_if)
131
132        b = OWGUI.button(box, self, "Commit",
133                         tooltip="Compute the distances and send the output signals.",
134                         callback=self.commit,
135                         default=True)
136
137        OWGUI.setStopper(self, b, cb, "changed_flag", callback=self.commit)
138
139        self.groups_box = OWGUI.widgetBox(self.mainArea, "Groups")
140        self.groups_scroll_area = QScrollArea()
142
143        self.data = None
144        self.partitions = []
145        self.matrix = None
146        self.split_groups = []
148
149        self.resize(800, 600)
150
151    def clear(self):
152        self.data = None
153        self.partitions = []
154        self.split_groups = []
155        self.matrix = None
156        self.send("Distances", None)
157
158    def get_suitable_keys(self, data):
159        """ Return suitable attr label keys from the data where the key has at least
160        two unique values in the data.
161
162        """
163        attrs = [attr.attributes.items() for attr in data.domain.attributes]
164        attrs  = reduce(list.__add__, attrs, [])
165        # in case someone put non string values in attributes dict
166        attrs = [(str(key), str(value)) for key, value in attrs]
167        attrs = set(attrs)
168        values = defaultdict(set)
169        for key, value in attrs:
171        keys = [key for key in values if len(values[key]) > 1]
172        return keys
173
174    def set_data(self, data=None):
175        """ Set the input example table.
176        """
177        self.closeContext()
178        self.clear()
179        self.data = data
180        self.error(0)
181        self.warning(0)
182        if data and not self.get_suitable_keys(data):
183            self.error(0, "Data has no suitable attribute labels.")
184            data = None
185
186        if data:
187            self.info_box.setText("{0} genes\n{1} experiments".format(len(data), len(data.domain)))
188            self.update_control()
189            self.split_data()
190        else:
191            self.separate_view.setModel(PyListModel([]))
192            self.relevant_view.setModel(PyListModel([]))
193            self.groups_scroll_area.setWidget(QWidget())
194            self.info_box.setText("No data on input.\n")
195            self.commit()
196
197    def update_control(self):
198        """ Update the control area of the widget. Populate the list
199        views with keys from attribute labels.
200
201        """
202        keys = self.get_suitable_keys(self.data)
203
204        model = PyListModel(keys)
205        self.separate_view.setModel(model)
206        self.connect(self.separate_view.selectionModel(),
207                     SIGNAL("selectionChanged(QItemSelection, QItemSelection)"),
208                     self.on_separate_key_changed)
209
210        model = PyListModel(keys)
211        self.relevant_view.setModel(model)
212        self.connect(self.relevant_view.selectionModel(),
213                     SIGNAL("selectionChanged(QItemSelection, QItemSelection)"),
214                     self.on_relevant_key_changed)
215
216        self.openContext("", keys)
217
218        # Get the selected keys from the open context
219        context = self.currentContexts[""]
220        separate_keys = getattr(context, "separate_keys", set())
221        relevant_keys = getattr(context, "relevant_keys", set())
222
223        def select(model, selection_model, selected_items):
224            all_items = list(model)
225            try:
226                indices = [all_items.index(item) for item in selected_items]
227            except:
228                indices = []
229            for ind in indices:
230                selection_model.select(model.index(ind), QItemSelectionModel.Select)
231
233        try:
234            select(self.relevant_view.model(),
235                   self.relevant_view.selectionModel(),
236                   relevant_keys)
237
238            select(self.separate_view.model(),
239                   self.separate_view.selectionModel(),
240                   separate_keys)
241        finally:
243
244    def on_separate_key_changed(self, *args):
246            context = self.currentContexts[""]
247            context.separate_keys = self.selected_separeate_by_keys()
248            self.split_data()
249
250    def on_relevant_key_changed(self, *args):
252            context = self.currentContexts[""]
253            context.relevant_keys = self.selected_relevant_keys()
254            self.split_data()
255
256    def selected_separeate_by_keys(self):
257        """ Return the currently selected separate by keys
258        """
259        rows = self.separate_view.selectionModel().selectedRows()
260        rows = sorted([idx.row() for idx in rows])
261        keys = [self.separate_view.model()[row] for row in rows]
262        return keys
263
264    def selected_relevant_keys(self):
265        """ Return the currently selected relevant keys
266        """
267        rows = self.relevant_view.selectionModel().selectedRows()
268        rows = sorted([idx.row() for idx in rows])
269        keys = [self.relevant_view.model()[row] for row in rows]
270        return keys
271
272    def split_data(self):
273        """ Split the data and update the Groups widget
274        """
275        separate_keys = self.selected_separeate_by_keys()
276        relevant_keys = self.selected_relevant_keys()
277
278        self.warning(0)
279        if not separate_keys:
280            self.warning(0, "No separate by attribute selected.")
281
282        partitions,uniquepos = separate_by(self.data, separate_keys, consider=relevant_keys)
283        partitions = partitions.items()
284
285        all_values = defaultdict(set)
286        for a in [ at.attributes for at in self.data.domain.attributes ]:
287            for k,v in a.iteritems():
289
290        #sort groups
291        pkeys = [ key for key,_ in partitions ]
292        types = [ data_type([a[i] for a in pkeys]) for i in range(len(pkeys[0])) ]
293
294        partitions = sorted(partitions, key=lambda x:
295                    tuple(types[i](v) for i,v in enumerate(x[0])))
296
297        split_groups = []
298
299        # Collect relevant key value pairs for all columns
300        relevant_items = None
301
302        for keys, indices in partitions:
303            if relevant_items == None:
304                relevant_items = [ defaultdict(set) for _ in range(len(indices)) ]
305            for i, ind in enumerate(indices):
306                if ind is not None:
307                    attr = self.data.domain[ind]
308                    for key in relevant_keys:
310
311        #those with different values between rows are not relevant
312        for d in relevant_items:
313            for k,s in d.items():
314                if len(s) > 1:
315                    del d[k]
316                else:
317                    d[k] = s.pop()
318
319        def get_attr(attr_index, i):
320            if attr_index is None:
321                attr = Orange.feature.Continuous(missing_name_gen.next())
322                attr.attributes.update(relevant_items[i])
323                return attr
324            else:
325                return self.data.domain[attr_index]
326
327        for keys, indices in partitions:
328            attrs = [get_attr(attr_index, i) for i, attr_index in enumerate(indices)]
329            for attr in attrs:
330                attr.attributes.update(zip(separate_keys, keys))
331            domain = Orange.data.Domain(attrs, None)
333#            newdata = Orange.data.Table(domain)
334            split_groups.append((keys, domain))
335
336        self.set_groups(separate_keys, split_groups, relevant_keys, relevant_items, all_values, uniquepos)
337
338        self.partitions = partitions
339        self.split_groups = split_groups
340
341        self.commit_if()
342#        self.update_distances(separate_keys, partitions, self.data)
343
344    def set_groups(self, keys, groups, relevant_keys, relevant_items, all_values, uniquepos):
345        """ Set the current data groups and update the Group widget
346        """
347        layout = QVBoxLayout()
350        palette = self.palette()
351        all_values = all_values.keys()
352
353        def for_print(rd):
354            attrs = []
355            for d in rd:
356                attr = Orange.feature.Continuous(inactive_name_gen.next())
357                attr.attributes.update(d)
358                attrs.append(attr)
359            return Orange.data.Domain(attrs, None)
360
361        for separatev, domain in [ (None, for_print(relevant_items)) ] + groups:
362            label = None
363            if separatev != None:
364                ann_vals = " <b>|</b> ".join(["<b>{0}</ b> = {1}".format(key,val) \
365                     for key, val in zip(keys, separatev)])
366                label = QLabel(ann_vals)
367
368            model = QStandardItemModel()
369            for i, attr in enumerate(domain.attributes):
370                item = QStandardItem()
371                if separatev != None:
372                    up = uniquepos[separatev][i]
373                else:
374                    up = False if False in [ a[i] for a in uniquepos.values() ] else True
375                if str(attr.name).startswith("!!missing "): ## TODO: Change this to not depend on name
376                    header_text = ["{0}={1}".format(key, attr.attributes.get(key, "?")) \
377                                   for key in all_values if key not in relevant_items[i]]
380                    item.setFlags(Qt.NoItemFlags)
381                    item.setData(QVariant(QColor(Qt.red)), Qt.ForegroundRole)
382                    item.setData(QVariant(palette.color(QPalette.Disabled, QPalette.Window)), Qt.BackgroundRole)
383                    item.setData(QVariant("Missing feature."), Qt.ToolTipRole)
384                elif str(attr.name).startswith("!!inactive "):
385                    header_text = ["{0}={1}".format(key, attr.attributes.get(key, "?")) \
386                                   for key in all_values if key in relevant_items[i]]
389                    item.setData(QVariant(palette.color(QPalette.Disabled, QPalette.Window)), Qt.BackgroundRole)
390                else:
391                    header_text = ["{0}={1}".format(key, attr.attributes.get(key, "?")) \
392                                   for key in all_values if key not in relevant_items[i]]
395                    item.setData(QVariant(attr.name), Qt.ToolTipRole)
396
397                if up == False:
398                    item.setData(QVariant(QColor(Qt.red)), Qt.ForegroundRole)
399
401            attr_count = len(domain.attributes)
404            view.setModel(model)
405            hint = view.sizeHint()
406            view.setMaximumHeight(hint.height())
407
408            widths = [view.sectionSizeHint(i) for i in range(attr_count)]
411
412            if label:
416
417        # Make all header sections the same width
418        width_sum = 0
421            max_width = max([w[i] for w in header_widths if i < len(w)] or [0])
423                if i < view.count():
424                    view.resizeSection(i, max_width)
425            width_sum += max_width + 2
426
428            h.setMinimumWidth(h.length() + 4)
429
430        widget = QWidget()
431        widget.setLayout(layout)
432        widget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum)
433        layout.activate()
434
435        max_width = max(h.length() for h in header_views) + 20
436
437        left, top, right, bottom  = self.getContentsMargins()
438        widget.setMinimumWidth(width_sum)
439        widget.setMinimumWidth(max_width + left + right)
440        self.groups_scroll_area.setWidget(widget)
441
442    def compute_distances(self, separate_keys, partitions, data):
443        """ Compute the distances between genotypes.
444        """
445        if separate_keys and partitions:
446            self.progressBarInit()
447            matrix = Orange.core.SymMatrix(len(partitions))
448            profiles = [linearize(data, indices) for _, indices in partitions]
449            dist_func = self.DISTANCE_FUNCTIONS[self.distance_measure][1]
450            from Orange.utils import progress_bar_milestones
451            count = (len(profiles) * len(profiles) - 1) / 2
452            milestones = progress_bar_milestones(count)
453            iter_count = 0
454            for i in range(len(profiles)):
455                for j in range(i + 1, len(profiles)):
456                    matrix[i, j] = dist_func(profiles[i], profiles[j])
457                    iter_count += 1
458                    if iter_count in milestones:
459                        self.progressBarSet(100.0 * iter_count / count)
460            self.progressBarFinished()
461
462            items = [["{0}={1}".format(key, value) for key, value in zip(separate_keys, values)] \
463                      for values, _ in partitions]
464            items = [" | ".join(item) for item in items]
465            matrix.setattr("items", items)
466        else:
467            matrix = None
468
469        self.matrix = matrix
470
471    def commit_if(self):
472        if self.auto_commit and self.changed_flag:
473            self.commit()
474        else:
475            self.changed_flag = True
476
477    def commit(self):
478        separate_keys = self.selected_separeate_by_keys()
479        self.compute_distances(separate_keys,
480                               self.partitions,
481                               self.data)
482
483        if self.split_groups:
484            all_attrs = []
485            for group, domain in self.split_groups:
486                attrs = []
487                group_name = " | ".join("{0}={1}".format(*item) for item in \
488                                        zip(separate_keys, group))
489                for attr in domain.attributes:
490                    newattr = clone_attr(attr)
491                    newattr.attributes["<GENOTYPE GROUP>"] = group_name # Need a better way to pass the groups to downstream widgets.
492                    attrs.append(newattr)
493
494                all_attrs.extend(attrs)
495
496            #all_attrs = reduce(add, [list(domain.attributes) for _, domain in self.split_groups], [])
497            domain = Orange.data.Domain(all_attrs, self.data.domain.class_var)
499
500            data = Orange.data.Table(domain, self.data)
501        else:
502            data = None
503        self.send("Sorted Example Table", data)
504        self.send("Distances", self.matrix)
505        self.changed_flag = False
506
507if __name__ == "__main__":
508    import os, sys
509    app = QApplication(sys.argv )
510    w = OWGenotypeDistances()
511#    data = Orange.data.Table(os.path.expanduser("~/Documents/dicty-express-sample.tab"))
513    data = Orange.data.Table(os.path.expanduser("~/tgr.tab"))
514    w.set_data(data)
515    w.show()
516    app.exec_()
517    w.saveSettings()
518
519#    data = Orange.data.Table("tmp.tab")
520#    partitions = separate_by(data, [ "genotype" ], consider=["tp", "replicate"]).items()
521#    print partitions
522#    l1 = linearize(data, partitions[0][1])
523#    l2 = linearize(data, partitions[1][1])
524#    print  dist_eucl(l1, l2)
525#    print  dist_pcorr(l1, l2)
526
527
528
Note: See TracBrowser for help on using the repository browser.