source: orange/Orange/OrangeWidgets/Prototypes/OWTranspose.py @ 10704:315ab8e8beb4

Revision 10704:315ab8e8beb4, 7.5 KB checked in by Ales Erjavec <ales.erjavec@…>, 2 years ago (diff)

Added Transpose widget to the prototypes category.

Line 
1"""
2<name>Transpose</name>
3<description>Transpose a data table</description>
4<icon>icons/Transpose.png</icon>
5
6"""
7
8from OWWidget import *
9from OWItemModels import VariableListModel
10import OWGUI
11
12import Orange
13from Orange import feature
14import warnings
15from operator import add
16
17def float_or_na(val):
18    if val.is_special():
19        return "NA"
20    else:
21        return float(val)
22
23class VariableOrNoneListModel(VariableListModel):
24    def data(self, index, role=Qt.DisplayRole):
25        i = index.row()
26        var = self[i]
27        if var is None:
28            if role == Qt.DisplayRole:
29                return QVariant("(None)")
30            elif role == Qt.ToolTipRole:
31                return QVariant("None - use default naming instead.")
32        else:
33            return VariableListModel.data(self, index, role)
34
35def transpose(data, feature_names=None, error_non_continuous="error",
36              error_meta="ignore"):
37    domain = data.domain
38    attributes = list(domain.variables)
39    metas = domain.get_metas().values()
40    if any(not isinstance(f, feature.Continuous) \
41                for f in attributes):
42        if error_non_continuous == "error":
43            raise TypeError()
44        elif error_non_continuous == "warn":
45            warnings.warn("Non-continuous features in the domain will be ignored!",
46                          UserWarning)
47        elif error_non_continuous == "ignore":
48            pass
49        else:
50            raise ValueError(error_discrete)
51        attributes = [attr for attr in attributes \
52                      if isinstance(attr, feature.Continuous)]
53
54    if any(not isinstance(m, feature.String) \
55                for m in metas):
56        if error_meta == "error":
57            raise TypeError("Non string meta features.")
58        elif error_meta == "ignore":
59            metas = [m for m in metas if isinstance(m, feature.String)]
60        elif error_meta == "warn":
61            warnings.warn("Non string meta features in the domain will be ignored!",
62                          UserWarning)
63        else:
64            raise ValueError(error_meta)
65
66    if feature_names is not None:
67        if isinstance(feature_names, basestring):
68            # Name of the feature
69            feature_names = domain[feature_names]
70        if isinstance(feature_names, feature.String):
71            feature_names = [str(inst[feature_names]) for inst in data]
72        elif isinstance(feature_names, list):
73            # List of names
74            pass
75        else:
76            raise ValueError(feature_names)
77    else:
78        feature_names = ["F_%s" % (i + 1) for i in range(len(data))]
79
80    new_features = map(feature.Continuous, feature_names)
81    labels = [f.attributes.keys() for f in domain.variables]
82    labels = sorted(reduce(set.union, labels, set()))
83
84    new_metas = map(feature.String, labels)
85    new_metas = dict((feature.Descriptor.new_meta_id(), m) for m in new_metas)
86
87    new_labels = [m.name for m in metas]
88    new_domain = Orange.data.Domain(new_features, False)
89
90    new_domain.add_metas(new_metas)
91
92    new_data = Orange.data.Table(new_domain)
93
94    for f in attributes:
95        vals = [float_or_na(inst[f]) for inst in data]
96        new_ins = Orange.data.Instance(new_domain, vals)
97
98        for key, value in f.attributes.items():
99            new_ins[key] = str(value)
100
101        new_data.append(new_ins)
102
103    for new_f, inst in zip(new_features, data):
104        for m in metas:
105            mval = inst[m]
106            if not mval.is_special():
107                new_f.attributes[m.name] = str(mval)
108
109    return new_data
110
111def is_cont(f):
112    return isinstance(f, feature.Continuous)
113
114def is_disc(f):
115    return isinstance(f, feature.Discrete)
116
117def is_string(f):
118    return isinstance(f, feature.String)
119
120class OWTranspose(OWWidget):
121    contextHandlers = {"": DomainContextHandler("", ["row_name_attr"])}
122    def __init__(self, parent=None, signalManager=None, title="Transpose"):
123        OWWidget.__init__(self, parent, signalManager, title,
124                          wantMainArea=False)
125
126        self.inputs = [("Data", Orange.data.Table, self.set_data)]
127        self.outputs = [("Transposed Data", Orange.data.Table)]
128
129        # Settings
130        self.row_name_attr = None
131
132        box = OWGUI.widgetBox(self.controlArea, "Info")
133        self.info_w = OWGUI.widgetLabel(box, "No data on input.")
134
135        box = OWGUI.widgetBox(self.controlArea, "Row Names")
136        self.row_name_combo = QComboBox(self, objectName="row_name_combo",
137                                toolTip="Row to use for new feature names.",
138                                activated=self.on_row_name_changed)
139        self.row_name_model = VariableOrNoneListModel()
140        self.row_name_combo.setModel(self.row_name_model)
141        box.layout().addWidget(self.row_name_combo)
142
143        OWGUI.rubber(self.controlArea)
144
145    def clear(self):
146        """Clear the widget state."""
147        self.warning(1)
148        self.error(0)
149        self.row_name_attr = None
150        self.row_name_model[:] = []
151        self.data = None
152
153    def set_data(self, data=None):
154        self.closeContext("")
155        self.clear()
156        self.data = data
157        if data is not None:
158            variables = data.domain.variables
159            cont_features = [f for f in variables if is_cont(f)]
160            non_cont = [f for f in variables if not is_cont(f)]
161            info = "Data with %i continuous feature%s\n" \
162                    % (len(cont_features), "s" if len(cont_features) > 1 else "")
163            if non_cont:
164                ignore_text = "Ignoring %i non-continuous feature%s" \
165                                % (len(non_cont), "s" if len(non_cont) > 1 else "")
166                info += ignore_text
167                self.warning(1, ignore_text)
168
169            self.info_w.setText(info)
170
171            all_vars = data.domain.variables + data.domain.get_metas().values()
172            str_features = [f for f in all_vars if is_string(f)]
173            str_feature_names = [f.name for f in str_features]
174
175            if len(str_feature_names):
176                self.row_name_attr = str_feature_names[0]
177
178            self.openContext("", data)
179
180            self.row_name_model[:] = [None] + str_features
181            # If changed after 'openContext'
182            if self.row_name_attr in str_feature_names:
183                index = str_feature_names.index(self.row_name_attr) + 1
184                self.row_name_combo.setCurrentIndex(index)
185            elif not str_feature_names:
186                self.row_name_attr = None
187
188            self.do_transpose()
189        else:
190            self.info_w.setText("No data on input")
191            self.send("Transposed Data", None)
192
193    def on_row_name_changed(self, index):
194        if len(self.row_name_model) and index > 0:
195            self.row_name_attr = self.row_name_model[index].name
196        else:
197            self.row_name_attr = None
198        self.do_transpose()
199
200    def do_transpose(self):
201        transposed = None
202        self.error(0)
203        if self.data is not None:
204            try:
205                transposed = transpose(self.data, self.row_name_attr,
206                                       error_non_continuous="ignore",
207                                       error_meta="ignore")
208            except Exception, ex:
209                self.error(0, str(ex))
210                raise
211
212        self.send("Transposed Data", transposed)
213
214
215if __name__ == "__main__":
216    import sys
217    app = QApplication(sys.argv)
218    w = OWTranspose()
219#    data = Orange.data.Table("doc:dicty-express.tab")
220    data = Orange.data.Table("doc:small-sample.tab")
221#    data = Orange.data.Table("iris")
222    w.set_data(data)
223    w.show()
224    sys.exit(app.exec_())
Note: See TracBrowser for help on using the repository browser.