source: orange/Orange/preprocess/scaling.py @ 9671:a7b056375472

Revision 9671:a7b056375472, 77.9 KB checked in by anze <anze.staric@…>, 2 years ago (diff)

Moved orange to Orange (part 2)

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 orngDataCaching import *
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 = 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 getCached(data, "visualizationData") and subset_data == None:
312            self.original_data, self.no_jittering_scaled_data, self.valid_data_array = 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: setCached(data, "visualizationData",
353                           (self.original_data, self.no_jittering_scaled_data,
354                            self.valid_data_array))
355        if subset_data: 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.data.variable.Continuous("xVar"),
680                                         Orange.data.variable.Continuous("yVar"),
681                                         Orange.data.variable.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.data.variable.Continuous("xVar"),
686                                         Orange.data.variable.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                                       "get_projected_point_position": "get_projected_point_position",
827                                       "create_projection_as_example_table": "create_projection_as_example_table",
828                                       "create_projection_as_numeric_array": "create_projection_as_numeric_array",
829                                       "_getSum_i": "_getsum_i",
830                                       "normalizeExamples": "normalize_examples",
831                                       "anchorData": "anchor_data",
832                                       "lastAttrIndices": "last_attr_indices",
833                                       "anchorDict": "anchor_dict",
834                                      })(ScaleLinProjData)
835
836class ScaleLinProjData3D(ScaleData):
837    def __init__(self):
838        ScaleData.__init__(self)
839        self.normalize_examples = 1
840        self.anchor_data = []        # form: [(anchor1x, anchor1y, anchor1z, label1),(anchor2x, anchor2y, anchor2z, label2), ...]
841        self.last_attr_indices = None
842        self.anchor_dict = {}
843
844    @deprecated_keywords({"xAnchors": "xanchors", "yAnchors": "yanchors"})
845    def set_anchors(self, xanchors, yanchors, zanchors, attributes):
846        if attributes:
847            if xanchors != None and yanchors != None and zanchors != None:
848                self.anchor_data = [(xanchors[i], yanchors[i], zanchors[i], attributes[i])
849                                    for i in range(len(attributes))]
850            else:
851                self.anchor_data = self.create_anchors(len(attributes), attributes)
852
853    setAnchors = set_anchors
854
855    @deprecated_keywords({"numOfAttr": "num_of_attr"})
856    def create_anchors(self, num_of_attrs, labels=None):
857        """
858        Create anchors on the sphere.
859       
860        """
861        # Golden Section Spiral algorithm approximates even distribution of points on a sphere
862        # (read more here http://www.softimageblog.com/archives/115)
863        n = num_of_attrs
864        xanchors = []
865        yanchors = []
866        zanchors = []
867
868        inc = math.pi * (3 - math.sqrt(5))
869        off = 2. / n
870        for k in range(n):
871            y = k * off - 1 + (off / 2)
872            r = math.sqrt(1 - y*y)
873            phi = k * inc
874            xanchors.append(math.cos(phi)*r)
875            yanchors.append(y)
876            zanchors.append(math.sin(phi)*r)
877
878        self.anchor_dict[num_of_attrs] = [xanchors, yanchors, zanchors]
879 
880        if labels:
881            return [(xanchors[i], yanchors[i], zanchors[i], labels[i]) for i in range(num_of_attrs)]
882        else:
883            return [(xanchors[i], yanchors[i], zanchors[i]) for i in range(num_of_attrs)]
884
885    createAnchors = create_anchors
886
887    @deprecated_keywords({"numOfAttrs": "num_of_attrs"})
888    def create_xanchors(self, num_of_attrs):
889        if not self.anchor_dict.has_key(num_of_attrs):
890            self.create_anchors(num_of_attrs)
891        return self.anchor_dict[num_of_attrs][0]
892
893    createXAnchors = create_xanchors
894
895    @deprecated_keywords({"numOfAttrs": "num_of_attrs"})
896    def create_yanchors(self, num_of_attrs):
897        if not self.anchor_dict.has_key(num_of_attrs):
898            self.create_anchors(num_of_attrs)
899        return self.anchor_dict[num_of_attrs][1]
900
901    createYAnchors = create_yanchors
902
903    @deprecated_keywords({"numOfAttrs": "num_of_attrs"})
904    def create_zanchors(self, num_of_attrs):
905        if not self.anchor_dict.has_key(num_of_attrs):
906            self.create_anchors(num_of_attrs)
907        return self.anchor_dict[num_of_attrs][2]
908
909    createZAnchors = create_zanchors
910
911    @deprecated_keywords({"fileName": "filename", "attrList": "attrlist",
912                          "useAnchorData": "use_anchor_data"})
913    def save_projection_as_tab_data(self, filename, attrlist, use_anchor_data=0):
914        """
915        Save projection (xattr, yattr, zattr, classval) into a filename filename.
916       
917        """
918        Orange.core.saveTabDelimited(filename,
919            self.create_projection_as_example_table([self.attribute_name_index[i]
920                                                     for i in attrlist],
921                                                    use_anchor_data=use_anchor_data))
922   
923    saveProjectionAsTabData = save_projection_as_tab_data
924
925    @deprecated_keywords({"attrIndices": "attr_indices",
926                          "settingsDict": "settings_dict"})
927    def get_projected_point_position(self, attr_indices, values, **settings_dict):
928        """
929        For attributes in attr_indices and values of these attributes in values
930        compute point positions. This function has more sense in radviz and
931        polyviz methods.
932   
933        """
934        # load the elements from the settings dict
935        use_anchor_data = settings_dict.get("useAnchorData")
936        xanchors = settings_dict.get('xAnchors')
937        yanchors = settings_dict.get('yAnchors')
938        zanchors = settings_dict.get('zAnchors')
939        anchor_radius = settings_dict.get("anchorRadius")
940        normalize_example = settings_dict.get("normalizeExample")
941
942        if attr_indices != self.last_attr_indices:
943            print "get_projected_point_position. Warning: Possible bug. The "+\
944                  "set of attributes is not the same as when computing the "+\
945                  "whole projection"
946
947        if xanchors != None and yanchors != None and zanchors != None:
948            xanchors = numpy.array(xanchors)
949            yanchors = numpy.array(yanchors)
950            zanchors = numpy.array(zanchors)
951            if anchor_radius == None: anchor_radius = numpy.sqrt(xanchors*xanchors +
952                                                                 yanchors*yanchors +
953                                                                 zanchors*zanchors)
954        elif use_anchor_data and self.anchor_data:
955            xanchors = numpy.array([val[0] for val in self.anchor_data])
956            yanchors = numpy.array([val[1] for val in self.anchor_data])
957            zanchors = numpy.array([val[2] for val in self.anchor_data])
958            if anchor_radius == None: anchor_radius = numpy.sqrt(xanchors*xanchors +
959                                                                 yanchors*yanchors +
960                                                                 zanchors*zanchors)
961        else:
962            self.create_anchors(len(attr_indices))
963            xanchors = numpy.array([val[0] for val in self.anchor_data])
964            yanchors = numpy.array([val[1] for val in self.anchor_data])
965            zanchors = numpy.array([val[2] for val in self.anchor_data])
966            anchor_radius = numpy.ones(len(attr_indices), numpy.float)
967
968        if normalize_example == 1 or (normalize_example == None
969                                      and self.normalize_examples):
970            m = min(values); M = max(values)
971            if m < 0.0 or M > 1.0: 
972                # we have to do rescaling of values so that all the values will
973                # be in the 0-1 interval
974                #print "example values are not in the 0-1 interval"
975                values = [max(0.0, min(val, 1.0)) for val in values]
976                #m = min(m, 0.0); M = max(M, 1.0); diff = max(M-m, 1e-10)
977                #values = [(val-m) / float(diff) for val in values]
978
979            s = sum(numpy.array(values)*anchor_radius)
980            if s == 0: return [0.0, 0.0]
981            x = self.trueScaleFactor * numpy.dot(xanchors*anchor_radius,
982                                                 values) / float(s)
983            y = self.trueScaleFactor * numpy.dot(yanchors*anchor_radius,
984                                                 values) / float(s)
985            z = self.trueScaleFactor * numpy.dot(zanchors*anchor_radius,
986                                                 values) / float(s)
987        else:
988            x = self.trueScaleFactor * numpy.dot(xanchors, values)
989            y = self.trueScaleFactor * numpy.dot(yanchors, values)
990            z = self.trueScaleFactor * numpy.dot(zanchors, values)
991
992        return [x, y, z]
993
994    getProjectedPointPosition = get_projected_point_position
995
996    @deprecated_keywords({"attrIndices": "attr_indices",
997                          "settingsDict": "settings_dict"})
998    def create_projection_as_example_table(self, attr_indices, **settings_dict):
999        """
1000        Create the projection of attribute indices given in attr_indices and
1001        create an example table with it.
1002        """
1003        if self.data_domain.class_var:
1004            domain = settings_dict.get("domain") or \
1005                     Orange.data.Domain([Orange.data.variable.Continuous("xVar"),
1006                                         Orange.data.variable.Continuous("yVar"),
1007                                         Orange.data.variable.Continuous("zVar"),
1008                                         Orange.data.variable.Discrete(self.data_domain.class_var.name,
1009                                                                       values=get_variable_values_sorted(self.data_domain.class_var))])
1010        else:
1011            domain = settings_dict.get("domain") or \
1012                     Orange.data.Domain([Orange.data.variable.Continuous("xVar"),
1013                                         Orange.data.variable.Continuous("yVar"),
1014                                         Orange.data.variable.Continuous("zVar")])
1015        data = self.create_projection_as_numeric_array(attr_indices,
1016                                                       **settings_dict)
1017        if data != None:
1018            return Orange.data.Table(domain, data)
1019        else:
1020            return Orange.data.Table(domain)
1021
1022    createProjectionAsExampleTable = create_projection_as_example_table
1023
1024    @deprecated_keywords({"attrIndices": "attr_indices",
1025                          "settingsDict": "settings_dict"})
1026    def create_projection_as_numeric_array(self, attr_indices, **settings_dict):
1027        # load the elements from the settings dict
1028        validData = settings_dict.get("validData")
1029        classList = settings_dict.get("classList")
1030        sum_i     = settings_dict.get("sum_i")
1031        XAnchors = settings_dict.get("XAnchors")
1032        YAnchors = settings_dict.get("YAnchors")
1033        ZAnchors = settings_dict.get("ZAnchors")
1034        scaleFactor = settings_dict.get("scaleFactor", 1.0)
1035        normalize = settings_dict.get("normalize")
1036        jitterSize = settings_dict.get("jitterSize", 0.0)
1037        useAnchorData = settings_dict.get("useAnchorData", 0)
1038        removeMissingData = settings_dict.get("removeMissingData", 1)
1039        useSubsetData = settings_dict.get("useSubsetData", 0)        # use the data or subsetData?
1040        #minmaxVals = settings_dict.get("minmaxVals", None)
1041
1042        # if we want to use anchor data we can get attr_indices from the anchor_data
1043        if useAnchorData and self.anchor_data:
1044            attr_indices = [self.attribute_name_index[val[3]] for val in self.anchor_data]
1045
1046        if validData == None:
1047            if useSubsetData: validData = self.get_valid_subset_list(attr_indices)
1048            else:             validData = self.get_valid_list(attr_indices)
1049        if sum(validData) == 0:
1050            return None
1051
1052        if classList == None and self.data_domain.class_var:
1053            if useSubsetData: classList = self.original_subset_data[self.data_class_index]
1054            else:             classList = self.original_data[self.data_class_index]
1055
1056        # if jitterSize is set below zero we use scaled_data that has already jittered data
1057        if useSubsetData:
1058            if jitterSize < 0.0: data = self.scaled_subset_data
1059            else:                data = self.no_jittering_scaled_subset_data
1060        else:
1061            if jitterSize < 0.0: data = self.scaled_data
1062            else:                data = self.no_jittering_scaled_data
1063
1064        selectedData = numpy.take(data, attr_indices, axis=0)
1065        if removeMissingData:
1066            selectedData = numpy.compress(validData, selectedData, axis=1)
1067            if classList != None and len(classList) != numpy.shape(selectedData)[1]:
1068                classList = numpy.compress(validData, classList)
1069
1070        if useAnchorData and self.anchor_data:
1071            XAnchors = numpy.array([val[0] for val in self.anchor_data])
1072            YAnchors = numpy.array([val[1] for val in self.anchor_data])
1073            ZAnchors = numpy.array([val[2] for val in self.anchor_data])
1074            r = numpy.sqrt(XAnchors*XAnchors + YAnchors*YAnchors + ZAnchors*ZAnchors)     # compute the distance of each anchor from the center of the circle
1075            if normalize == 1 or (normalize == None and self.normalize_examples):
1076                XAnchors *= r
1077                YAnchors *= r
1078                ZAnchors *= r
1079        elif (XAnchors != None and YAnchors != None and ZAnchors != None):
1080            XAnchors = numpy.array(XAnchors)
1081            YAnchors = numpy.array(YAnchors)
1082            ZAnchors = numpy.array(ZAnchors)
1083            r = numpy.sqrt(XAnchors*XAnchors + YAnchors*YAnchors + ZAnchors*ZAnchors)     # compute the distance of each anchor from the center of the circle
1084        else:
1085            self.create_anchors(len(attr_indices))
1086            XAnchors = numpy.array([val[0] for val in self.anchor_data])
1087            YAnchors = numpy.array([val[1] for val in self.anchor_data])
1088            ZAnchors = numpy.array([val[2] for val in self.anchor_data])
1089            r = numpy.ones(len(XAnchors), numpy.float)
1090
1091        x_positions = numpy.dot(XAnchors, selectedData)
1092        y_positions = numpy.dot(YAnchors, selectedData)
1093        z_positions = numpy.dot(ZAnchors, selectedData)
1094
1095        if normalize == 1 or (normalize == None and self.normalize_examples):
1096            if sum_i == None:
1097                sum_i = self._getSum_i(selectedData, useAnchorData, r)
1098            x_positions /= sum_i
1099            y_positions /= sum_i
1100            z_positions /= sum_i
1101            self.trueScaleFactor = scaleFactor
1102        else:
1103            if not removeMissingData:
1104                try:
1105                    x_validData = numpy.compress(validData, x_positions)
1106                    y_validData = numpy.compress(validData, y_positions)
1107                    z_validData = numpy.compress(validData, z_positions)
1108                except:
1109                    print validData
1110                    print x_positions
1111                    print numpy.shape(validData)
1112                    print numpy.shape(x_positions)
1113            else:
1114                x_validData = x_positions
1115                y_validData = y_positions
1116                z_validData = z_positions
1117
1118            dist = math.sqrt(max(x_validData*x_validData + y_validData*y_validData + z_validData*z_validData)) or 1
1119            self.trueScaleFactor = scaleFactor / dist
1120
1121        self.unscaled_x_positions = numpy.array(x_positions)
1122        self.unscaled_y_positions = numpy.array(y_positions)
1123        self.unscaled_z_positions = numpy.array(z_positions)
1124
1125        if self.trueScaleFactor != 1.0:
1126            x_positions *= self.trueScaleFactor
1127            y_positions *= self.trueScaleFactor
1128            z_positions *= self.trueScaleFactor
1129
1130        if jitterSize > 0.0:
1131            x_positions += numpy.random.uniform(-jitterSize, jitterSize, len(x_positions))
1132            y_positions += numpy.random.uniform(-jitterSize, jitterSize, len(y_positions))
1133            z_positions += numpy.random.uniform(-jitterSize, jitterSize, len(z_positions))
1134
1135        self.last_attr_indices = attr_indices
1136        if classList != None:
1137            return numpy.transpose(numpy.array((x_positions, y_positions, z_positions, classList)))
1138        else:
1139            return numpy.transpose(numpy.array((x_positions, y_positions, z_positions)))
1140
1141    createProjectionAsNumericArray = create_projection_as_numeric_array
1142
1143    @deprecated_keywords({"useAnchorData": "use_anchor_data",
1144                          "anchorRadius": "anchor_radius"})
1145    def _getsum_i(self, data, use_anchor_data=0, anchor_radius=None):
1146        """
1147        Function to compute the sum of all values for each element in the data.
1148        Used to normalize.
1149       
1150        """
1151        if use_anchor_data:
1152            if anchor_radius == None:
1153                anchor_radius = numpy.sqrt([a[0]**2+a[1]**2+a[2]**2 for a in self.anchor_data])
1154            sum_i = numpy.add.reduce(numpy.transpose(numpy.transpose(data)*anchor_radius))
1155        else:
1156            sum_i = numpy.add.reduce(data)
1157        if len(numpy.nonzero(sum_i)) < len(sum_i):    # test if there are zeros in sum_i
1158            sum_i += numpy.where(sum_i == 0, 1.0, 0.0)
1159        return sum_i
1160   
1161    _getSum_i = _getsum_i
1162
1163ScaleLinProjData3D = deprecated_members({"setAnchors": "set_anchors",
1164                                       "createAnchors": "create_anchors",
1165                                       "saveProjectionAsTabData": "save_projection_as_tab_data",
1166                                       "get_projected_point_position": "get_projected_point_position",
1167                                       "create_projection_as_example_table": "create_projection_as_example_table",
1168                                       "create_projection_as_numeric_array": "create_projection_as_numeric_array",
1169                                       "_getSum_i": "_getsum_i",
1170                                       "normalizeExamples": "normalize_examples",
1171                                       "anchorData": "anchor_data",
1172                                       "lastAttrIndices": "last_attr_indices",
1173                                       "anchorDict": "anchor_dict",
1174                                      })(ScaleLinProjData3D)
1175
1176class ScalePolyvizData(ScaleLinProjData):
1177    def __init__(self):
1178        ScaleLinProjData.__init__(self)
1179        self.normalize_examples = 1
1180        self.anchor_data =[]        # form: [(anchor1x, anchor1y, label1),(anchor2x, anchor2y, label2), ...]
1181       
1182
1183    # attributeReverse, validData = None, classList = None, sum_i = None, XAnchors = None, YAnchors = None, domain = None, scaleFactor = 1.0, jitterSize = 0.0
1184    @deprecated_keywords({"attrIndices": "attr_indices",
1185                          "settingsDict": "settings_dict"})
1186    def create_projection_as_example_table(self, attr_list, **settings_dict):
1187        if self.data_domain.class_var:
1188            domain = settings_dict.get("domain") or \
1189                     Orange.data.Domain([Orange.data.variable.Continuous("xVar"),
1190                                         Orange.data.variable.Continuous("yVar"),
1191                                         Orange.data.variable.Discrete(self.data_domain.class_var.name,
1192                                                                       values = get_variable_values_sorted(self.data_domain.class_var))])
1193        else:
1194            domain = settings_dict.get("domain") or \
1195                     Orange.data.Domain([Orange.data.variable.Continuous("xVar"),
1196                                         Orange.data.variable.Continuous("yVar")])
1197        data = self.create_projection_as_numeric_array(attr_list, **settings_dict)
1198        if data != None:
1199            return Orange.data.Table(domain, data)
1200        else:
1201            return Orange.data.Table(domain)
1202   
1203    createProjectionAsExampleTable = create_projection_as_example_table
1204   
1205    @deprecated_keywords({"attrIndices": "attr_indices",
1206                          "settingsDict": "settings_dict"})
1207    def create_projection_as_numeric_array(self, attr_indices, **settings_dict):
1208        # load the elements from the settings dict
1209        attributeReverse = settings_dict.get("reverse", [0]*len(attr_indices))
1210        validData = settings_dict.get("validData")
1211        classList = settings_dict.get("classList")
1212        sum_i     = settings_dict.get("sum_i")
1213        XAnchors  = settings_dict.get("XAnchors")
1214        YAnchors  = settings_dict.get("YAnchors")
1215        scaleFactor = settings_dict.get("scaleFactor", 1.0)
1216        jitterSize  = settings_dict.get("jitterSize", 0.0)
1217        removeMissingData = settings_dict.get("removeMissingData", 1)
1218       
1219        if validData == None:
1220            validData = self.get_valid_list(attr_indices)
1221        if sum(validData) == 0:
1222            return None
1223
1224        if classList == None and self.data_has_class:
1225            classList = self.original_data[self.data_class_index] 
1226
1227        if removeMissingData:
1228            selectedData = numpy.compress(validData,
1229                                          numpy.take(self.no_jittering_scaled_data,
1230                                                     attr_indices, axis = 0),
1231                                                     axis = 1)
1232            if classList != None and len(classList) != numpy.shape(selectedData)[1]:
1233                classList = numpy.compress(validData, classList)
1234        else:
1235            selectedData = numpy.take(self.no_jittering_scaled_data,
1236                                      attr_indices, axis = 0)
1237       
1238        if sum_i == None:
1239            sum_i = self._getSum_i(selectedData)
1240
1241        if XAnchors == None or YAnchors == None:
1242            XAnchors = self.create_xanchors(len(attr_indices))
1243            YAnchors = self.create_yanchors(len(attr_indices))
1244
1245        xanchors = numpy.zeros(numpy.shape(selectedData), numpy.float)
1246        yanchors = numpy.zeros(numpy.shape(selectedData), numpy.float)
1247        length = len(attr_indices)
1248
1249        for i in range(length):
1250            if attributeReverse[i]:
1251                xanchors[i] = selectedData[i] * XAnchors[i] + (1-selectedData[i]) * XAnchors[(i+1)%length]
1252                yanchors[i] = selectedData[i] * YAnchors[i] + (1-selectedData[i]) * YAnchors[(i+1)%length]
1253            else:
1254                xanchors[i] = (1-selectedData[i]) * XAnchors[i] + selectedData[i] * XAnchors[(i+1)%length]
1255                yanchors[i] = (1-selectedData[i]) * YAnchors[i] + selectedData[i] * YAnchors[(i+1)%length]
1256
1257        x_positions = numpy.sum(numpy.multiply(xanchors, selectedData), axis=0)/sum_i
1258        y_positions = numpy.sum(numpy.multiply(yanchors, selectedData), axis=0)/sum_i
1259        #x_positions = numpy.sum(numpy.transpose(xanchors* numpy.transpose(selectedData)), axis=0) / sum_i
1260        #y_positions = numpy.sum(numpy.transpose(yanchors* numpy.transpose(selectedData)), axis=0) / sum_i
1261
1262        if scaleFactor != 1.0:
1263            x_positions = x_positions * scaleFactor
1264            y_positions = y_positions * scaleFactor
1265        if jitterSize > 0.0:
1266            x_positions += (numpy.random.random(len(x_positions))-0.5)*jitterSize
1267            y_positions += (numpy.random.random(len(y_positions))-0.5)*jitterSize
1268
1269        if classList != None:
1270            return numpy.transpose(numpy.array((x_positions, y_positions, classList)))
1271        else:
1272            return numpy.transpose(numpy.array((x_positions, y_positions)))
1273
1274    createProjectionAsNumericArray = create_projection_as_numeric_array
1275
1276    @deprecated_keywords({"attrIndices": "attr_indices",
1277                          "settingsDict": "settings_dict"})
1278    def get_projected_point_position(self, attr_indices, values, **settings_dict):
1279        # load the elements from the settings dict
1280        attributeReverse = settings_dict.get("reverse", [0]*len(attr_indices))
1281        useAnchorData = settings_dict.get("useAnchorData")
1282        XAnchors = settings_dict.get("XAnchors")
1283        YAnchors = settings_dict.get("YAnchors")
1284   
1285        if XAnchors != None and YAnchors != None:
1286            XAnchors = numpy.array(XAnchors)
1287            YAnchors = numpy.array(YAnchors)
1288        elif useAnchorData:
1289            XAnchors = numpy.array([val[0] for val in self.anchor_data])
1290            YAnchors = numpy.array([val[1] for val in self.anchor_data])
1291        else:
1292            XAnchors = self.create_xanchors(len(attr_indices))
1293            YAnchors = self.create_yanchors(len(attr_indices))
1294
1295        m = min(values); M = max(values)
1296        if m < 0.0 or M > 1.0:  # we have to do rescaling of values so that all
1297            # the values will be in the 0-1 interval
1298            values = [max(0.0, min(val, 1.0)) for val in values]
1299            #m = min(m, 0.0); M = max(M, 1.0); diff = max(M-m, 1e-10)
1300            #values = [(val-m) / float(diff) for val in values]
1301       
1302        s = sum(numpy.array(values))
1303        if s == 0: return [0.0, 0.0]
1304
1305        length = len(values)
1306        xanchors = numpy.zeros(length, numpy.float)
1307        yanchors = numpy.zeros(length, numpy.float)
1308        for i in range(length):
1309            if attributeReverse[i]:
1310                xanchors[i] = values[i] * XAnchors[i] + (1-values[i]) * XAnchors[(i+1)%length]
1311                yanchors[i] = values[i] * YAnchors[i] + (1-values[i]) * YAnchors[(i+1)%length]
1312            else:
1313                xanchors[i] = (1-values[i]) * XAnchors[i] + values[i] * XAnchors[(i+1)%length]
1314                yanchors[i] = (1-values[i]) * YAnchors[i] + values[i] * YAnchors[(i+1)%length]
1315
1316        x_positions = numpy.sum(numpy.dot(xanchors, values), axis=0) / float(s)
1317        y_positions = numpy.sum(numpy.dot(yanchors, values), axis=0) / float(s)
1318        return [x, y]
1319   
1320    getProjectedPointPosition = get_projected_point_position
1321
1322ScalePolyvizData = deprecated_members({"create_projection_as_example_table": "create_projection_as_example_table",
1323                                       "create_projection_as_numeric_array": "create_projection_as_numeric_array",
1324                                       "get_projected_point_position": "get_projected_point_position"
1325                                       })(ScalePolyvizData)
1326
1327class ScaleScatterPlotData(ScaleData):
1328    def get_original_data(self, indices):
1329        data = self.original_data.take(indices, axis = 0)
1330        for i, ind in enumerate(indices):
1331            [minVal, maxVal] = self.attr_values[self.data_domain[ind].name]
1332            if self.data_domain[ind].varType == Orange.core.VarTypes.Discrete:
1333                data[i] += (self.jitter_size/50.0)*(numpy.random.random(len(self.raw_data)) - 0.5)
1334            elif self.data_domain[ind].varType == Orange.core.VarTypes.Continuous and self.jitter_continuous:
1335                data[i] += (self.jitter_size/(50.0*(maxVal-minVal or 1)))*(numpy.random.random(len(self.raw_data)) - 0.5)
1336        return data
1337   
1338    getOriginalData = get_original_data
1339   
1340    def get_original_subset_data(self, indices):
1341        data = self.original_subset_data.take(indices, axis = 0)
1342        for i, ind in enumerate(indices):
1343            [minVal, maxVal] = self.attr_values[self.raw_subset_data.domain[ind].name]
1344            if self.data_domain[ind].varType == Orange.core.VarTypes.Discrete:
1345                data[i] += (self.jitter_size/(50.0*max(1, maxVal)))*(numpy.random.random(len(self.raw_subset_data)) - 0.5)
1346            elif self.data_domain[ind].varType == Orange.core.VarTypes.Continuous and self.jitter_continuous:
1347                data[i] += (self.jitter_size/(50.0*(maxVal-minVal or 1)))*(numpy.random.random(len(self.raw_subset_data)) - 0.5)
1348        return data
1349
1350    getOriginalSubsetData = get_original_subset_data
1351
1352    @deprecated_keywords({"xAttr": "xattr", "yAttr": "yattr"})
1353    def get_xy_data_positions(self, xattr, yattr):
1354        """
1355        Create x-y projection of attributes in attrlist.
1356       
1357        """
1358        xattr_index, yattr_index = self.attribute_name_index[xattr], self.attribute_name_index[yattr]
1359
1360        xdata = self.scaled_data[xattr_index].copy()
1361        ydata = self.scaled_data[yattr_index].copy()
1362       
1363        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
1364        else:  xdata = xdata * (self.attr_values[xattr][1] - self.attr_values[xattr][0]) + float(self.attr_values[xattr][0])
1365
1366        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
1367        else:  ydata = ydata * (self.attr_values[yattr][1] - self.attr_values[yattr][0]) + float(self.attr_values[yattr][0])
1368        return (xdata, ydata)
1369   
1370    getXYDataPositions = get_xy_data_positions
1371   
1372    @deprecated_keywords({"xAttr": "xattr", "yAttr": "yattr"})
1373    def get_xy_subset_data_positions(self, xattr, yattr):
1374        """
1375        Create x-y projection of attributes in attr_list.
1376       
1377        """
1378        xattr_index, yattr_index = self.attribute_name_index[xattr], self.attribute_name_index[yattr]
1379
1380        xdata = self.scaled_subset_data[xattr_index].copy()
1381        ydata = self.scaled_subset_data[yattr_index].copy()
1382       
1383        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
1384        else:  xdata = xdata * (self.attr_values[xattr][1] - self.attr_values[xattr][0]) + float(self.attr_values[xattr][0])
1385
1386        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
1387        else:  ydata = ydata * (self.attr_values[yattr][1] - self.attr_values[yattr][0]) + float(self.attr_values[yattr][0])
1388        return (xdata, ydata)
1389   
1390    getXYSubsetDataPositions = get_xy_subset_data_positions
1391   
1392    @deprecated_keywords({"attrIndices": "attr_indices",
1393                          "settingsDict": "settings_dict"})
1394    def get_projected_point_position(self, attr_indices, values, **settings_dict):
1395        """
1396        For attributes in attr_indices and values of these attributes in values
1397        compute point positions this function has more sense in radviz and
1398        polyviz methods. settings_dict has to be because radviz and polyviz have
1399        this parameter.
1400        """
1401        return values
1402
1403    getProjectedPointPosition = get_projected_point_position
1404
1405    @deprecated_keywords({"attrIndices": "attr_indices",
1406                          "settingsDict": "settings_dict"})
1407    def create_projection_as_example_table(self, attr_indices, **settings_dict):
1408        """
1409        Create the projection of attribute indices given in attr_indices and
1410        create an example table with it.
1411       
1412        """
1413        if self.data_has_class:
1414            domain = settings_dict.get("domain") or \
1415                     Orange.data.Domain([Orange.data.variable.Continuous(self.data_domain[attr_indices[0]].name),
1416                                         Orange.data.variable.Continuous(self.data_domain[attr_indices[1]].name),
1417                                         Orange.data.variable.Discrete(self.data_domain.class_var.name,
1418                                                                       values = get_variable_values_sorted(self.data_domain.class_var))])
1419        else:
1420            domain = settings_dict.get("domain") or \
1421                     Orange.data.Domain([Orange.data.variable.Continuous(self.data_domain[attr_indices[0]].name),
1422                                         Orange.data.variable.Continuous(self.data_domain[attr_indices[1]].name)])
1423
1424        data = self.create_projection_as_numeric_array(attr_indices,
1425                                                       **settings_dict)
1426        if data != None:
1427            return Orange.data.Table(domain, data)
1428        else:
1429            return Orange.data.Table(domain)
1430
1431    createProjectionAsExampleTable = create_projection_as_example_table
1432
1433    @deprecated_keywords({"attrIndices": "attr_indices",
1434                          "settingsDict": "settings_dict"})
1435    def create_projection_as_example_table_3D(self, attr_indices, **settings_dict):
1436        """
1437        Create the projection of attribute indices given in attr_indices and
1438        create an example table with it.
1439       
1440        """
1441        if self.data_has_class:
1442            domain = settings_dict.get("domain") or \
1443                     Orange.data.Domain([Orange.data.variable.Continuous(self.data_domain[attr_indices[0]].name),
1444                                         Orange.data.variable.Continuous(self.data_domain[attr_indices[1]].name),
1445                                         Orange.data.variable.Continuous(self.data_domain[attr_indices[2]].name),
1446                                         Orange.data.variable.Discrete(self.data_domain.class_var.name,
1447                                                                       values = get_variable_values_sorted(self.data_domain.class_var))])
1448        else:
1449            domain = settings_dict.get("domain") or \
1450                     Orange.data.Domain([Orange.data.variable.Continuous(self.data_domain[attr_indices[0]].name),
1451                                         Orange.data.variable.Continuous(self.data_domain[attr_indices[1]].name),
1452                                         Orange.data.variable.Continuous(self.data_domain[attr_indices[2]].name)])
1453
1454        data = self.create_projection_as_numeric_array_3D(attr_indices,
1455                                                          **settings_dict)
1456        if data != None:
1457            return Orange.data.Table(domain, data)
1458        else:
1459            return Orange.data.Table(domain)
1460
1461    createProjectionAsExampleTable3D = create_projection_as_example_table_3D
1462
1463    @deprecated_keywords({"attrIndices": "attr_indices",
1464                          "settingsDict": "settings_dict"})
1465    def create_projection_as_numeric_array(self, attr_indices, **settings_dict):
1466        validData = settings_dict.get("validData")
1467        classList = settings_dict.get("classList")
1468        jitterSize = settings_dict.get("jitter_size", 0.0)
1469
1470        if validData == None:
1471            validData = self.get_valid_list(attr_indices)
1472        if sum(validData) == 0:
1473            return None
1474
1475        if classList == None and self.data_has_class:
1476            classList = self.original_data[self.data_class_index]
1477
1478        xArray = self.no_jittering_scaled_data[attr_indices[0]]
1479        yArray = self.no_jittering_scaled_data[attr_indices[1]]
1480        if jitterSize > 0.0:
1481            xArray += (numpy.random.random(len(xArray))-0.5)*jitterSize
1482            yArray += (numpy.random.random(len(yArray))-0.5)*jitterSize
1483        if classList != None:
1484            data = numpy.compress(validData, numpy.array((xArray, yArray, classList)), axis = 1)
1485        else:
1486            data = numpy.compress(validData, numpy.array((xArray, yArray)), axis = 1)
1487        data = numpy.transpose(data)
1488        return data
1489
1490    createProjectionAsNumericArray = create_projection_as_numeric_array
1491
1492    @deprecated_keywords({"attrIndices": "attr_indices",
1493                          "settingsDict": "settings_dict"})
1494    def create_projection_as_numeric_array_3D(self, attr_indices, **settings_dict):
1495        validData = settings_dict.get("validData")
1496        classList = settings_dict.get("classList")
1497        jitterSize = settings_dict.get("jitter_size", 0.0)
1498
1499        if validData == None:
1500            validData = self.get_valid_list(attr_indices)
1501        if sum(validData) == 0:
1502            return None
1503
1504        if classList == None and self.data_has_class:
1505            classList = self.original_data[self.data_class_index]
1506
1507        xArray = self.no_jittering_scaled_data[attr_indices[0]]
1508        yArray = self.no_jittering_scaled_data[attr_indices[1]]
1509        zArray = self.no_jittering_scaled_data[attr_indices[2]]
1510        if jitterSize > 0.0:
1511            xArray += (numpy.random.random(len(xArray))-0.5)*jitterSize
1512            yArray += (numpy.random.random(len(yArray))-0.5)*jitterSize
1513            zArray += (numpy.random.random(len(zArray))-0.5)*jitterSize
1514        if classList != None:
1515            data = numpy.compress(validData, numpy.array((xArray, yArray, zArray, classList)), axis = 1)
1516        else:
1517            data = numpy.compress(validData, numpy.array((xArray, yArray, zArray)), axis = 1)
1518        data = numpy.transpose(data)
1519        return data
1520
1521    createProjectionAsNumericArray3D = create_projection_as_numeric_array_3D
1522
1523    @deprecated_keywords({"attributeNameOrder": "attribute_name_order",
1524                          "addResultFunct": "add_result_funct"})
1525    def get_optimal_clusters(self, attribute_name_order, add_result_funct):
1526        if not self.data_has_class or self.data_has_continuous_class:
1527            return
1528       
1529        jitter_size = 0.001 * self.clusterOptimization.jitterDataBeforeTriangulation
1530        domain = Orange.data.Domain([Orange.data.variable.Continuous("xVar"),
1531                                     Orange.data.variable.Continuous("yVar"),
1532                                    self.data_domain.class_var])
1533
1534        # init again, in case that the attribute ordering took too much time
1535        self.scatterWidget.progressBarInit()
1536        startTime = time.time()
1537        count = len(attribute_name_order)*(len(attribute_name_order)-1)/2
1538        testIndex = 0
1539
1540        for i in range(len(attribute_name_order)):
1541            for j in range(i):
1542                try:
1543                    attr1 = self.attribute_name_index[attribute_name_order[j]]
1544                    attr2 = self.attribute_name_index[attribute_name_order[i]]
1545                    testIndex += 1
1546                    if self.clusterOptimization.isOptimizationCanceled():
1547                        secs = time.time() - startTime
1548                        self.clusterOptimization.setStatusBarText("Evaluation stopped (evaluated %d projections in %d min, %d sec)"
1549                                                                  % (testIndex, secs/60, secs%60))
1550                        self.scatterWidget.progressBarFinished()
1551                        return
1552
1553                    data = self.create_projection_as_example_table([attr1, attr2],
1554                                                                   domain = domain,
1555                                                                   jitter_size = jitter_size)
1556                    graph, valueDict, closureDict, polygonVerticesDict, enlargedClosureDict, otherDict = self.clusterOptimization.evaluateClusters(data)
1557
1558                    allValue = 0.0
1559                    classesDict = {}
1560                    for key in valueDict.keys():
1561                        add_result_funct(valueDict[key], closureDict[key],
1562                                         polygonVerticesDict[key],
1563                                         [attribute_name_order[i],
1564                                          attribute_name_order[j]],
1565                                          int(graph.objects[polygonVerticesDict[key][0]].getclass()),
1566                                          enlargedClosureDict[key], otherDict[key])
1567                        classesDict[key] = int(graph.objects[polygonVerticesDict[key][0]].getclass())
1568                        allValue += valueDict[key]
1569                    add_result_funct(allValue, closureDict, polygonVerticesDict,
1570                                     [attribute_name_order[i], attribute_name_order[j]],
1571                                     classesDict, enlargedClosureDict, otherDict)     # add all the clusters
1572                   
1573                    self.clusterOptimization.setStatusBarText("Evaluated %d projections..."
1574                                                              % (testIndex))
1575                    self.scatterWidget.progressBarSet(100.0*testIndex/float(count))
1576                    del data, graph, valueDict, closureDict, polygonVerticesDict, enlargedClosureDict, otherDict, classesDict
1577                except:
1578                    type, val, traceback = sys.exc_info()
1579                    sys.excepthook(type, val, traceback)  # print the exception
1580       
1581        secs = time.time() - startTime
1582        self.clusterOptimization.setStatusBarText("Finished evaluation (evaluated %d projections in %d min, %d sec)" % (testIndex, secs/60, secs%60))
1583        self.scatterWidget.progressBarFinished()
1584   
1585    getOptimalClusters = get_optimal_clusters
1586
1587ScaleScatterPlotData = deprecated_members({"getOriginalData": "get_original_data",
1588                                           "getOriginalSubsetData": "get_original_subset_data",
1589                                           "getXYDataPositions": "get_xy_data_positions",
1590                                           "getXYSubsetDataPositions": "get_xy_subset_data_positions",
1591                                           "getProjectedPointPosition": "get_projected_point_position",
1592                                           "createProjectionAsExampleTable": "create_projection_as_example_table",
1593                                           "createProjectionAsExampleTable3D": "create_projection_as_example_table_3D",
1594                                           "createProjectionAsNumericArray": "create_projection_as_numeric_array",
1595                                           "createProjectionAsNumericArray3D": "create_projection_as_numeric_array_3D",
1596                                           "getOptimalClusters": "get_optimal_clusters"
1597                                           })(ScaleScatterPlotData)
Note: See TracBrowser for help on using the repository browser.