source: orange/orange/Orange/data/utils.py @ 9122:bc73a69830f9

Revision 9122:bc73a69830f9, 8.7 KB checked in by ales_erjavec <ales.erjavec@…>, 3 years ago (diff)

Moved group_by function to Orange.data.utils

Line 
1"""\
2**************************
3Data Utilities (``utils``)
4**************************
5
6Common operations on :class:`Orange.data.Table`.
7
8"""
9#from __future__ import absolute_import
10
11import random
12import math
13from operator import itemgetter
14
15from collections import defaultdict
16
17from Orange.data import Table, Domain, Instance, variable
18
19def table_map(table, attrs, exclude_special=True):
20    map = defaultdict(list)
21    for i, ex in enumerate(table):
22        key = [ex[a] for a in attrs]
23        if exclude_special and any(k.isSpecial() for k in key):
24            continue
25        key = tuple([str(k) for k in key])
26        map[key].append(i)
27    return map
28   
29def join_domains(domain1, domain2):
30    variables = domain1.variables + domain1.variables
31    used_set = set()
32    def used(vars):
33        mask = []
34        for var in vars:
35            mask.append(var not in used_set)
36            used_set.add(var)
37           
38    used_mask1 = used(domain1.variables)
39    used_mask2 = used(domain2.variables)
40    if domain2.classVar:
41        used_mask2[-1] = True
42       
43    variables = [v for v, used in zip(variables, used_mask1 + used_mask2)]
44   
45    joined_domain = Domain(variables, domain2.classVar)
46    joined_domain.add_metas(domain1.get_metas())
47    joined_domain.add_metas(domain2.get_metas())
48    return joined_domain, used_mask1, used_mask2
49   
50def left_join(table1, table2, on_attrs1, on_attrs2):
51    """ Left join table1 and table2 on attributes attr1 and attr2
52    """
53    if not isinstance(on_attrs1, (list, tuple)):
54        on_attrs1 = [on_attrs1]
55    if not isinstance(on_attrs2, (list, tuple)):
56        on_attrs2 = [on_attrs2]
57    key_map1 = table_map(table1, on_attrs1)
58    key_map2 = table_map(table2, on_attrs2)
59    domain1, domain2 = table1.domain, table2.domain
60   
61    left_examples = []
62    right_examples = []
63    for ex in table1:
64        key = tuple([str(ex[a]) for a in on_attrs1])
65        if key in key_map1 and key in key_map2:
66            for ind in key_map2[key]:
67                ex2 = table2[ind]
68                left_examples.append(ex)
69                right_examples.append(ex2)
70               
71    left_table = Table(left_examples)
72    right_table = Table(right_examples)
73    new_table = Table([left_table, right_table])
74    return new_table
75   
76def right_join(table1, table2, on_attrs1, on_attrs2):
77    """ Right join table1 and table2 on attributes attr1 and attr2
78    """
79    if not isinstance(on_attrs1, (list, tuple)):
80        on_attrs1 = [on_attrs1]
81    if not isinstance(on_attrs2, (list, tuple)):
82        on_attrs2 = [on_attrs2]
83    key_map1 = table_map(table1, on_attrs1)
84    key_map2 = table_map(table2, on_attrs2)
85    domain1, domain2 = table1.domain, table2.domain
86
87    left_examples = []
88    right_examples = []
89    for ex in table2:
90        key = tuple([str(ex[a]) for a in on_attrs2])
91        if key in key_map1 and key in key_map2:
92            for ind in key_map1[key]:
93                ex1 = table1[ind]
94                left_examples.append(ex1)
95                right_examples.append(ex)
96               
97    left_table = Table(left_examples)
98    right_table = Table(right_examples)
99    new_table = Table([left_table, right_table])
100    return new_table
101   
102def hstack(table1, table2):
103    """ Horizontally stack ``table1`` and ``table2``
104    """
105    return Table([table1, table2])
106
107def vstack(table1, table2):
108    """ Stack ``table1`` and ``table2`` vertically.
109    """
110    return Table(table1[:] + table2[:])
111
112def take(table, indices, axis=0):
113    """ Take values form the ``table`` along the ``axis``.
114    """
115    indices = mask_to_indices(indices, (len(table), len(table.domain)), axis)
116    if axis == 0:
117        # Take the rows (instances)
118        instances = [table[i] for i in indices]
119        table = Table(instances) if instances else Table(table.domain)
120    elif axis == 1:
121        # Take the columns (attributes)
122        variables = table.domain.variables
123        vars = [variables[i] for i in indices]
124        domain = Domain(vars, table.domain.class_var in vars)
125        domain.add_metas(table.domain.get_metas())
126        table = Table(domain, table)
127    return table
128
129def mask_to_indices(mask, shape, axis=0):
130    """ Convert a mask into indices.
131    """
132    import numpy
133    mask = numpy.asarray(mask)
134    dtype = mask.dtype
135    size = shape[axis]
136    if dtype.kind == "b":
137        if len(mask) != size:
138            raise ValueError("Mask size does not match the shape.")
139        indices = [i for i, m in zip(range(size), mask)]
140    elif dtype.kind == "i":
141        indices = mask
142    return indices
143
144
145from threading import Lock as _Lock
146_global_id = 0
147_global_id_lock = _Lock()
148 
149def range_generator():
150    global _global_id
151    while True:
152        with _global_id_lock:
153            id = int(_global_id)
154            _global_id += 1
155        yield id
156       
157def uuid_generator():
158    import uuid
159    while True:
160        yield str(uuid.uuid4())
161
162from Orange.data import new_meta_id, variable
163
164_row_meta_id = new_meta_id()
165_id_variable = variable.String("Row Id")
166
167def add_row_id(table, start=0):
168    """ Add an Row Id meta variable to the table.
169   
170    Parameters
171    ==========
172    :param table: The ids will be added to this table.
173    :type table: Orange.data.Table
174       
175    :param start: Start id for the ids. It can also be an iterator
176        yielding unique values.
177    :type start: int
178       
179       
180    """
181    if _row_meta_id not in table.domain.get_metas():
182        table.domain.add_meta(_row_meta_id, _id_variable)
183       
184    if isinstance(start, int):
185        ids = iter(range(start, start + len(table)))
186    else:
187        ids = start
188               
189    for ex in table:
190        ex[_id_variable] = str(ids.next())
191
192   
193from Orange.statistics import distribution
194
195def modus(values):
196    dist = distribution.Distribution(values[0].variable)
197    for v in values:
198        dist.add(v)
199    return dist.modus()
200
201def mean(values):
202    dist = distribution.Distribution(values[0].variable)
203    for v in values:
204        dist.add(v)
205    return dist.average()
206
207def geometric_mean(values):
208    values = [float(v) for v in values if not v.is_special()]
209    if values:
210        prod = reduce(float.__mul__, values, 1.0)
211        print prod, len(values)
212        return math.pow(prod, 1.0/len(values))
213    else:
214        return "?"
215   
216def harmonic_mean(values):
217    values = [float(v) for v in values if not v.is_special()]
218    if values:
219        hsum = sum(map(lambda v: 1.0 / (v or 1e-6), values))
220        return len(values) / (hsum or 1e-6) 
221    else:
222        return "?"
223
224_aggregate_mapping = {"random": random.choice,
225                      "first": itemgetter(0),
226                      "last": itemgetter(-1),
227                      "modus": modus,
228                      "mean": mean,
229                      "geometric mean": geometric_mean,
230                      "harmonic mean": harmonic_mean,
231                      "join": lambda values: ", ".join(map(str, values))
232                      }
233
234def _aggregate_func(func):
235    if isinstance(func, basestring):
236        if func in _aggregate_mapping:
237            return _aggregate_mapping[func]
238        else:
239            raise ValueError("Unknown aggregate function %r." % func)
240    return func
241       
242def group_by(table, group_attrs, aggregate_disc="first", aggregate_cont="mean",
243             aggregate_string="join", attr_aggregate=None):
244    if attr_aggregate is None:
245        attr_aggregate = {}
246    else:
247        attr_aggregate = dict(attr_aggregate) # It is modified later
248       
249    all_vars = table.domain.variables + table.domain.getmetas().values()
250    aggregate_vars = []
251    for v in all_vars:
252        if v not in group_attrs:
253            if v in attr_aggregate:
254                pass
255            elif isinstance(v, variable.Continuous):
256                attr_aggregate[v] = aggregate_cont
257            elif isinstance(v, variable.Discrete):
258                attr_aggregate[v] = aggregate_disc
259            elif isinstance(v, variable.String):
260                attr_aggregate[v] = aggregate_string
261            else:
262                raise TypeError(v)
263            aggregate_vars.append(v)
264            attr_aggregate[v] = _aggregate_func(attr_aggregate[v])
265           
266    indices_map = table_map(table, group_attrs, exclude_special=False)
267    new_instances = []
268    key_set = set()
269   
270    for inst in table: # Iterate over the table instead of the inidces_map to preserve order
271        key = tuple([str(inst[v]) for v in group_attrs])
272        if key in key_set:
273            continue # Already seen this group
274        indices = indices_map[key]
275        new_instance = Instance(inst) # Copy
276        for v in aggregate_vars:
277            values = [table[i][v] for i in indices] # Values to aggregate
278            new_instance[v] = attr_aggregate[v](values)
279        new_instances.append(new_instance)
280        key_set.add(key)
281    return Table(new_instances)
Note: See TracBrowser for help on using the repository browser.