source: orange/Orange/preprocess/scaling.py @ 10020:b96c450ff58a

Revision 10020:b96c450ff58a, 78.1 KB checked in by Matija Polajnar <matija.polajnar@…>, 2 years ago (diff)

Detect trivially detectable errorneous deprecated_members.

Line 
1"""
2.. index:: data scaling
3
4.. index::
5   single: scaling
6
7**************************
8Data Scaling (``scaling``)
9**************************
10
11This module is a conglomerate of Orange 2.0 modules orngScaleData,
12orngScaleLinProjData, orngScaleLinProjData3D, orngScalePolyvizData and orngScaleScatterPlotData. The
13documentation is poor and has to be improved in the future.
14
15.. autoclass:: Orange.preprocess.scaling.ScaleData
16   :members:
17   :show-inheritance:
18
19.. autoclass:: Orange.preprocess.scaling.ScaleLinProjData
20   :members:
21   :show-inheritance:
22
23.. autoclass:: Orange.preprocess.scaling.ScaleLinProjData3D
24   :members:
25   :show-inheritance:
26
27.. autoclass:: Orange.preprocess.scaling.ScalePolyvizData
28   :members:
29   :show-inheritance:
30
31.. autoclass:: Orange.preprocess.scaling.ScaleScatterPlotData
32   :members:
33   :show-inheritance:
34
35.. autofunction:: get_variable_values_sorted
36
37.. autofunction:: get_variable_value_indices
38
39.. autofunction:: discretize_domain
40
41"""
42
43
44import sys
45import numpy
46import random
47import time
48try:
49    import numpy.ma as MA
50except:
51    import numpy.core.ma as MA
52from copy import copy
53from math import sqrt
54import math
55
56import Orange
57import Orange.core
58import Orange.preprocess
59import Orange.data
60from Orange.misc import caching
61
62import warnings
63
64from Orange.misc import deprecated_keywords, deprecated_members
65
66def get_variable_values_sorted(variable):
67    """
68    Return a list of sorted values for given attribute.
69   
70    EXPLANATION: if variable values have values 1, 2, 3, 4, ... then their
71    order in orange depends on when they appear first in the data. With this
72    function we get a sorted list of values.
73   
74    """
75    if variable.var_type == Orange.core.VarTypes.Continuous:
76        print "get_variable_values_sorted - attribute %s is a continuous variable" % (variable)
77        return []
78
79    values = list(variable.values)
80    int_values = []
81
82    # do all attribute values containt integers?
83    try:
84        int_values = [(int(val), val) for val in values]
85    except:
86        return values
87
88    # if all values were intergers, we first sort them ascendently
89    int_values.sort()
90    return [val[1] for val in int_values]
91
92@deprecated_keywords({"sortValuesForDiscreteAttrs":
93                      "sort_values_for_discrete_attrs"})
94def get_variable_value_indices(variable, sort_values_for_discrete_attrs = 1):
95    """
96    Create a dictionary with given variable. Keys are variable values, values
97    are indices (transformed from string to int); in case all values are
98    integers, we also sort them.
99   
100    """
101    if variable.var_type == Orange.core.VarTypes.Continuous:
102        print "get_variable_value_indices - attribute %s is a continuous "\
103              "variable" % (str(variable))
104        return {}
105
106    if sort_values_for_discrete_attrs:
107        values = get_variable_values_sorted(variable)
108    else:
109        values = list(variable.values)
110    return dict([(values[i], i) for i in range(len(values))])
111
112
113@deprecated_keywords({"removeUnusedValues": "remove_unused_values",
114                      "numberOfIntervals": "number_of_intervals"})
115def discretize_domain(data, remove_unused_values = 1, number_of_intervals = 2):
116    """
117    Discretize the domain. If we have a class, remove the instances with missing
118    class value, discretize the continuous class into discrete class with two
119    values, discretize continuous attributes using entropy discretization (or
120    equiN if we don't have a class or class is continuous).
121    """
122    entro_disc = Orange.preprocess.EntropyDiscretization()
123    equi_disc  = Orange.preprocess.EquiNDiscretization(number_of_intervals =
124                                                       number_of_intervals)
125    disc_attrs = []
126
127    classname = (data and len(data) > 0 and data.domain.class_var and
128                 data.domain.class_var.name or None)
129
130    if not data or len(data) == 0:
131        return None
132
133    # if we have a continuous class we have to discretize it before we can
134    # discretize the attributes
135    if classname and data.domain.class_var.var_type == \
136                     Orange.core.VarTypes.Continuous:
137        try:
138            newclass = equi_disc(data.domain.class_var.name, data)
139            newclass.name = classname
140        except Orange.core.KernelException, ex:
141            warnings.warn("Could not discretize class variable '%s'. %s" %
142                          (data.domain.class_var.name, ex.message))
143            newclass = None
144            classname = None
145        new_domain = Orange.data.Domain(data.domain.attributes, newclass)
146        data = Orange.data.Table(new_domain, data)
147
148    for attr in data.domain.attributes:
149        try:
150            name = attr.name
151            if attr.var_type == Orange.core.VarTypes.Continuous:
152                # if continuous attribute then use entropy discretization
153                if data.domain.class_var and data.domain.class_var.var_type == \
154                   Orange.core.VarTypes.Discrete:
155                    new_attr = entro_disc(attr, data)
156                else:
157                    new_attr = equi_disc(attr, data)
158            else:
159                new_attr = attr
160            if remove_unused_values:
161                new_attr = Orange.preprocess.RemoveUnusedValues(new_attr, data)
162                if new_attr is None:
163                    raise Orange.core.KernelException, "No values"
164           
165            new_attr.name = name
166            disc_attrs.append(new_attr)
167        except Orange.core.KernelException, ex:
168            # if all values are missing, entropy discretization will throw an
169            # exception. in such cases ignore the attribute
170            warnings.warn("Could not discretize %s attribute. %s" %
171                          (attr.name, ex.message))
172
173    if classname: disc_attrs.append(data.domain.class_var)
174    d2 = data.translate(disc_attrs, True)
175    return d2
176
177
178class ScaleData:
179    def __init__(self):
180        self.raw_data = None           # input data
181        self.raw_subset_data = None
182        self.attribute_names = []    # list of attribute names from self.raw_data
183        self.attribute_name_index = {}  # dict with indices to attributes
184        self.attribute_flip_info = {}   # dictionary with attrName: 0/1 attribute is flipped or not
185       
186        self.data_has_class = False
187        self.data_has_continuous_class = False
188        self.data_has_discrete_class = False
189        self.data_class_name = None
190        self.data_domain = None
191        self.data_class_index = None
192        self.have_data = False
193        self.have_subset_data = False
194
195        self.jitter_size = 10
196        self.jitter_continuous = 0
197
198        self.attr_values = {}
199        self.domain_data_stat = []
200        self.original_data = self.original_subset_data = None    # input (nonscaled) data in a numpy array
201        self.scaled_data = self.scaled_subset_data = None        # scaled data to the interval 0-1
202        self.no_jittering_scaled_data = self.no_jittering_scaled_subset_data = None
203        self.valid_data_array = self.valid_subset_data_array = None
204
205    @deprecated_keywords({"subsetData": "subset_data"})
206    def merge_data_sets(self, data, subset_data):
207        """
208        Take examples from data and subset_data and merge them into one
209        dataset.
210       
211        """
212        if data == None and subset_data == None: None
213        if subset_data == None:
214            full_data = data
215        elif data == None:
216            full_data = subset_data
217        else:
218            full_data = Orange.data.Table(data)
219            full_data.extend(subset_data)
220        return full_data
221   
222    mergeDataSets = merge_data_sets
223
224    def rescale_data(self):
225        """
226        Force the existing data to be rescaled due to changes like
227        jitter_continuous, jitter_size, ...
228        """
229        self.set_data(self.raw_data, self.raw_subset_data, skipIfSame = 0)
230   
231    rescaleData = rescale_data
232
233    @deprecated_keywords({"subsetData": "subset_data",
234                          "sortValuesForDiscreteAttrs":
235                          "sort_values_for_discrete_attrs"})
236    def set_data(self, data, subset_data = None, **args):
237        if args.get("skipIfSame", 1):
238            if (((data == None and self.raw_data == None) or
239                (self.raw_data != None and data != None and
240                 self.raw_data.checksum() == data.checksum())) and
241                ((subset_data == None and self.raw_subset_data == None) or
242                 (self.raw_subset_data != None and subset_data != None and
243                  self.raw_subset_data.checksum() == subset_data.checksum()))):
244                    return
245
246        self.domain_data_stat = []
247        self.attr_values = {}
248        self.original_data = self.original_subset_data = None
249        self.scaled_data = self.scaled_subset_data = None
250        self.no_jittering_scaled_data = self.no_jittering_scaled_subset_data = None
251        self.valid_data_array = self.valid_subset_data_array = None
252
253        self.raw_data = None
254        self.raw_subset_data = None
255        self.have_data = False
256        self.have_subset_data = False
257        self.data_has_class = False
258        self.data_has_continuous_class = False
259        self.data_has_discrete_class = False
260        self.data_class_name = None
261        self.data_domain = None
262        self.data_class_index = None
263               
264        if data == None: return
265        full_data = self.merge_data_sets(data, subset_data)
266               
267        self.raw_data = data
268        self.raw_subset_data = subset_data
269
270        len_data = data and len(data) or 0
271        numpy.random.seed(1)     # we always reset the random generator, so that if we receive the same data again we will add the same noise
272
273        self.attribute_names = [attr.name for attr in full_data.domain]
274        self.attribute_name_index = dict([(full_data.domain[i].name, i)
275                                          for i in range(len(full_data.domain))])
276        self.attribute_flip_info = {}         # dict([(attr.name, 0) for attr in full_data.domain]) # reset the fliping information
277       
278        self.data_domain = full_data.domain
279        self.data_has_class = bool(full_data.domain.class_var)
280        self.data_has_continuous_class = bool(self.data_has_class and
281                                              full_data.domain.class_var.var_type == Orange.core.VarTypes.Continuous)
282        self.data_has_discrete_class = bool(self.data_has_class and
283                                            full_data.domain.class_var.var_type == Orange.core.VarTypes.Discrete)
284        self.data_class_name = self.data_has_class and full_data.domain.class_var.name
285        if self.data_has_class:
286            self.data_class_index = self.attribute_name_index[self.data_class_name]
287        self.have_data = bool(self.raw_data and len(self.raw_data) > 0)
288        self.have_subset_data = bool(self.raw_subset_data and
289                                     len(self.raw_subset_data) > 0)
290       
291        self.domain_data_stat = caching.getCached(full_data,
292                                          Orange.core.DomainBasicAttrStat,
293                                          (full_data,))
294
295        sort_values_for_discrete_attrs = args.get("sort_values_for_discrete_attrs",
296                                                  1)
297
298        for index in range(len(full_data.domain)):
299            attr = full_data.domain[index]
300            if attr.var_type == Orange.core.VarTypes.Discrete:
301                self.attr_values[attr.name] = [0, len(attr.values)]
302            elif attr.var_type == Orange.core.VarTypes.Continuous:
303                self.attr_values[attr.name] = [self.domain_data_stat[index].min,
304                                               self.domain_data_stat[index].max]
305       
306        # the original_data, no_jittering_scaled_data and validArray are arrays
307        # that we can cache so that other visualization widgets don't need to
308        # compute it. The scaled_data on the other hand has to be computed for
309        # each widget separately because of different
310        # jitter_continuous and jitter_size values
311        if caching.getCached(data, "visualizationData") and subset_data == None:
312            self.original_data, self.no_jittering_scaled_data, self.valid_data_array = caching.getCached(data,"visualizationData")
313            self.original_subset_data = self.no_jittering_scaled_subset_data = self.valid_subset_data_array = numpy.array([]).reshape([len(self.original_data), 0])
314        else:
315            no_jittering_data = full_data.toNumpyMA("ac")[0].T
316            valid_data_array = numpy.array(1-no_jittering_data.mask,
317                                           numpy.short)  # have to convert to int array, otherwise when we do some operations on this array we get overflow
318            no_jittering_data = numpy.array(MA.filled(no_jittering_data,
319                                                      Orange.core.Illegal_Float))
320            original_data = no_jittering_data.copy()
321           
322            for index in range(len(data.domain)):
323                attr = data.domain[index]
324                if attr.var_type == Orange.core.VarTypes.Discrete:
325                    # see if the values for discrete attributes have to be resorted
326                    variable_value_indices = get_variable_value_indices(data.domain[index],
327                                                                        sort_values_for_discrete_attrs)
328                    if 0 in [i == variable_value_indices[attr.values[i]]
329                             for i in range(len(attr.values))]:
330                        # make the array a contiguous, otherwise the putmask
331                        # function does not work
332                        line = no_jittering_data[index].copy()
333                        indices = [numpy.where(line == val, 1, 0)
334                                   for val in range(len(attr.values))]
335                        for i in range(len(attr.values)):
336                            numpy.putmask(line, indices[i],
337                                          variable_value_indices[attr.values[i]])
338                        no_jittering_data[index] = line   # save the changed array
339                        original_data[index] = line     # reorder also the values in the original data
340                    no_jittering_data[index] = ((no_jittering_data[index]*2.0 + 1.0)
341                                                / float(2*len(attr.values)))
342                   
343                elif attr.var_type == Orange.core.VarTypes.Continuous:
344                    diff = self.domain_data_stat[index].max - self.domain_data_stat[index].min or 1     # if all values are the same then prevent division by zero
345                    no_jittering_data[index] = (no_jittering_data[index] -
346                                                self.domain_data_stat[index].min) / diff
347
348            self.original_data = original_data[:,:len_data]; self.original_subset_data = original_data[:,len_data:]
349            self.no_jittering_scaled_data = no_jittering_data[:,:len_data]; self.no_jittering_scaled_subset_data = no_jittering_data[:,len_data:]
350            self.valid_data_array = valid_data_array[:,:len_data]; self.valid_subset_data_array = valid_data_array[:,len_data:]
351       
352        if data: caching.setCached(data, "visualizationData",
353                           (self.original_data, self.no_jittering_scaled_data,
354                            self.valid_data_array))
355        if subset_data: caching.setCached(subset_data, "visualizationData",
356                                  (self.original_subset_data,
357                                   self.no_jittering_scaled_subset_data,
358                                   self.valid_subset_data_array))
359           
360        # compute the scaled_data arrays
361        scaled_data = numpy.concatenate([self.no_jittering_scaled_data,
362                                         self.no_jittering_scaled_subset_data],
363                                         axis = 1)
364        for index in range(len(data.domain)):
365            attr = data.domain[index]
366            if attr.var_type == Orange.core.VarTypes.Discrete:
367                scaled_data[index] += (self.jitter_size/(50.0*max(1,len(attr.values))))*\
368                                      (numpy.random.random(len(full_data)) - 0.5)
369               
370            elif attr.var_type == Orange.core.VarTypes.Continuous and self.jitter_continuous:
371                scaled_data[index] += self.jitter_size/50.0 * (0.5 - numpy.random.random(len(full_data)))
372                scaled_data[index] = numpy.absolute(scaled_data[index])       # fix values below zero
373                ind = numpy.where(scaled_data[index] > 1.0, 1, 0)     # fix values above 1
374                numpy.putmask(scaled_data[index], ind, 2.0 - numpy.compress(ind, scaled_data[index]))
375        self.scaled_data = scaled_data[:,:len_data]; self.scaled_subset_data = scaled_data[:,len_data:]
376   
377    setData = set_data
378
379    @deprecated_keywords({"example": "instance"})
380    def scale_example_value(self, instance, index):
381        """
382        Scale instance's value at index index to a range between 0 and 1 with
383        respect to self.raw_data.
384        """
385        if instance[index].isSpecial():
386            print "Warning: scaling instance with missing value"
387            return 0.5     #1e20
388        if instance.domain[index].var_type == Orange.core.VarTypes.Discrete:
389            d = get_variable_value_indices(instance.domain[index])
390            return (d[instance[index].value]*2 + 1) / float(2*len(d))
391        elif instance.domain[index].var_type == Orange.core.VarTypes.Continuous:
392            diff = self.domain_data_stat[index].max - self.domain_data_stat[index].min
393            if diff == 0: diff = 1          # if all values are the same then prevent division by zero
394            return (instance[index] - self.domain_data_stat[index].min) / diff
395
396    scaleExampleValue = scale_example_value
397
398    @deprecated_keywords({"attrName": "attr_name"})
399    def get_attribute_label(self, attr_name):
400        if self.attribute_flip_info.get(attr_name, 0) and self.data_domain[attr_name].var_type == Orange.core.VarTypes.Continuous:
401            return "-" + attr_name
402        return attr_name
403   
404    getAttributeLabel = get_attribute_label
405
406    @deprecated_keywords({"attrName": "attr_name"})
407    def flip_attribute(self, attr_name):
408        if attr_name not in self.attribute_names: return 0
409        if self.data_domain[attr_name].var_type == Orange.core.VarTypes.Discrete: return 0
410
411        index = self.attribute_name_index[attr_name]
412        self.attribute_flip_info[attr_name] = 1 - self.attribute_flip_info.get(attr_name, 0)
413        if self.data_domain[attr_name].var_type == Orange.core.VarTypes.Continuous:
414            self.attr_values[attr_name] = [-self.attr_values[attr_name][1], -self.attr_values[attr_name][0]]
415
416        self.scaled_data[index] = 1 - self.scaled_data[index]
417        self.scaled_subset_data[index] = 1 - self.scaled_subset_data[index]
418        self.no_jittering_scaled_data[index] = 1 - self.no_jittering_scaled_data[index]
419        self.no_jittering_scaled_subset_data[index] = 1 - self.no_jittering_scaled_subset_data[index]
420        return 1
421
422    flipAttribute = flip_attribute
423   
424    def get_min_max_val(self, attr):
425        if type(attr) == int:
426            attr = self.attribute_names[attr]
427        diff = self.attr_values[attr][1] - self.attr_values[attr][0]
428        return diff or 1.0
429
430    getMinMaxVal = get_min_max_val
431
432    @deprecated_keywords({"alsoClassIfExists": "also_class_if_exists"})
433    def get_valid_list(self, indices, also_class_if_exists = 1):
434        """
435        Get array of 0 and 1 of len = len(self.raw_data). If there is a missing
436        value at any attribute in indices return 0 for that instance.
437        """
438        if self.valid_data_array == None or len(self.valid_data_array) == 0:
439            return numpy.array([], numpy.bool)
440       
441        inds = indices[:]
442        if also_class_if_exists and self.data_has_class: 
443            inds.append(self.data_class_index) 
444        selectedArray = self.valid_data_array.take(inds, axis = 0)
445        arr = numpy.add.reduce(selectedArray)
446        return numpy.equal(arr, len(inds))
447   
448    getValidList = get_valid_list
449
450    @deprecated_keywords({"alsoClassIfExists": "also_class_if_exists"})
451    def get_valid_subset_list(self, indices, also_class_if_exists = 1):
452        """
453        Get array of 0 and 1 of len = len(self.raw_subset_data). if there is a
454        missing value at any attribute in indices return 0 for that instance.
455        """
456        if self.valid_subset_data_array == None or len(self.valid_subset_data_array) == 0:
457            return numpy.array([], numpy.bool)
458        inds = indices[:]
459        if also_class_if_exists and self.data_class_index: 
460            inds.append(self.data_class_index)
461        selectedArray = self.valid_subset_data_array.take(inds, axis = 0)
462        arr = numpy.add.reduce(selectedArray)
463        return numpy.equal(arr, len(inds))
464   
465    getValidSubsetList = get_valid_subset_list
466
467    def get_valid_indices(self, indices):
468        """
469        Get array with numbers that represent the instance indices that have a
470        valid data value.
471        """
472        validList = self.get_valid_list(indices)
473        return numpy.nonzero(validList)[0]
474   
475    getValidIndices = get_valid_indices
476
477    def get_valid_subset_indices(self, indices):
478        """
479        Get array with numbers that represent the instance indices that have a
480        valid data value.
481        """
482        validList = self.get_valid_subset_list(indices)
483        return numpy.nonzero(validList)[0]
484   
485    getValidSubsetIndices = get_valid_subset_indices
486
487    def rnd_correction(self, max):
488        """
489        Return a number from -max to max.
490        """
491        return (random.random() - 0.5)*2*max
492   
493    rndCorrection = rnd_correction
494
495ScaleData = deprecated_members({"rawData": "raw_data",
496                                "rawSubsetData": "raw_subset_data",
497                                "attributeNames": "attribute_names",
498                                "attributeNameIndex": "attribute_name_index",
499                                "attributeFlipInfo": "attribute_flip_info",
500                                "dataHasClass": "data_has_class",
501                                "dataHasContinuousClass": "data_has_continuous_class",
502                                "dataHasDiscreteClass": "data_has_discrete_class",
503                                "dataClassName": "data_class_name",
504                                "dataDomain": "data_domain",
505                                "dataClassIndex": "data_class_index",
506                                "haveData": "have_data",
507                                "haveSubsetData": "have_subset_data",
508                                "jitterSize": "jitter_size",
509                                "jitterContinuous": "jitter_continuous",
510                                "attrValues": "attr_values",
511                                "domainDataStat": "domain_data_stat",
512                                "originalData": "original_data",
513                                "originalSubsetData": "original_subset_data",
514                                "scaledData": "scaled_data",
515                                "scaledSubsetData": "scaled_subset_data",
516                                "noJitteringScaledData": "no_jittering_scaled_data",
517                                "noJitteringScaledSubsetData": "no_jittering_scaled_subset_data",
518                                "validDataArray": "valid_data_array",
519                                "validSubsetDataArray": "valid_subset_data_array",
520                                "mergeDataSets": "merge_data_sets",
521                                "rescaleData": "rescale_data",
522                                "setData": "set_data",
523                                "scaleExampleValue": "scale_example_value",
524                                "getAttributeLabel": "get_attribute_label",
525                                "flipAttribute": "flip_attribute",
526                                "getMinMaxVal": "get_min_max_val",
527                                "getValidList": "get_valid_list",
528                                "getValidSubsetList": "get_valid_subset_list",
529                                "getValidIndices": "get_valid_indices",
530                                "getValidSubsetIndices": "get_valid_subset_indices",
531                                "rndCorrection": "rnd_correction",
532                                })(ScaleData)
533
534
535class ScaleLinProjData(ScaleData):
536    def __init__(self):
537        ScaleData.__init__(self)
538        self.normalize_examples = 1
539        self.anchor_data =[]        # form: [(anchor1x, anchor1y, label1),(anchor2x, anchor2y, label2), ...]
540        self.last_attr_indices = None
541        self.anchor_dict = {}
542
543    @deprecated_keywords({"xAnchors": "xanchors", "yAnchors": "yanchors"})
544    def set_anchors(self, xanchors, yanchors, attributes):
545        if attributes:
546            if xanchors != None and yanchors != None:
547                self.anchor_data = [(xanchors[i], yanchors[i], attributes[i])
548                                    for i in range(len(attributes))]
549            else:
550                self.anchor_data = self.create_anchors(len(attributes), attributes)
551   
552    setAnchors = set_anchors
553
554    @deprecated_keywords({"numOfAttr": "num_of_attr"})
555    def create_anchors(self, num_of_attr, labels = None):
556        """
557        Create anchors around the circle.
558       
559        """
560        xanchors = self.create_xanchors(num_of_attr)
561        yanchors = self.create_yanchors(num_of_attr)
562        if labels:
563            return [(xanchors[i], yanchors[i], labels[i]) for i in range(num_of_attr)]
564        else:
565            return [(xanchors[i], yanchors[i]) for i in range(num_of_attr)]
566   
567    createAnchors = create_anchors
568
569    @deprecated_keywords({"numOfAttrs": "num_of_attrs"})
570    def create_xanchors(self, num_of_attrs):
571        if not self.anchor_dict.has_key(num_of_attrs):
572            self.anchor_dict[num_of_attrs] = (numpy.cos(numpy.arange(num_of_attrs)
573                                                        * 2*math.pi
574                                                        / float(num_of_attrs)),
575                                              numpy.sin(numpy.arange(num_of_attrs)
576                                                        * 2*math.pi
577                                                        / float(num_of_attrs)))
578        return self.anchor_dict[num_of_attrs][0]
579   
580    createXAnchors = create_xanchors
581
582    @deprecated_keywords({"numOfAttrs": "num_of_attrs"})
583    def create_yanchors(self, num_of_attrs):
584        if not self.anchor_dict.has_key(num_of_attrs):
585            self.anchor_dict[num_of_attrs] = (numpy.cos(numpy.arange(num_of_attrs)
586                                                        * 2*math.pi
587                                                        / float(num_of_attrs)),
588                                              numpy.sin(numpy.arange(num_of_attrs)
589                                                        * 2*math.pi
590                                                        / float(num_of_attrs)))
591        return self.anchor_dict[num_of_attrs][1]
592
593    createYAnchors = create_yanchors
594
595    @deprecated_keywords({"fileName": "filename", "attrList": "attrlist",
596                          "useAnchorData": "use_anchor_data"})
597    def save_projection_as_tab_data(self, filename, attrlist, use_anchor_data = 0):
598        """
599        Save projection (xattr, yattr, classval) into a filename filename.
600       
601        """
602        Orange.core.saveTabDelimited(filename,
603            self.create_projection_as_example_table([self.attribute_name_index[i]
604                                                     for i in attrlist],
605                                                    use_anchor_data = use_anchor_data))
606   
607    saveProjectionAsTabData = save_projection_as_tab_data
608
609    @deprecated_keywords({"attrIndices": "attr_indices",
610                          "settingsDict": "settings_dict"})
611    def get_projected_point_position(self, attr_indices, values, **settings_dict):
612        """
613        For attributes in attr_indices and values of these attributes in values
614        compute point positions. This function has more sense in radviz and
615        polyviz methods.
616   
617        """
618        # load the elements from the settings dict
619        use_anchor_data = settings_dict.get("useAnchorData")
620        xanchors = settings_dict.get("xAnchors")
621        yanchors = settings_dict.get("yAnchors")
622        anchor_radius = settings_dict.get("anchorRadius")
623        normalize_example = settings_dict.get("normalizeExample")
624
625        if attr_indices != self.last_attr_indices:
626            print "get_projected_point_position. Warning: Possible bug. The "+\
627                  "set of attributes is not the same as when computing the "+\
628                  "whole projection"
629
630        if xanchors != None and yanchors != None:
631            xanchors = numpy.array(xanchors)
632            yanchors = numpy.array(yanchors)
633            if anchor_radius == None: anchor_radius = numpy.sqrt(xanchors*xanchors +
634                                                                 yanchors*yanchors)
635        elif use_anchor_data and self.anchor_data:
636            xanchors = numpy.array([val[0] for val in self.anchor_data])
637            yanchors = numpy.array([val[1] for val in self.anchor_data])
638            if anchor_radius == None: anchor_radius = numpy.sqrt(xanchors*xanchors +
639                                                                 yanchors*yanchors)
640        else:
641            xanchors = self.create_xanchors(len(attr_indices))
642            yanchors = self.create_yanchors(len(attr_indices))
643            anchor_radius = numpy.ones(len(attr_indices), numpy.float)
644
645        if normalize_example == 1 or (normalize_example == None
646                                      and self.normalize_examples):
647            m = min(values); M = max(values)
648            if m < 0.0 or M > 1.0: 
649                # we have to do rescaling of values so that all the values will
650                # be in the 0-1 interval
651                #print "example values are not in the 0-1 interval"
652                values = [max(0.0, min(val, 1.0)) for val in values]
653                #m = min(m, 0.0); M = max(M, 1.0); diff = max(M-m, 1e-10)
654                #values = [(val-m) / float(diff) for val in values]
655
656            s = sum(numpy.array(values)*anchor_radius)
657            if s == 0: return [0.0, 0.0]
658            x = self.trueScaleFactor * numpy.dot(xanchors*anchor_radius,
659                                                 values) / float(s)
660            y = self.trueScaleFactor * numpy.dot(yanchors*anchor_radius,
661                                                 values) / float(s)
662        else:
663            x = self.trueScaleFactor * numpy.dot(xanchors, values)
664            y = self.trueScaleFactor * numpy.dot(yanchors, values)
665
666        return [x, y]
667   
668    getProjectedPointPosition = get_projected_point_position
669
670    @deprecated_keywords({"attrIndices": "attr_indices",
671                          "settingsDict": "settings_dict"})
672    def create_projection_as_example_table(self, attr_indices, **settings_dict):
673        """
674        Create the projection of attribute indices given in attr_indices and
675        create an example table with it.
676        """
677        if self.data_domain.class_var:
678            domain = settings_dict.get("domain") or \
679                     Orange.data.Domain([Orange.feature.Continuous("xVar"),
680                                         Orange.feature.Continuous("yVar"),
681                                         Orange.feature.Discrete(self.data_domain.class_var.name,
682                                                                       values = get_variable_values_sorted(self.data_domain.class_var))])
683        else:
684            domain = settings_dict.get("domain") or \
685                     Orange.data.Domain([Orange.feature.Continuous("xVar"),
686                                         Orange.feature.Continuous("yVar")])
687        data = self.create_projection_as_numeric_array(attr_indices,
688                                                       **settings_dict)
689        if data != None:
690            return Orange.data.Table(domain, data)
691        else:
692            return Orange.data.Table(domain)
693
694    createProjectionAsExampleTable = create_projection_as_example_table
695
696    @deprecated_keywords({"attrIndices": "attr_indices",
697                          "settingsDict": "settings_dict"})
698    def create_projection_as_numeric_array(self, attr_indices, **settings_dict):
699        # load the elements from the settings dict
700        validData = settings_dict.get("validData")
701        classList = settings_dict.get("classList")
702        sum_i     = settings_dict.get("sum_i")
703        XAnchors = settings_dict.get("XAnchors")
704        YAnchors = settings_dict.get("YAnchors")
705        scaleFactor = settings_dict.get("scaleFactor", 1.0)
706        normalize = settings_dict.get("normalize")
707        jitterSize = settings_dict.get("jitterSize", 0.0)
708        useAnchorData = settings_dict.get("useAnchorData", 0)
709        removeMissingData = settings_dict.get("removeMissingData", 1)
710        useSubsetData = settings_dict.get("useSubsetData", 0)        # use the data or subsetData?
711        #minmaxVals = settings_dict.get("minmaxVals", None)
712
713        # if we want to use anchor data we can get attr_indices from the anchor_data
714        if useAnchorData and self.anchor_data:
715            attr_indices = [self.attribute_name_index[val[2]] for val in self.anchor_data]
716
717        if validData == None:
718            if useSubsetData: validData = self.get_valid_subset_list(attr_indices)
719            else:             validData = self.get_valid_list(attr_indices)
720        if sum(validData) == 0:
721            return None
722
723        if classList == None and self.data_domain.class_var:
724            if useSubsetData: classList = self.original_subset_data[self.data_class_index]
725            else:             classList = self.original_data[self.data_class_index]
726
727        # if jitterSize is set below zero we use scaled_data that has already jittered data
728        if useSubsetData:
729            if jitterSize < 0.0: data = self.scaled_subset_data
730            else:                data = self.no_jittering_scaled_subset_data
731        else:
732            if jitterSize < 0.0: data = self.scaled_data
733            else:                data = self.no_jittering_scaled_data
734
735        selectedData = numpy.take(data, attr_indices, axis = 0)
736        if removeMissingData:
737            selectedData = numpy.compress(validData, selectedData, axis = 1)
738            if classList != None and len(classList) != numpy.shape(selectedData)[1]:
739                classList = numpy.compress(validData, classList)
740
741        if useAnchorData and self.anchor_data:
742            XAnchors = numpy.array([val[0] for val in self.anchor_data])
743            YAnchors = numpy.array([val[1] for val in self.anchor_data])
744            r = numpy.sqrt(XAnchors*XAnchors + YAnchors*YAnchors)     # compute the distance of each anchor from the center of the circle
745            if normalize == 1 or (normalize == None and self.normalize_examples):
746                XAnchors *= r
747                YAnchors *= r
748        elif (XAnchors != None and YAnchors != None):
749            XAnchors = numpy.array(XAnchors); YAnchors = numpy.array(YAnchors)
750            r = numpy.sqrt(XAnchors*XAnchors + YAnchors*YAnchors)     # compute the distance of each anchor from the center of the circle
751        else:
752            XAnchors = self.create_xanchors(len(attr_indices))
753            YAnchors = self.create_yanchors(len(attr_indices))
754            r = numpy.ones(len(XAnchors), numpy.float)
755
756        x_positions = numpy.dot(XAnchors, selectedData)
757        y_positions = numpy.dot(YAnchors, selectedData)
758
759        if normalize == 1 or (normalize == None and self.normalize_examples):
760            if sum_i == None:
761                sum_i = self._getSum_i(selectedData, useAnchorData, r)
762            x_positions /= sum_i
763            y_positions /= sum_i
764            self.trueScaleFactor = scaleFactor
765        else:
766            if not removeMissingData:
767                try:
768                    x_validData = numpy.compress(validData, x_positions)
769                    y_validData = numpy.compress(validData, y_positions)
770                except:
771                    print validData
772                    print x_positions
773                    print numpy.shape(validData)
774                    print numpy.shape(x_positions)
775            else:
776                x_validData = x_positions
777                y_validData = y_positions
778           
779            dist = math.sqrt(max(x_validData*x_validData + y_validData*y_validData)) or 1
780            self.trueScaleFactor = scaleFactor / dist
781
782        self.unscaled_x_positions = numpy.array(x_positions)
783        self.unscaled_y_positions = numpy.array(y_positions)
784
785        if self.trueScaleFactor != 1.0:
786            x_positions *= self.trueScaleFactor
787            y_positions *= self.trueScaleFactor
788
789        if jitterSize > 0.0:
790            x_positions += numpy.random.uniform(-jitterSize, jitterSize, len(x_positions))
791            y_positions += numpy.random.uniform(-jitterSize, jitterSize, len(y_positions))
792
793        self.last_attr_indices = attr_indices
794        if classList != None:
795            return numpy.transpose(numpy.array((x_positions, y_positions, classList)))
796        else:
797            return numpy.transpose(numpy.array((x_positions, y_positions)))
798
799    createProjectionAsNumericArray = create_projection_as_numeric_array
800   
801    @deprecated_keywords({"useAnchorData": "use_anchor_data",
802                          "anchorRadius": "anchor_radius"})
803    def _getsum_i(self, data, use_anchor_data = 0, anchor_radius = None):
804        """
805        Function to compute the sum of all values for each element in the data.
806        Used to normalize.
807       
808        """
809        if use_anchor_data:
810            if anchor_radius == None:
811                anchor_radius = numpy.sqrt([a[0]**2+a[1]**2 for a in self.anchor_data])
812            sum_i = numpy.add.reduce(numpy.transpose(numpy.transpose(data)*anchor_radius))
813        else:
814            sum_i = numpy.add.reduce(data)
815        if len(numpy.nonzero(sum_i)) < len(sum_i):    # test if there are zeros in sum_i
816            sum_i += numpy.where(sum_i == 0, 1.0, 0.0)
817        return sum_i
818   
819    _getSum_i = _getsum_i
820
821ScaleLinProjData = deprecated_members({"setAnchors": "set_anchors",
822                                       "createAnchors": "create_anchors",
823                                       "createXAnchors": "create_xanchors",
824                                       "createYAnchors": "create_yanchors",
825                                       "saveProjectionAsTabData": "save_projection_as_tab_data",
826                                       "getProjectedPointPosition":
827                                           "get_projected_point_position",
828                                       "createProjectionAsExampleTable":
829                                           "create_projection_as_example_table",
830                                       "createProjectionAsNumericArray":
831                                           "create_projection_as_numeric_array",
832                                       "_getSum_i": "_getsum_i",
833                                       "normalizeExamples": "normalize_examples",
834                                       "anchorData": "anchor_data",
835                                       "lastAttrIndices": "last_attr_indices",
836                                       "anchorDict": "anchor_dict",
837                                      })(ScaleLinProjData)
838
839class ScaleLinProjData3D(ScaleData):
840    def __init__(self):
841        ScaleData.__init__(self)
842        self.normalize_examples = 1
843        self.anchor_data = []        # form: [(anchor1x, anchor1y, anchor1z, label1),(anchor2x, anchor2y, anchor2z, label2), ...]
844        self.last_attr_indices = None
845        self.anchor_dict = {}
846
847    @deprecated_keywords({"xAnchors": "xanchors", "yAnchors": "yanchors"})
848    def set_anchors(self, xanchors, yanchors, zanchors, attributes):
849        if attributes:
850            if xanchors != None and yanchors != None and zanchors != None:
851                self.anchor_data = [(xanchors[i], yanchors[i], zanchors[i], attributes[i])
852                                    for i in range(len(attributes))]
853            else:
854                self.anchor_data = self.create_anchors(len(attributes), attributes)
855
856    setAnchors = set_anchors
857
858    @deprecated_keywords({"numOfAttr": "num_of_attr"})
859    def create_anchors(self, num_of_attrs, labels=None):
860        """
861        Create anchors on the sphere.
862       
863        """
864        # Golden Section Spiral algorithm approximates even distribution of points on a sphere
865        # (read more here http://www.softimageblog.com/archives/115)
866        n = num_of_attrs
867        xanchors = []
868        yanchors = []
869        zanchors = []
870
871        inc = math.pi * (3 - math.sqrt(5))
872        off = 2. / n
873        for k in range(n):
874            y = k * off - 1 + (off / 2)
875            r = math.sqrt(1 - y*y)
876            phi = k * inc
877            xanchors.append(math.cos(phi)*r)
878            yanchors.append(y)
879            zanchors.append(math.sin(phi)*r)
880
881        self.anchor_dict[num_of_attrs] = [xanchors, yanchors, zanchors]
882 
883        if labels:
884            return [(xanchors[i], yanchors[i], zanchors[i], labels[i]) for i in range(num_of_attrs)]
885        else:
886            return [(xanchors[i], yanchors[i], zanchors[i]) for i in range(num_of_attrs)]
887
888    createAnchors = create_anchors
889
890    @deprecated_keywords({"numOfAttrs": "num_of_attrs"})
891    def create_xanchors(self, num_of_attrs):
892        if not self.anchor_dict.has_key(num_of_attrs):
893            self.create_anchors(num_of_attrs)
894        return self.anchor_dict[num_of_attrs][0]
895
896    createXAnchors = create_xanchors
897
898    @deprecated_keywords({"numOfAttrs": "num_of_attrs"})
899    def create_yanchors(self, num_of_attrs):
900        if not self.anchor_dict.has_key(num_of_attrs):
901            self.create_anchors(num_of_attrs)
902        return self.anchor_dict[num_of_attrs][1]
903
904    createYAnchors = create_yanchors
905
906    @deprecated_keywords({"numOfAttrs": "num_of_attrs"})
907    def create_zanchors(self, num_of_attrs):
908        if not self.anchor_dict.has_key(num_of_attrs):
909            self.create_anchors(num_of_attrs)
910        return self.anchor_dict[num_of_attrs][2]
911
912    createZAnchors = create_zanchors
913
914    @deprecated_keywords({"fileName": "filename", "attrList": "attrlist",
915                          "useAnchorData": "use_anchor_data"})
916    def save_projection_as_tab_data(self, filename, attrlist, use_anchor_data=0):
917        """
918        Save projection (xattr, yattr, zattr, classval) into a filename filename.
919       
920        """
921        Orange.core.saveTabDelimited(filename,
922            self.create_projection_as_example_table([self.attribute_name_index[i]
923                                                     for i in attrlist],
924                                                    use_anchor_data=use_anchor_data))
925   
926    saveProjectionAsTabData = save_projection_as_tab_data
927
928    @deprecated_keywords({"attrIndices": "attr_indices",
929                          "settingsDict": "settings_dict"})
930    def get_projected_point_position(self, attr_indices, values, **settings_dict):
931        """
932        For attributes in attr_indices and values of these attributes in values
933        compute point positions. This function has more sense in radviz and
934        polyviz methods.
935   
936        """
937        # load the elements from the settings dict
938        use_anchor_data = settings_dict.get("useAnchorData")
939        xanchors = settings_dict.get('xAnchors')
940        yanchors = settings_dict.get('yAnchors')
941        zanchors = settings_dict.get('zAnchors')
942        anchor_radius = settings_dict.get("anchorRadius")
943        normalize_example = settings_dict.get("normalizeExample")
944
945        if attr_indices != self.last_attr_indices:
946            print "get_projected_point_position. Warning: Possible bug. The "+\
947                  "set of attributes is not the same as when computing the "+\
948                  "whole projection"
949
950        if xanchors != None and yanchors != None and zanchors != None:
951            xanchors = numpy.array(xanchors)
952            yanchors = numpy.array(yanchors)
953            zanchors = numpy.array(zanchors)
954            if anchor_radius == None: anchor_radius = numpy.sqrt(xanchors*xanchors +
955                                                                 yanchors*yanchors +
956                                                                 zanchors*zanchors)
957        elif use_anchor_data and self.anchor_data:
958            xanchors = numpy.array([val[0] for val in self.anchor_data])
959            yanchors = numpy.array([val[1] for val in self.anchor_data])
960            zanchors = numpy.array([val[2] for val in self.anchor_data])
961            if anchor_radius == None: anchor_radius = numpy.sqrt(xanchors*xanchors +
962                                                                 yanchors*yanchors +
963                                                                 zanchors*zanchors)
964        else:
965            self.create_anchors(len(attr_indices))
966            xanchors = numpy.array([val[0] for val in self.anchor_data])
967            yanchors = numpy.array([val[1] for val in self.anchor_data])
968            zanchors = numpy.array([val[2] for val in self.anchor_data])
969            anchor_radius = numpy.ones(len(attr_indices), numpy.float)
970
971        if normalize_example == 1 or (normalize_example == None
972                                      and self.normalize_examples):
973            m = min(values); M = max(values)
974            if m < 0.0 or M > 1.0: 
975                # we have to do rescaling of values so that all the values will
976                # be in the 0-1 interval
977                #print "example values are not in the 0-1 interval"
978                values = [max(0.0, min(val, 1.0)) for val in values]
979                #m = min(m, 0.0); M = max(M, 1.0); diff = max(M-m, 1e-10)
980                #values = [(val-m) / float(diff) for val in values]
981
982            s = sum(numpy.array(values)*anchor_radius)
983            if s == 0: return [0.0, 0.0]
984            x = self.trueScaleFactor * numpy.dot(xanchors*anchor_radius,
985                                                 values) / float(s)
986            y = self.trueScaleFactor * numpy.dot(yanchors*anchor_radius,
987                                                 values) / float(s)
988            z = self.trueScaleFactor * numpy.dot(zanchors*anchor_radius,
989                                                 values) / float(s)
990        else:
991            x = self.trueScaleFactor * numpy.dot(xanchors, values)
992            y = self.trueScaleFactor * numpy.dot(yanchors, values)
993            z = self.trueScaleFactor * numpy.dot(zanchors, values)
994
995        return [x, y, z]
996
997    getProjectedPointPosition = get_projected_point_position
998
999    @deprecated_keywords({"attrIndices": "attr_indices",
1000                          "settingsDict": "settings_dict"})
1001    def create_projection_as_example_table(self, attr_indices, **settings_dict):
1002        """
1003        Create the projection of attribute indices given in attr_indices and
1004        create an example table with it.
1005        """
1006        if self.data_domain.class_var:
1007            domain = settings_dict.get("domain") or \
1008                     Orange.data.Domain([Orange.feature.Continuous("xVar"),
1009                                         Orange.feature.Continuous("yVar"),
1010                                         Orange.feature.Continuous("zVar"),
1011                                         Orange.feature.Discrete(self.data_domain.class_var.name,
1012                                                                       values=get_variable_values_sorted(self.data_domain.class_var))])
1013        else:
1014            domain = settings_dict.get("domain") or \
1015                     Orange.data.Domain([Orange.feature.Continuous("xVar"),
1016                                         Orange.feature.Continuous("yVar"),
1017                                         Orange.feature.Continuous("zVar")])
1018        data = self.create_projection_as_numeric_array(attr_indices,
1019                                                       **settings_dict)
1020        if data != None:
1021            return Orange.data.Table(domain, data)
1022        else:
1023            return Orange.data.Table(domain)
1024
1025    createProjectionAsExampleTable = create_projection_as_example_table
1026
1027    @deprecated_keywords({"attrIndices": "attr_indices",
1028                          "settingsDict": "settings_dict"})
1029    def create_projection_as_numeric_array(self, attr_indices, **settings_dict):
1030        # load the elements from the settings dict
1031        validData = settings_dict.get("validData")
1032        classList = settings_dict.get("classList")
1033        sum_i     = settings_dict.get("sum_i")
1034        XAnchors = settings_dict.get("XAnchors")
1035        YAnchors = settings_dict.get("YAnchors")
1036        ZAnchors = settings_dict.get("ZAnchors")
1037        scaleFactor = settings_dict.get("scaleFactor", 1.0)
1038        normalize = settings_dict.get("normalize")
1039        jitterSize = settings_dict.get("jitterSize", 0.0)
1040        useAnchorData = settings_dict.get("useAnchorData", 0)
1041        removeMissingData = settings_dict.get("removeMissingData", 1)
1042        useSubsetData = settings_dict.get("useSubsetData", 0)        # use the data or subsetData?
1043        #minmaxVals = settings_dict.get("minmaxVals", None)
1044
1045        # if we want to use anchor data we can get attr_indices from the anchor_data
1046        if useAnchorData and self.anchor_data:
1047            attr_indices = [self.attribute_name_index[val[3]] for val in self.anchor_data]
1048
1049        if validData == None:
1050            if useSubsetData: validData = self.get_valid_subset_list(attr_indices)
1051            else:             validData = self.get_valid_list(attr_indices)
1052        if sum(validData) == 0:
1053            return None
1054
1055        if classList == None and self.data_domain.class_var:
1056            if useSubsetData: classList = self.original_subset_data[self.data_class_index]
1057            else:             classList = self.original_data[self.data_class_index]
1058
1059        # if jitterSize is set below zero we use scaled_data that has already jittered data
1060        if useSubsetData:
1061            if jitterSize < 0.0: data = self.scaled_subset_data
1062            else:                data = self.no_jittering_scaled_subset_data
1063        else:
1064            if jitterSize < 0.0: data = self.scaled_data
1065            else:                data = self.no_jittering_scaled_data
1066
1067        selectedData = numpy.take(data, attr_indices, axis=0)
1068        if removeMissingData:
1069            selectedData = numpy.compress(validData, selectedData, axis=1)
1070            if classList != None and len(classList) != numpy.shape(selectedData)[1]:
1071                classList = numpy.compress(validData, classList)
1072
1073        if useAnchorData and self.anchor_data:
1074            XAnchors = numpy.array([val[0] for val in self.anchor_data])
1075            YAnchors = numpy.array([val[1] for val in self.anchor_data])
1076            ZAnchors = numpy.array([val[2] for val in self.anchor_data])
1077            r = numpy.sqrt(XAnchors*XAnchors + YAnchors*YAnchors + ZAnchors*ZAnchors)     # compute the distance of each anchor from the center of the circle
1078            if normalize == 1 or (normalize == None and self.normalize_examples):
1079                XAnchors *= r
1080                YAnchors *= r
1081                ZAnchors *= r
1082        elif (XAnchors != None and YAnchors != None and ZAnchors != None):
1083            XAnchors = numpy.array(XAnchors)
1084            YAnchors = numpy.array(YAnchors)
1085            ZAnchors = numpy.array(ZAnchors)
1086            r = numpy.sqrt(XAnchors*XAnchors + YAnchors*YAnchors + ZAnchors*ZAnchors)     # compute the distance of each anchor from the center of the circle
1087        else:
1088            self.create_anchors(len(attr_indices))
1089            XAnchors = numpy.array([val[0] for val in self.anchor_data])
1090            YAnchors = numpy.array([val[1] for val in self.anchor_data])
1091            ZAnchors = numpy.array([val[2] for val in self.anchor_data])
1092            r = numpy.ones(len(XAnchors), numpy.float)
1093
1094        x_positions = numpy.dot(XAnchors, selectedData)
1095        y_positions = numpy.dot(YAnchors, selectedData)
1096        z_positions = numpy.dot(ZAnchors, selectedData)
1097
1098        if normalize == 1 or (normalize == None and self.normalize_examples):
1099            if sum_i == None:
1100                sum_i = self._getSum_i(selectedData, useAnchorData, r)
1101            x_positions /= sum_i
1102            y_positions /= sum_i
1103            z_positions /= sum_i
1104            self.trueScaleFactor = scaleFactor
1105        else:
1106            if not removeMissingData:
1107                try:
1108                    x_validData = numpy.compress(validData, x_positions)
1109                    y_validData = numpy.compress(validData, y_positions)
1110                    z_validData = numpy.compress(validData, z_positions)
1111                except:
1112                    print validData
1113                    print x_positions
1114                    print numpy.shape(validData)
1115                    print numpy.shape(x_positions)
1116            else:
1117                x_validData = x_positions
1118                y_validData = y_positions
1119                z_validData = z_positions
1120
1121            dist = math.sqrt(max(x_validData*x_validData + y_validData*y_validData + z_validData*z_validData)) or 1
1122            self.trueScaleFactor = scaleFactor / dist
1123
1124        self.unscaled_x_positions = numpy.array(x_positions)
1125        self.unscaled_y_positions = numpy.array(y_positions)
1126        self.unscaled_z_positions = numpy.array(z_positions)
1127
1128        if self.trueScaleFactor != 1.0:
1129            x_positions *= self.trueScaleFactor
1130            y_positions *= self.trueScaleFactor
1131            z_positions *= self.trueScaleFactor
1132
1133        if jitterSize > 0.0:
1134            x_positions += numpy.random.uniform(-jitterSize, jitterSize, len(x_positions))
1135            y_positions += numpy.random.uniform(-jitterSize, jitterSize, len(y_positions))
1136            z_positions += numpy.random.uniform(-jitterSize, jitterSize, len(z_positions))
1137
1138        self.last_attr_indices = attr_indices
1139        if classList != None:
1140            return numpy.transpose(numpy.array((x_positions, y_positions, z_positions, classList)))
1141        else:
1142            return numpy.transpose(numpy.array((x_positions, y_positions, z_positions)))
1143
1144    createProjectionAsNumericArray = create_projection_as_numeric_array
1145
1146    @deprecated_keywords({"useAnchorData": "use_anchor_data",
1147                          "anchorRadius": "anchor_radius"})
1148    def _getsum_i(self, data, use_anchor_data=0, anchor_radius=None):
1149        """
1150        Function to compute the sum of all values for each element in the data.
1151        Used to normalize.
1152       
1153        """
1154        if use_anchor_data:
1155            if anchor_radius == None:
1156                anchor_radius = numpy.sqrt([a[0]**2+a[1]**2+a[2]**2 for a in self.anchor_data])
1157            sum_i = numpy.add.reduce(numpy.transpose(numpy.transpose(data)*anchor_radius))
1158        else:
1159            sum_i = numpy.add.reduce(data)
1160        if len(numpy.nonzero(sum_i)) < len(sum_i):    # test if there are zeros in sum_i
1161            sum_i += numpy.where(sum_i == 0, 1.0, 0.0)
1162        return sum_i
1163   
1164    _getSum_i = _getsum_i
1165
1166ScaleLinProjData3D = deprecated_members({"setAnchors": "set_anchors",
1167                                       "createAnchors": "create_anchors",
1168                                       "saveProjectionAsTabData": "save_projection_as_tab_data",
1169                                       "getProjectedPointPosition":
1170                                           "get_projected_point_position",
1171                                       "createProjectionAsExampleTable":
1172                                           "create_projection_as_example_table",
1173                                       "createProjectionAsNumericArray":
1174                                           "create_projection_as_numeric_array",
1175                                       "_getSum_i": "_getsum_i",
1176                                       "normalizeExamples": "normalize_examples",
1177                                       "anchorData": "anchor_data",
1178                                       "lastAttrIndices": "last_attr_indices",
1179                                       "anchorDict": "anchor_dict",
1180                                      })(ScaleLinProjData3D)
1181
1182class ScalePolyvizData(ScaleLinProjData):
1183    def __init__(self):
1184        ScaleLinProjData.__init__(self)
1185        self.normalize_examples = 1
1186        self.anchor_data =[]        # form: [(anchor1x, anchor1y, label1),(anchor2x, anchor2y, label2), ...]
1187       
1188
1189    # attributeReverse, validData = None, classList = None, sum_i = None, XAnchors = None, YAnchors = None, domain = None, scaleFactor = 1.0, jitterSize = 0.0
1190    @deprecated_keywords({"attrIndices": "attr_indices",
1191                          "settingsDict": "settings_dict"})
1192    def create_projection_as_example_table(self, attr_list, **settings_dict):
1193        if self.data_domain.class_var:
1194            domain = settings_dict.get("domain") or \
1195                     Orange.data.Domain([Orange.feature.Continuous("xVar"),
1196                                         Orange.feature.Continuous("yVar"),
1197                                         Orange.feature.Discrete(self.data_domain.class_var.name,
1198                                                                       values = get_variable_values_sorted(self.data_domain.class_var))])
1199        else:
1200            domain = settings_dict.get("domain") or \
1201                     Orange.data.Domain([Orange.feature.Continuous("xVar"),
1202                                         Orange.feature.Continuous("yVar")])
1203        data = self.create_projection_as_numeric_array(attr_list, **settings_dict)
1204        if data != None:
1205            return Orange.data.Table(domain, data)
1206        else:
1207            return Orange.data.Table(domain)
1208   
1209    createProjectionAsExampleTable = create_projection_as_example_table
1210   
1211    @deprecated_keywords({"attrIndices": "attr_indices",
1212                          "settingsDict": "settings_dict"})
1213    def create_projection_as_numeric_array(self, attr_indices, **settings_dict):
1214        # load the elements from the settings dict
1215        attributeReverse = settings_dict.get("reverse", [0]*len(attr_indices))
1216        validData = settings_dict.get("validData")
1217        classList = settings_dict.get("classList")
1218        sum_i     = settings_dict.get("sum_i")
1219        XAnchors  = settings_dict.get("XAnchors")
1220        YAnchors  = settings_dict.get("YAnchors")
1221        scaleFactor = settings_dict.get("scaleFactor", 1.0)
1222        jitterSize  = settings_dict.get("jitterSize", 0.0)
1223        removeMissingData = settings_dict.get("removeMissingData", 1)
1224       
1225        if validData == None:
1226            validData = self.get_valid_list(attr_indices)
1227        if sum(validData) == 0:
1228            return None
1229
1230        if classList == None and self.data_has_class:
1231            classList = self.original_data[self.data_class_index] 
1232
1233        if removeMissingData:
1234            selectedData = numpy.compress(validData,
1235                                          numpy.take(self.no_jittering_scaled_data,
1236                                                     attr_indices, axis = 0),
1237                                                     axis = 1)
1238            if classList != None and len(classList) != numpy.shape(selectedData)[1]:
1239                classList = numpy.compress(validData, classList)
1240        else:
1241            selectedData = numpy.take(self.no_jittering_scaled_data,
1242                                      attr_indices, axis = 0)
1243       
1244        if sum_i == None:
1245            sum_i = self._getSum_i(selectedData)
1246
1247        if XAnchors == None or YAnchors == None:
1248            XAnchors = self.create_xanchors(len(attr_indices))
1249            YAnchors = self.create_yanchors(len(attr_indices))
1250
1251        xanchors = numpy.zeros(numpy.shape(selectedData), numpy.float)
1252        yanchors = numpy.zeros(numpy.shape(selectedData), numpy.float)
1253        length = len(attr_indices)
1254
1255        for i in range(length):
1256            if attributeReverse[i]:
1257                xanchors[i] = selectedData[i] * XAnchors[i] + (1-selectedData[i]) * XAnchors[(i+1)%length]
1258                yanchors[i] = selectedData[i] * YAnchors[i] + (1-selectedData[i]) * YAnchors[(i+1)%length]
1259            else:
1260                xanchors[i] = (1-selectedData[i]) * XAnchors[i] + selectedData[i] * XAnchors[(i+1)%length]
1261                yanchors[i] = (1-selectedData[i]) * YAnchors[i] + selectedData[i] * YAnchors[(i+1)%length]
1262
1263        x_positions = numpy.sum(numpy.multiply(xanchors, selectedData), axis=0)/sum_i
1264        y_positions = numpy.sum(numpy.multiply(yanchors, selectedData), axis=0)/sum_i
1265        #x_positions = numpy.sum(numpy.transpose(xanchors* numpy.transpose(selectedData)), axis=0) / sum_i
1266        #y_positions = numpy.sum(numpy.transpose(yanchors* numpy.transpose(selectedData)), axis=0) / sum_i
1267
1268        if scaleFactor != 1.0:
1269            x_positions = x_positions * scaleFactor
1270            y_positions = y_positions * scaleFactor
1271        if jitterSize > 0.0:
1272            x_positions += (numpy.random.random(len(x_positions))-0.5)*jitterSize
1273            y_positions += (numpy.random.random(len(y_positions))-0.5)*jitterSize
1274
1275        if classList != None:
1276            return numpy.transpose(numpy.array((x_positions, y_positions, classList)))
1277        else:
1278            return numpy.transpose(numpy.array((x_positions, y_positions)))
1279
1280    createProjectionAsNumericArray = create_projection_as_numeric_array
1281
1282    @deprecated_keywords({"attrIndices": "attr_indices",
1283                          "settingsDict": "settings_dict"})
1284    def get_projected_point_position(self, attr_indices, values, **settings_dict):
1285        # load the elements from the settings dict
1286        attributeReverse = settings_dict.get("reverse", [0]*len(attr_indices))
1287        useAnchorData = settings_dict.get("useAnchorData")
1288        XAnchors = settings_dict.get("XAnchors")
1289        YAnchors = settings_dict.get("YAnchors")
1290   
1291        if XAnchors != None and YAnchors != None:
1292            XAnchors = numpy.array(XAnchors)
1293            YAnchors = numpy.array(YAnchors)
1294        elif useAnchorData:
1295            XAnchors = numpy.array([val[0] for val in self.anchor_data])
1296            YAnchors = numpy.array([val[1] for val in self.anchor_data])
1297        else:
1298            XAnchors = self.create_xanchors(len(attr_indices))
1299            YAnchors = self.create_yanchors(len(attr_indices))
1300
1301        m = min(values); M = max(values)
1302        if m < 0.0 or M > 1.0:  # we have to do rescaling of values so that all
1303            # the values will be in the 0-1 interval
1304            values = [max(0.0, min(val, 1.0)) for val in values]
1305            #m = min(m, 0.0); M = max(M, 1.0); diff = max(M-m, 1e-10)
1306            #values = [(val-m) / float(diff) for val in values]
1307       
1308        s = sum(numpy.array(values))
1309        if s == 0: return [0.0, 0.0]
1310
1311        length = len(values)
1312        xanchors = numpy.zeros(length, numpy.float)
1313        yanchors = numpy.zeros(length, numpy.float)
1314        for i in range(length):
1315            if attributeReverse[i]:
1316                xanchors[i] = values[i] * XAnchors[i] + (1-values[i]) * XAnchors[(i+1)%length]
1317                yanchors[i] = values[i] * YAnchors[i] + (1-values[i]) * YAnchors[(i+1)%length]
1318            else:
1319                xanchors[i] = (1-values[i]) * XAnchors[i] + values[i] * XAnchors[(i+1)%length]
1320                yanchors[i] = (1-values[i]) * YAnchors[i] + values[i] * YAnchors[(i+1)%length]
1321
1322        x_positions = numpy.sum(numpy.dot(xanchors, values), axis=0) / float(s)
1323        y_positions = numpy.sum(numpy.dot(yanchors, values), axis=0) / float(s)
1324        return [x, y]
1325   
1326    getProjectedPointPosition = get_projected_point_position
1327
1328ScalePolyvizData = deprecated_members({"createProjectionAsExampleTable":
1329                                           "create_projection_as_example_table",
1330                                       "createProjectionAsNumericArray":
1331                                           "create_projection_as_numeric_array",
1332                                       "getProjectedPointPosition":
1333                                           "get_projected_point_position"
1334                                       })(ScalePolyvizData)
1335
1336class ScaleScatterPlotData(ScaleData):
1337    def get_original_data(self, indices):
1338        data = self.original_data.take(indices, axis = 0)
1339        for i, ind in enumerate(indices):
1340            [minVal, maxVal] = self.attr_values[self.data_domain[ind].name]
1341            if self.data_domain[ind].varType == Orange.core.VarTypes.Discrete:
1342                data[i] += (self.jitter_size/50.0)*(numpy.random.random(len(self.raw_data)) - 0.5)
1343            elif self.data_domain[ind].varType == Orange.core.VarTypes.Continuous and self.jitter_continuous:
1344                data[i] += (self.jitter_size/(50.0*(maxVal-minVal or 1)))*(numpy.random.random(len(self.raw_data)) - 0.5)
1345        return data
1346   
1347    getOriginalData = get_original_data
1348   
1349    def get_original_subset_data(self, indices):
1350        data = self.original_subset_data.take(indices, axis = 0)
1351        for i, ind in enumerate(indices):
1352            [minVal, maxVal] = self.attr_values[self.raw_subset_data.domain[ind].name]
1353            if self.data_domain[ind].varType == Orange.core.VarTypes.Discrete:
1354                data[i] += (self.jitter_size/(50.0*max(1, maxVal)))*(numpy.random.random(len(self.raw_subset_data)) - 0.5)
1355            elif self.data_domain[ind].varType == Orange.core.VarTypes.Continuous and self.jitter_continuous:
1356                data[i] += (self.jitter_size/(50.0*(maxVal-minVal or 1)))*(numpy.random.random(len(self.raw_subset_data)) - 0.5)
1357        return data
1358
1359    getOriginalSubsetData = get_original_subset_data
1360
1361    @deprecated_keywords({"xAttr": "xattr", "yAttr": "yattr"})
1362    def get_xy_data_positions(self, xattr, yattr):
1363        """
1364        Create x-y projection of attributes in attrlist.
1365       
1366        """
1367        xattr_index, yattr_index = self.attribute_name_index[xattr], self.attribute_name_index[yattr]
1368
1369        xdata = self.scaled_data[xattr_index].copy()
1370        ydata = self.scaled_data[yattr_index].copy()
1371       
1372        if self.data_domain[xattr_index].varType == Orange.core.VarTypes.Discrete: xdata = ((xdata * 2*len(self.data_domain[xattr_index].values)) - 1.0) / 2.0
1373        else:  xdata = xdata * (self.attr_values[xattr][1] - self.attr_values[xattr][0]) + float(self.attr_values[xattr][0])
1374
1375        if self.data_domain[yattr_index].varType == Orange.core.VarTypes.Discrete: ydata = ((ydata * 2*len(self.data_domain[yattr_index].values)) - 1.0) / 2.0
1376        else:  ydata = ydata * (self.attr_values[yattr][1] - self.attr_values[yattr][0]) + float(self.attr_values[yattr][0])
1377        return (xdata, ydata)
1378   
1379    getXYDataPositions = get_xy_data_positions
1380   
1381    @deprecated_keywords({"xAttr": "xattr", "yAttr": "yattr"})
1382    def get_xy_subset_data_positions(self, xattr, yattr):
1383        """
1384        Create x-y projection of attributes in attr_list.
1385       
1386        """
1387        xattr_index, yattr_index = self.attribute_name_index[xattr], self.attribute_name_index[yattr]
1388
1389        xdata = self.scaled_subset_data[xattr_index].copy()
1390        ydata = self.scaled_subset_data[yattr_index].copy()
1391       
1392        if self.data_domain[xattr_index].varType == Orange.core.VarTypes.Discrete: xdata = ((xdata * 2*len(self.data_domain[xattr_index].values)) - 1.0) / 2.0
1393        else:  xdata = xdata * (self.attr_values[xattr][1] - self.attr_values[xattr][0]) + float(self.attr_values[xattr][0])
1394
1395        if self.data_domain[yattr_index].varType == Orange.core.VarTypes.Discrete: ydata = ((ydata * 2*len(self.data_domain[yattr_index].values)) - 1.0) / 2.0
1396        else:  ydata = ydata * (self.attr_values[yattr][1] - self.attr_values[yattr][0]) + float(self.attr_values[yattr][0])
1397        return (xdata, ydata)
1398   
1399    getXYSubsetDataPositions = get_xy_subset_data_positions
1400   
1401    @deprecated_keywords({"attrIndices": "attr_indices",
1402                          "settingsDict": "settings_dict"})
1403    def get_projected_point_position(self, attr_indices, values, **settings_dict):
1404        """
1405        For attributes in attr_indices and values of these attributes in values
1406        compute point positions this function has more sense in radviz and
1407        polyviz methods. settings_dict has to be because radviz and polyviz have
1408        this parameter.
1409        """
1410        return values
1411
1412    getProjectedPointPosition = get_projected_point_position
1413
1414    @deprecated_keywords({"attrIndices": "attr_indices",
1415                          "settingsDict": "settings_dict"})
1416    def create_projection_as_example_table(self, attr_indices, **settings_dict):
1417        """
1418        Create the projection of attribute indices given in attr_indices and
1419        create an example table with it.
1420       
1421        """
1422        if self.data_has_class:
1423            domain = settings_dict.get("domain") or \
1424                     Orange.data.Domain([Orange.feature.Continuous(self.data_domain[attr_indices[0]].name),
1425                                         Orange.feature.Continuous(self.data_domain[attr_indices[1]].name),
1426                                         Orange.feature.Discrete(self.data_domain.class_var.name,
1427                                                                       values = get_variable_values_sorted(self.data_domain.class_var))])
1428        else:
1429            domain = settings_dict.get("domain") or \
1430                     Orange.data.Domain([Orange.feature.Continuous(self.data_domain[attr_indices[0]].name),
1431                                         Orange.feature.Continuous(self.data_domain[attr_indices[1]].name)])
1432
1433        data = self.create_projection_as_numeric_array(attr_indices,
1434                                                       **settings_dict)
1435        if data != None:
1436            return Orange.data.Table(domain, data)
1437        else:
1438            return Orange.data.Table(domain)
1439
1440    createProjectionAsExampleTable = create_projection_as_example_table
1441
1442    @deprecated_keywords({"attrIndices": "attr_indices",
1443                          "settingsDict": "settings_dict"})
1444    def create_projection_as_example_table_3D(self, attr_indices, **settings_dict):
1445        """
1446        Create the projection of attribute indices given in attr_indices and
1447        create an example table with it.
1448       
1449        """
1450        if self.data_has_class:
1451            domain = settings_dict.get("domain") or \
1452                     Orange.data.Domain([Orange.feature.Continuous(self.data_domain[attr_indices[0]].name),
1453                                         Orange.feature.Continuous(self.data_domain[attr_indices[1]].name),
1454                                         Orange.feature.Continuous(self.data_domain[attr_indices[2]].name),
1455                                         Orange.feature.Discrete(self.data_domain.class_var.name,
1456                                                                       values = get_variable_values_sorted(self.data_domain.class_var))])
1457        else:
1458            domain = settings_dict.get("domain") or \
1459                     Orange.data.Domain([Orange.feature.Continuous(self.data_domain[attr_indices[0]].name),
1460                                         Orange.feature.Continuous(self.data_domain[attr_indices[1]].name),
1461                                         Orange.feature.Continuous(self.data_domain[attr_indices[2]].name)])
1462
1463        data = self.create_projection_as_numeric_array_3D(attr_indices,
1464                                                          **settings_dict)
1465        if data != None:
1466            return Orange.data.Table(domain, data)
1467        else:
1468            return Orange.data.Table(domain)
1469
1470    createProjectionAsExampleTable3D = create_projection_as_example_table_3D
1471
1472    @deprecated_keywords({"attrIndices": "attr_indices",
1473                          "settingsDict": "settings_dict"})
1474    def create_projection_as_numeric_array(self, attr_indices, **settings_dict):
1475        validData = settings_dict.get("validData")
1476        classList = settings_dict.get("classList")
1477        jitterSize = settings_dict.get("jitter_size", 0.0)
1478
1479        if validData == None:
1480            validData = self.get_valid_list(attr_indices)
1481        if sum(validData) == 0:
1482            return None
1483
1484        if classList == None and self.data_has_class:
1485            classList = self.original_data[self.data_class_index]
1486
1487        xArray = self.no_jittering_scaled_data[attr_indices[0]]
1488        yArray = self.no_jittering_scaled_data[attr_indices[1]]
1489        if jitterSize > 0.0:
1490            xArray += (numpy.random.random(len(xArray))-0.5)*jitterSize
1491            yArray += (numpy.random.random(len(yArray))-0.5)*jitterSize
1492        if classList != None:
1493            data = numpy.compress(validData, numpy.array((xArray, yArray, classList)), axis = 1)
1494        else:
1495            data = numpy.compress(validData, numpy.array((xArray, yArray)), axis = 1)
1496        data = numpy.transpose(data)
1497        return data
1498
1499    createProjectionAsNumericArray = create_projection_as_numeric_array
1500
1501    @deprecated_keywords({"attrIndices": "attr_indices",
1502                          "settingsDict": "settings_dict"})
1503    def create_projection_as_numeric_array_3D(self, attr_indices, **settings_dict):
1504        validData = settings_dict.get("validData")
1505        classList = settings_dict.get("classList")
1506        jitterSize = settings_dict.get("jitter_size", 0.0)
1507
1508        if validData == None:
1509            validData = self.get_valid_list(attr_indices)
1510        if sum(validData) == 0:
1511            return None
1512
1513        if classList == None and self.data_has_class:
1514            classList = self.original_data[self.data_class_index]
1515
1516        xArray = self.no_jittering_scaled_data[attr_indices[0]]
1517        yArray = self.no_jittering_scaled_data[attr_indices[1]]
1518        zArray = self.no_jittering_scaled_data[attr_indices[2]]
1519        if jitterSize > 0.0:
1520            xArray += (numpy.random.random(len(xArray))-0.5)*jitterSize
1521            yArray += (numpy.random.random(len(yArray))-0.5)*jitterSize
1522            zArray += (numpy.random.random(len(zArray))-0.5)*jitterSize
1523        if classList != None:
1524            data = numpy.compress(validData, numpy.array((xArray, yArray, zArray, classList)), axis = 1)
1525        else:
1526            data = numpy.compress(validData, numpy.array((xArray, yArray, zArray)), axis = 1)
1527        data = numpy.transpose(data)
1528        return data
1529
1530    createProjectionAsNumericArray3D = create_projection_as_numeric_array_3D
1531
1532    @deprecated_keywords({"attributeNameOrder": "attribute_name_order",
1533                          "addResultFunct": "add_result_funct"})
1534    def get_optimal_clusters(self, attribute_name_order, add_result_funct):
1535        if not self.data_has_class or self.data_has_continuous_class:
1536            return
1537       
1538        jitter_size = 0.001 * self.clusterOptimization.jitterDataBeforeTriangulation
1539        domain = Orange.data.Domain([Orange.feature.Continuous("xVar"),
1540                                     Orange.feature.Continuous("yVar"),
1541                                    self.data_domain.class_var])
1542
1543        # init again, in case that the attribute ordering took too much time
1544        self.scatterWidget.progressBarInit()
1545        startTime = time.time()
1546        count = len(attribute_name_order)*(len(attribute_name_order)-1)/2
1547        testIndex = 0
1548
1549        for i in range(len(attribute_name_order)):
1550            for j in range(i):
1551                try:
1552                    attr1 = self.attribute_name_index[attribute_name_order[j]]
1553                    attr2 = self.attribute_name_index[attribute_name_order[i]]
1554                    testIndex += 1
1555                    if self.clusterOptimization.isOptimizationCanceled():
1556                        secs = time.time() - startTime
1557                        self.clusterOptimization.setStatusBarText("Evaluation stopped (evaluated %d projections in %d min, %d sec)"
1558                                                                  % (testIndex, secs/60, secs%60))
1559                        self.scatterWidget.progressBarFinished()
1560                        return
1561
1562                    data = self.create_projection_as_example_table([attr1, attr2],
1563                                                                   domain = domain,
1564                                                                   jitter_size = jitter_size)
1565                    graph, valueDict, closureDict, polygonVerticesDict, enlargedClosureDict, otherDict = self.clusterOptimization.evaluateClusters(data)
1566
1567                    allValue = 0.0
1568                    classesDict = {}
1569                    for key in valueDict.keys():
1570                        add_result_funct(valueDict[key], closureDict[key],
1571                                         polygonVerticesDict[key],
1572                                         [attribute_name_order[i],
1573                                          attribute_name_order[j]],
1574                                          int(graph.objects[polygonVerticesDict[key][0]].getclass()),
1575                                          enlargedClosureDict[key], otherDict[key])
1576                        classesDict[key] = int(graph.objects[polygonVerticesDict[key][0]].getclass())
1577                        allValue += valueDict[key]
1578                    add_result_funct(allValue, closureDict, polygonVerticesDict,
1579                                     [attribute_name_order[i], attribute_name_order[j]],
1580                                     classesDict, enlargedClosureDict, otherDict)     # add all the clusters
1581                   
1582                    self.clusterOptimization.setStatusBarText("Evaluated %d projections..."
1583                                                              % (testIndex))
1584                    self.scatterWidget.progressBarSet(100.0*testIndex/float(count))
1585                    del data, graph, valueDict, closureDict, polygonVerticesDict, enlargedClosureDict, otherDict, classesDict
1586                except:
1587                    type, val, traceback = sys.exc_info()
1588                    sys.excepthook(type, val, traceback)  # print the exception
1589       
1590        secs = time.time() - startTime
1591        self.clusterOptimization.setStatusBarText("Finished evaluation (evaluated %d projections in %d min, %d sec)" % (testIndex, secs/60, secs%60))
1592        self.scatterWidget.progressBarFinished()
1593   
1594    getOptimalClusters = get_optimal_clusters
1595
1596ScaleScatterPlotData = deprecated_members({"getOriginalData": "get_original_data",
1597                                           "getOriginalSubsetData": "get_original_subset_data",
1598                                           "getXYDataPositions": "get_xy_data_positions",
1599                                           "getXYSubsetDataPositions": "get_xy_subset_data_positions",
1600                                           "getProjectedPointPosition": "get_projected_point_position",
1601                                           "createProjectionAsExampleTable": "create_projection_as_example_table",
1602                                           "createProjectionAsExampleTable3D": "create_projection_as_example_table_3D",
1603                                           "createProjectionAsNumericArray": "create_projection_as_numeric_array",
1604                                           "createProjectionAsNumericArray3D": "create_projection_as_numeric_array_3D",
1605                                           "getOptimalClusters": "get_optimal_clusters"
1606                                           })(ScaleScatterPlotData)
Note: See TracBrowser for help on using the repository browser.