source: orange-bioinformatics/orangecontrib/bio/widgets/OWGenotypeDistances.py @ 1897:2e74f7146dac

Revision 1897:2e74f7146dac, 21.0 KB checked in by markotoplak, 6 months ago (diff)

Removed PIPA widget, renamed Genotype distances.

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