source: orange/Orange/data/io.py @ 10581:94a831b04ec3

Revision 10581:94a831b04ec3, 34.1 KB checked in by markotoplak, 2 years ago (diff)

Moved some other scripts from misc to utils and Orange imports and canvas not works, although not systematically tested.

Line 
1"""\
2*****************
3Data I/O (``io``)
4*****************
5
6Import/Export
7=============
8
9This module contains the functions for importing and exporting Orange
10data tables from/to different file formats. This works by associating
11a filename extension with a set of loading/saving functions using
12:obj:`register_file_type`.
13
14Support for some formats is already implemented: 
15   
16    - Weka `.arff` format
17    - C4.5 `.data/.names` format
18    - LibSVM data format
19    - R `.R` data frame source (export only)
20
21
22.. function:: register_file_type(format_name, load_func, save_func, extension)
23
24    Register the ``save_func``, ``load_func`` pair for the
25    ``format_name``. The format is identified by the ``extension``.
26   
27    :param format_name: the name of the format.
28    :type format_name: str
29   
30    :param load_func: a function used for loading the data (see
31        :ref:`custom-formats` for details)
32    :type load_func: function
33   
34    :param save_func: a function used for saving the data (see
35        :ref:`custom-formats` for details)
36    :type save_func: function
37   
38    :param extension: the file extension associated with this format
39        (e.g. '.myformat'). This can be a list of extension if the
40        format uses multiple extensions (for instance the
41        `.data` and `.names` file pairs in the C4.5 format)
42   
43    Example from the :obj:`~Orange.data.io` module that registers the Weka .arff
44    format ::
45       
46        register_file_type("Weka", load_ARFF, to_ARFF, ".arff")
47       
48``load_func`` or ``save_func`` can be None, indicating that the
49corresponding functionality is not supported.
50 
51Loading and saving from/to custom formats then works the same way as
52the standard Orange `.tab` file but with a different filename
53extension. ::
54
55    >>> import Orange
56    >>> data = Orange.data.Table("iris.arff")
57    >>> data.save("Copy of iris.arff")
58 
59   
60
61.. _custom-formats:
62
63Implementing custom import/export functions.
64--------------------------------------------
65
66The signature for the custom load functions should be
67
68``load_myformat(filename, create_new_on=Orange.feature.Descriptor.MakeStatus.NoRecognizedValues, **kwargs)``
69   
70When constructing variables :obj:`Orange.feature.Descriptor.make` should
71be used with the ``create_new_on`` parameter.
72:obj:`~Orange.feature.Descriptor.make` will return an attribute and the
73status of the variable, telling whether a new attribute was created
74or the old one reused and why (see :mod:`Orange.feature`).
75Additional keyword arguments can be provided in the call to
76:obj:`~Orange.data.Table` constructor. These will be passed in the
77``**kwargs``.
78The function should return the build :obj:`~Orange.data.Table` object.
79For examples see the source code for the ``Orange.data.io`` module
80
81The save function is easier to implement.
82
83``save_myformat(filename, table, **kwargs)``
84
85Similar as above the ``**kwargs`` contains any additional arguments
86:obj:`~Orange.data.Table.save`.
87 
88"""
89import os
90
91import Orange
92import Orange.feature
93import Orange.misc
94from Orange.core import \
95     BasketFeeder, FileExampleGenerator, BasketExampleGenerator, \
96     C45ExampleGenerator, TabDelimExampleGenerator, \
97     registerFileType as register_file_type
98
99import Orange.feature as variable
100from Orange.feature import Descriptor
101MakeStatus = Orange.feature.Descriptor.MakeStatus
102make = Orange.feature.Descriptor.make
103
104def loadARFF(filename, create_on_new=MakeStatus.Incompatible, **kwargs):
105    """Return class:`Orange.data.Table` containing data from file in Weka ARFF format
106       if there exists no .xml file with the same name. If it does, a multi-label
107       dataset is read and returned.
108    """
109    if filename[-5:] == ".arff":
110        filename = filename[:-5]
111    if os.path.exists(filename + ".xml") and os.path.exists(filename + ".arff"):
112        xml_name = filename + ".xml"
113        arff_name = filename + ".arff"
114        return Orange.multilabel.mulan.trans_mulan_data(xml_name, arff_name, create_on_new)
115    else:
116        return loadARFF_Weka(filename, create_on_new)
117
118def loadARFF_Weka(filename, create_on_new=MakeStatus.Incompatible, **kwargs):
119    """Return class:`Orange.data.Table` containing data from file in Weka ARFF format"""
120    if not os.path.exists(filename) and os.path.exists(filename + ".arff"):
121        filename = filename + ".arff"
122    f = open(filename, 'r')
123
124    attributes = []
125    attributeLoadStatus = []
126
127    name = ''
128    state = 0 # header
129    data = []
130    for l in f.readlines():
131        l = l.rstrip("\n") # strip \n
132        l = l.replace('\t', ' ') # get rid of tabs
133        x = l.split('%')[0] # strip comments
134        if len(x.strip()) == 0:
135            continue
136        if state == 0 and x[0] != '@':
137            print "ARFF import ignoring:", x
138        if state == 1:
139            if x[0] == '{':#sparse data format, begin with '{', ends with '}'
140                r = [None] * len(attributes)
141                dd = x[1:-1]
142                dd = dd.split(',')
143                for xs in dd:
144                    y = xs.split(" ")
145                    if len(y) <> 2:
146                        raise ValueError("the format of the data is error")
147                    r[int(y[0])] = y[1]
148                data.append(r)
149            else:#normal data format, split by ','
150                dd = x.split(',')
151                r = []
152                for xs in dd:
153                    y = xs.strip(" ")
154                    if len(y) > 0:
155                        if y[0] == "'" or y[0] == '"':
156                            r.append(xs.strip("'\""))
157                        else:
158                            ns = xs.split()
159                            for ls in ns:
160                                if len(ls) > 0:
161                                    r.append(ls)
162                    else:
163                        r.append('?')
164                data.append(r[:len(attributes)])
165        else:
166            y = []
167            for cy in x.split(' '):
168                if len(cy) > 0:
169                    y.append(cy)
170            if str.lower(y[0][1:]) == 'data':
171                state = 1
172            elif str.lower(y[0][1:]) == 'relation':
173                name = str.strip(y[1])
174            elif str.lower(y[0][1:]) == 'attribute':
175                if y[1][0] == "'":
176                    atn = y[1].strip("' ")
177                    idx = 1
178                    while y[idx][-1] != "'":
179                        idx += 1
180                        atn += ' ' + y[idx]
181                    atn = atn.strip("' ")
182                else:
183                    atn = y[1]
184                z = x.split('{')
185                w = z[-1].split('}')
186                if len(z) > 1 and len(w) > 1:
187                    # there is a list of values
188                    vals = []
189                    for y in w[0].split(','):
190                        sy = y.strip(" '\"")
191                        if len(sy) > 0:
192                            vals.append(sy)
193                    a, s = make(atn, Orange.feature.Type.Discrete, vals, [], create_on_new)
194                else:
195                    # real...
196                    a, s = make(atn, Orange.feature.Type.Continuous, [], [], create_on_new)
197
198                attributes.append(a)
199                attributeLoadStatus.append(s)
200    # generate the domain
201    d = Orange.data.Domain(attributes)
202    lex = []
203    for dd in data:
204        e = Orange.data.Instance(d, dd)
205        lex.append(e)
206    t = Orange.data.Table(d, lex)
207    t.name = name
208
209    #if hasattr(t, "attribute_load_status"):
210    t.setattr("attribute_load_status", attributeLoadStatus)
211    return t
212loadARFF = Orange.utils.deprecated_keywords(
213{"createOnNew": "create_on_new"}
214)(loadARFF)
215
216
217def toARFF(filename, table, try_numericize=0):
218    """Save class:`Orange.data.Table` to file in Weka's ARFF format"""
219    t = table
220    if filename[-5:] == ".arff":
221        filename = filename[:-5]
222    #print filename
223    f = open(filename + '.arff', 'w')
224    f.write('@relation %s\n' % t.domain.classVar.name)
225    # attributes
226    ats = [i for i in t.domain.attributes]
227    ats.append(t.domain.classVar)
228    for i in ats:
229        real = 1
230        if i.varType == 1:
231            if try_numericize:
232                # try if all values numeric
233                for j in i.values:
234                    try:
235                        x = float(j)
236                    except:
237                        real = 0 # failed
238                        break
239            else:
240                real = 0
241        iname = str(i.name)
242        if iname.find(" ") != -1:
243            iname = "'%s'" % iname
244        if real == 1:
245            f.write('@attribute %s real\n' % iname)
246        else:
247            f.write('@attribute %s { ' % iname)
248            x = []
249            for j in i.values:
250                s = str(j)
251                if s.find(" ") == -1:
252                    x.append("%s" % s)
253                else:
254                    x.append("'%s'" % s)
255            for j in x[:-1]:
256                f.write('%s,' % j)
257            f.write('%s }\n' % x[-1])
258
259    # examples
260    f.write('@data\n')
261    for j in t:
262        x = []
263        for i in range(len(ats)):
264            s = str(j[i])
265            if s.find(" ") == -1:
266                x.append("%s" % s)
267            else:
268                x.append("'%s'" % s)
269        for i in x[:-1]:
270            f.write('%s,' % i)
271        f.write('%s\n' % x[-1])
272
273def loadMULAN(filename, create_on_new=MakeStatus.Incompatible, **kwargs):
274    """Return class:`Orange.data.Table` containing data from file in Mulan ARFF and XML format"""
275    if filename[-4:] == ".xml":
276        filename = filename[:-4]
277    if os.path.exists(filename + ".xml") and os.path.exists(filename + ".arff"):
278        xml_name = filename + ".xml"
279        arff_name = filename + ".arff"
280        return Orange.multilabel.mulan.trans_mulan_data(xml_name, arff_name)
281    else:
282        return None
283loadARFF = Orange.utils.deprecated_keywords(
284{"createOnNew": "create_on_new"}
285)(loadARFF)
286
287def toC50(filename, table, try_numericize=0):
288    """Save class:`Orange.data.Table` to file in C50 format"""
289    t = table
290    # export names
291#    basename = os.path.basename(filename)
292    filename_prefix, ext = os.path.splitext(filename)
293    f = open('%s.names' % filename_prefix, 'w')
294    f.write('%s.\n\n' % t.domain.class_var.name)
295    # attributes
296    ats = [i for i in t.domain.attributes]
297    ats.append(t.domain.classVar)
298    for i in ats:
299        real = 1
300        # try if real
301        if i.varType == Orange.core.VarTypes.Discrete:
302            if try_numericize:
303                # try if all values numeric
304                for j in i.values:
305                    try:
306                        x = float(j)
307                    except Exception:
308                        real = 0 # failed
309                        break
310            else:
311                real = 0
312        if real == 1:
313            f.write('%s: continuous.\n' % i.name)
314        else:
315            f.write('%s: ' % i.name)
316            x = []
317            for j in i.values:
318                x.append('%s' % j)
319            for j in x[:-1]:
320                f.write('%s,' % j)
321            f.write('%s.\n' % x[-1])
322    # examples
323    f.close()
324
325    f = open('%s.data' % filename_prefix, 'w')
326    for j in t:
327        x = []
328        for i in range(len(ats)):
329            x.append('%s' % j[i])
330        for i in x[:-1]:
331            f.write('%s,' % i)
332        f.write('%s\n' % x[-1])
333
334def toR(filename, t):
335    """Save class:`Orange.data.Table` to file in R format"""
336    if str.upper(filename[-2:]) == ".R":
337        filename = filename[:-2]
338    f = open(filename + '.R', 'w')
339
340    atyp = []
341    aord = []
342    labels = []
343    as0 = []
344    for a in t.domain.variables:
345        as0.append(a)
346#    as0.append(t.domain.class_var)
347    for a in as0:
348        labels.append(str(a.name))
349        atyp.append(a.var_type)
350        aord.append(a.ordered)
351
352    f.write('data <- data.frame(\n')
353    for i in xrange(len(labels)):
354        if atyp[i] == 2: # continuous
355            f.write('"%s" = c(' % (labels[i]))
356            for j in xrange(len(t)):
357                if t[j][i].isSpecial():
358                    f.write('NA')
359                else:
360                    f.write(str(t[j][i]))
361                if (j == len(t) - 1):
362                    f.write(')')
363                else:
364                    f.write(',')
365        elif atyp[i] == 1: # discrete
366            if aord[i]: # ordered
367                f.write('"%s" = ordered(' % labels[i])
368            else:
369                f.write('"%s" = factor(' % labels[i])
370            f.write('levels=c(')
371            for j in xrange(len(as0[i].values)):
372                f.write('"x%s"' % (as0[i].values[j]))
373                if j == len(as0[i].values) - 1:
374                    f.write('),c(')
375                else:
376                    f.write(',')
377            for j in xrange(len(t)):
378                if t[j][i].isSpecial():
379                    f.write('NA')
380                else:
381                    f.write('"x%s"' % str(t[j][i]))
382                if (j == len(t) - 1):
383                    f.write('))')
384                else:
385                    f.write(',')
386        else:
387            raise "Unknown attribute type."
388        if (i < len(labels) - 1):
389            f.write(',\n')
390    f.write(')\n')
391
392
393def toLibSVM(filename, example):
394    """Save class:`Orange.data.Table` to file in LibSVM format"""
395    import Orange.classification.svm
396    Orange.classification.svm.tableToSVMFormat(example, open(filename, "wb"))
397
398
399@Orange.utils.deprecated_keywords({"createOnNew": "create_on_new"})
400def loadLibSVM(filename, create_on_new=MakeStatus.Incompatible, **kwargs):
401    """Return class:`Orange.data.Table` containing data from file in LibSVM format"""
402    attributeLoadStatus = {}
403    def make_float(name):
404        attr, s = Orange.feature.Descriptor.make(name, Orange.feature.Type.Continuous, [], [], create_on_new)
405        attributeLoadStatus[attr] = s
406        return attr
407
408    def make_disc(name, unordered):
409        attr, s = Orange.feature.Descriptor.make(name, Orange.feature.Type.Discrete, [], unordered, create_on_new)
410        attributeLoadStatus[attr] = s
411        return attr
412
413    data = [line.split() for line in open(filename, "rb").read().splitlines() if line.strip()]
414    vars = type("attr", (dict,), {"__missing__": lambda self, key: self.setdefault(key, make_float(key))})()
415    item = lambda i, v: (vars[i], vars[i](v))
416    values = [dict([item(*val.split(":"))  for val in ex[1:]]) for ex in data]
417    classes = [ex[0] for ex in data]
418    disc = all(["." not in c for c in classes])
419    attributes = sorted(vars.values(), key=lambda var: int(var.name))
420    classVar = make_disc("class", sorted(set(classes))) if disc else make_float("target")
421    attributeLoadStatus = [attributeLoadStatus[attr] for attr in attributes] + \
422                          [attributeLoadStatus[classVar]]
423    domain = Orange.data.Domain(attributes, classVar)
424    table = Orange.data.Table([Orange.data.Instance(domain, [ex.get(attr, attr("?")) for attr in attributes] + [c]) for ex, c in zip(values, classes)])
425    table.setattr("attribute_load_status", attributeLoadStatus)
426    return table
427
428
429"""\
430A general CSV file reader.
431--------------------------
432
433Currently not yet documented and not registered (needs testing).
434
435"""
436
437def split_escaped_str(str, split_str=" ", escape="\\"):
438    res = []
439    index = 0
440    start = 0
441    find_start = 0
442    while index != -1:
443        index = str.find(split_str, find_start)
444        if index != -1 and index > 0:
445            if str[index - 1] == escape: # Skip the escaped split_str
446                find_start = index + 1
447            else:
448                res.append(str[start:index])
449                start = find_start = index + 1
450
451        elif index == -1:
452            res.append(str[start:])
453    return [r.replace(escape + split_str, split_str) for r in res]
454
455def is_standard_var_def(cell):
456    """Is the cell a standard variable definition (empty, cont, disc, string)
457    """
458    try:
459        var_type(cell)
460        return True
461    except ValueError, ex:
462        return False
463
464def is_var_types_row(row):
465    """ Is the row a variable type definition row (as in the orange .tab file)
466    """
467    return all(map(is_standard_var_def, row))
468
469def var_type(cell):
470    """ Return variable type from a variable type definition in cell.
471    """
472    if cell in ["c", "continuous"]:
473        return variable.Continuous
474    elif cell in ["d", "discrete"]:
475        return variable.Discrete
476    elif cell in ["s", "string"]:
477        return variable.String
478    elif cell.startswith("pyhton"):
479        return variable.Python
480    elif cell == "":
481        return variable.Descriptor
482    elif len(split_escaped_str(cell, " ")) > 1:
483        return variable.Discrete, split_escaped_str(cell, " ")
484    else:
485        raise ValueError("Unknown variable type definition %r." % cell)
486
487def var_types(row):
488    """ Return variable types from row.
489    """
490    return map(var_type, row)
491
492def is_var_attributes_row(row):
493    """ Is the row an attribute definition row (i.e. the third row in the
494    standard orange .tab file format).
495   
496    """
497    return all(map(is_var_attributes_def, row))
498
499def is_var_attributes_def(cell):
500    """ Is the cell a standard variable attributes definition.
501    """
502    try:
503        var_attribute(cell)
504        return True
505    except ValueError, ex:
506        raise
507        return False
508
509def _var_attribute_label_parse(cell):
510    """
511    """
512    key_value = split_escaped_str(cell, "=")
513    if len(key_value) == 2:
514        return tuple(key_value)
515    else:
516        raise ValueError("Invalid attribute label definition %r." % cell)
517
518def var_attribute(cell):
519    """ Return variable specifier ("meta" or "class" or None) and attributes
520    labels dict.
521    """
522    items = split_escaped_str(cell, " ")
523    if cell == "":
524        return None, {}
525    elif items:
526        specifier = None
527        if items[0] in ["m", "meta"]:
528            specifier = "meta"
529            items = items[1:]
530        elif items[0] == "class":
531            specifier = "class"
532            items = items[1:]
533        elif items[0] == "multiclass":
534            specifier = "multiclass"
535            items = items[1:]
536        elif items[0] in ["i", "ignore"]:
537            specifier = "ignore"
538            items = items[1:]
539        return specifier, dict(map(_var_attribute_label_parse, items))
540    else:
541        raise ValueError("Unknown attribute label definition")
542
543def var_attributes(row):
544    """ Return variable specifiers and label definitions for row.
545    """
546    return map(var_attribute, row)
547
548
549class _var_placeholder(object):
550    """ A place holder for an arbitrary variable while it's values are still unknown.
551    """
552    def __init__(self, name="", values=[]):
553        self.name = name
554        self.values = set(values)
555
556class _disc_placeholder(_var_placeholder):
557    """ A place holder for discrete variables while their values are not yet known.
558    """
559    pass
560
561def is_val_cont(cell):
562    """ Is cell a string representing a real value.
563    """
564    try:
565        float(cell)
566        return True
567    except ValueError:
568        return False
569
570def is_variable_cont(values, n=None, cutoff=0.5):
571    """ Is variable with ``values`` in column (``n`` rows) a continuous variable.
572    """
573    cont = sum(map(is_val_cont, values)) or 1e-30
574    if n is None:
575        n = len(values) or 1
576    return (float(cont) / n) >= cutoff
577
578
579def is_variable_discrete(values, n=None, cutoff=0.3):
580    """ Is variable with ``values`` in column (``n`` rows) a discrete variable.
581    """
582    return not is_variable_cont(values, n, cutoff=1.0 - cutoff)
583
584def is_variable_string(values, n=None, cutuff=0.75):
585    """ Is variable with ``values`` in column (``n`` rows) a string variable.
586    """
587    if n is None:
588        n = len(values)
589    return float(len(set(values))) / (n or 1.0) > cutoff
590
591def load_csv(file, create_new_on=MakeStatus.Incompatible, 
592             delimiter=None, quotechar=None, escapechar=None,
593             skipinitialspace=None, has_header=None, 
594             has_types=None, has_annotations=None, DK=None, **kwargs):
595    """ Load an Orange.data.Table from s csv file.
596    """
597    import csv, numpy
598    file = as_open_file(file, "rb")
599    snifer = csv.Sniffer()
600    sample = file.read(5 * 2 ** 20) # max 5MB sample TODO: What if this is not enough. Try with a bigger sample
601    dialect = snifer.sniff(sample)
602   
603    if has_header is None:
604        has_header = snifer.has_header(sample)
605   
606    file.seek(0) # Rewind
607   
608    def kwparams(**kwargs):
609        """Return not None kwargs.
610        """
611        return dict([(k, v) for k, v in kwargs.items() if v is not None])
612   
613    fmtparam = kwparams(delimiter=delimiter,
614                        quotechar=quotechar,
615                        escapechar=escapechar,
616                        skipinitialspace=skipinitialspace)
617   
618    reader = csv.reader(file, dialect=dialect,
619                        **fmtparam)
620
621    header = types = var_attrs = None
622
623    row = first_row = reader.next()
624   
625    if has_header:
626        header = row
627        # Eat this row and move to the next
628        row = reader.next()
629
630    # Guess types row
631    if has_types is None:
632        has_types = has_header and is_var_types_row(row)
633       
634    if has_types:
635        types = var_types(row)
636        # Eat this row and move to the next
637        row = reader.next()
638
639    # Guess variable annotations row
640    if has_annotations is None:
641        has_annotations = has_header and has_types and \
642                          is_var_attributes_row(row)
643       
644    if has_annotations:
645        labels_row = row
646        var_attrs = var_attributes(row)
647        # Eat this row and move to the next
648        row = reader.next()
649
650    if not header:
651        # Create a default header
652        header = ["F_%i" % i for i in range(len(first_row))]
653       
654    if not types:
655        # Create blank variable types
656        types = [None] * len(header)
657       
658    if not var_attrs:
659        # Create blank variable attributes
660        var_attrs = [None] * len(header)
661
662    # start from the beginning
663    file.seek(0)
664    reader = csv.reader(file, dialect=dialect, **fmtparam)
665   
666    for defined in [has_header, has_types, has_annotations]:
667        if defined: 
668            # skip definition rows if present in the file
669            reader.next()
670   
671    variables = []
672    undefined_vars = []
673    # Missing value flags
674    missing_flags = DK.split(",") if DK is not None else ["?", "", "NA", "~", "*"]
675    missing_map = dict.fromkeys(missing_flags, "?")
676    missing_translate = lambda val: missing_map.get(val, val)
677   
678    # Create domain variables or corresponding place holders
679    for i, (name, var_t) in enumerate(zip(header, types)):
680        if var_t == variable.Discrete:
681            # We do not have values yet
682            variables.append(_disc_placeholder(name))
683            undefined_vars.append((i, variables[-1]))
684        elif var_t == variable.Continuous:
685            variables.append(make(name, Orange.feature.Type.Continuous, [], [], create_new_on))
686        elif var_t == variable.String:
687            variables.append(make(name, Orange.feature.Type.String, [], [], create_new_on))
688        elif var_t == variable.Python:
689            variables.append(variable.Python(name))
690        elif isinstance(var_t, tuple):
691            var_t, values = var_t
692            if var_t == variable.Discrete:
693                # We have values for discrete variable
694                variables.append(make(name, Orange.feature.Type.Discrete, values, [], create_new_on))
695            elif var_t == variable.Python:
696                # Python variables are not supported yet
697                raise NotImplementedError()
698        elif var_t is None:
699            # Unknown variable type, to be deduced at the end
700            variables.append(_var_placeholder(name))
701            undefined_vars.append((i, variables[-1]))
702
703    data = []
704    # Read all the rows
705    for row in reader:
706        # check for final newline.
707        if row:
708            row = map(missing_translate, row)
709            data.append(row)
710            # For undefined variables collect all their values
711            for ind, var_def in undefined_vars:
712                var_def.values.add(row[ind])
713   
714    # Process undefined variables now that we can deduce their type
715    for ind, var_def in undefined_vars:
716        values = var_def.values - set(["?", ""]) # TODO: Other unknown strings?
717        values = sorted(values)
718        if isinstance(var_def, _disc_placeholder):
719            variables[ind] = make(var_def.name, Orange.feature.Type.Discrete, [], values, create_new_on)
720        elif isinstance(var_def, _var_placeholder):
721            if is_variable_cont(values, cutoff=1.0):
722                variables[ind] = make(var_def.name, Orange.feature.Type.Continuous, [], [], create_new_on)
723            elif is_variable_discrete(values, cutoff=0.0):
724                variables[ind] = make(var_def.name, Orange.feature.Type.Discrete, [], values, create_new_on)
725            elif is_variable_string(values):
726                variables[ind] = make(var_def.name, Orange.feature.Type.String, [], [], create_new_on)
727            else:
728                # Treat it as a string anyway
729                variables[ind] = make(var_def.name, Orange.feature.Type.String, [], [], create_new_on)
730
731    attribute_load_status = []
732    meta_attribute_load_status = {}
733    class_var_load_status = []
734    multiclass_var_load_status = []
735
736    attributes = []
737    class_var = []
738    class_vars = []
739    metas = {}
740    attribute_indices = []
741    variable_indices = []
742    class_indices = []
743    multiclass_indices = []
744    meta_indices = []
745    ignore_indices = []
746    for i, ((var, status), var_attr) in enumerate(zip(variables, var_attrs)):
747        if var_attr:
748            flag, attrs = var_attr
749            if flag == "class":
750                class_var.append(var)
751                class_var_load_status.append(status)
752                class_indices.append(i)
753            elif flag == "multiclass":
754                class_vars.append(var)
755                multiclass_var_load_status.append(status)
756                multiclass_indices.append(i)
757            elif flag == "meta":
758                mid = Orange.feature.Descriptor.new_meta_id()
759                metas[mid] = var
760                meta_attribute_load_status[mid] = status
761                meta_indices.append((i, var))
762            elif flag == "ignore":
763                ignore_indices.append(i)
764            else:
765                attributes.append(var)
766                attribute_load_status.append(status)
767                attribute_indices.append(i)
768            var.attributes.update(attrs)
769        else:
770            attributes.append(var)
771            attribute_load_status.append(status)
772            attribute_indices.append(i)
773
774    if len(class_var) > 1:
775        raise ValueError("Multiple class variables defined")
776    if class_var and class_vars:
777        raise ValueError("Both 'class' and 'multiclass' used.")
778
779    class_var = class_var[0] if class_var else None
780   
781    attribute_load_status += class_var_load_status
782    variable_indices = attribute_indices + class_indices
783    domain = Orange.data.Domain(attributes, class_var, class_vars=class_vars)
784    domain.add_metas(metas)
785    normal = [[row[i] for i in variable_indices] for row in data]
786    meta_part = [[row[i] for i, _ in meta_indices] for row in data]
787    multiclass_part = [[row[i] for i in multiclass_indices] for row in data]
788    table = Orange.data.Table(domain, normal)
789    for ex, m_part, mc_part in zip(table, meta_part, multiclass_part):
790        for (column, var), val in zip(meta_indices, m_part):
791            ex[var] = var(val)
792        if mc_part:
793            ex.set_classes(mc_part)
794
795    table.setattr("metaAttributeLoadStatus", meta_attribute_load_status)
796    table.setattr("attributeLoadStatus", attribute_load_status)
797
798    return table
799
800def as_open_file(file, mode="rb"):
801    if isinstance(file, basestring):
802        file = open(file, mode)
803    else: # assuming it is file like with proper mode, could check for write, read
804        pass
805    return file
806
807def save_csv(file, table, orange_specific=True, **kwargs):
808    import csv
809    file = as_open_file(file, "wb")
810    writer = csv.writer(file, **kwargs)
811    attrs = table.domain.attributes
812    class_var = table.domain.class_var
813    metas = [v for _, v in sorted(table.domain.get_metas().items(),
814                                  reverse=True)]
815    all_vars = attrs + ([class_var] if class_var else []) + metas
816    names = [v.name for v in all_vars]
817    writer.writerow(names)
818
819    if orange_specific:
820        type_cells = []
821        for v in all_vars:
822            if isinstance(v, variable.Discrete):
823                escaped_values = [val.replace(" ", r"\ ") for val in v.values]
824                type_cells.append(" ".join(escaped_values))
825            elif isinstance(v, variable.Continuous):
826                type_cells.append("continuous")
827            elif isinstance(v, variable.String):
828                type_cells.append("string")
829            elif isinstance(v, variable.Python):
830                type_cells.append("python")
831            else:
832                raise TypeError("Unknown variable type")
833        writer.writerow(type_cells)
834
835        var_attr_cells = []
836        for spec, var in [("", v) for v in attrs] + \
837                         ([("class", class_var)] if class_var else []) + \
838                         [("m", v) for v in metas]:
839
840            labels = ["{0}={1}".format(*t) for t in var.attributes.items()] # TODO escape spaces
841            var_attr_cells.append(" ".join([spec] if spec else [] + labels))
842
843        writer.writerow(var_attr_cells)
844
845    for instance in table:
846        instance = list(instance) + [instance[m] for m in metas]
847        writer.writerow(instance)
848
849
850register_file_type("R", None, toR, ".R")
851register_file_type("Weka", loadARFF, toARFF, ".arff")
852register_file_type("Mulan", loadMULAN, None, ".xml")
853#registerFileType("C50", None, toC50, [".names", ".data", ".test"])
854register_file_type("libSVM", loadLibSVM, toLibSVM, ".svm")
855
856registerFileType = Orange.utils.deprecated_function_name(register_file_type)
857
858#__doc__ +=  \
859"""\
860Search Paths
861============
862
863Associate a prefix with a search path for easier data loading.
864The paths can be stored in a user specific file.
865
866Example ::
867
868    >>> import Orange, os
869    >>> from Orange.data import io
870    >>> io.set_search_path("my_datasets",
871    ...                     os.path.expanduser("~/Documents/My Datasets"))
872    ...                     persistent=True)
873    ...
874   
875    >>> data = Orange.data.Table("mydatasets:dataset1.tab")
876
877
878.. autofunction:: set_search_path
879
880.. autofunction:: search_paths
881
882.. autofunction:: persistent_search_paths
883
884.. autofunction:: find_file
885
886.. autofunction:: expand_filename
887
888"""
889# Non-persistent registered paths
890_session_paths = []
891
892import ConfigParser
893from ConfigParser import SafeConfigParser
894
895@Orange.utils.lru_cache(maxsize=1)
896def persistent_search_paths():
897    """ Return a list of persistent registered (prefix, path) pairs
898    """
899
900    global_settings_dir = Orange.utils.environ.install_dir
901    user_settings_dir = Orange.utils.environ.orange_settings_dir
902    parser = SafeConfigParser()
903    parser.read([os.path.join(global_settings_dir, "orange-search-paths.cfg"),
904                 os.path.join(user_settings_dir, "orange-search-paths.cfg")])
905    try:
906        items = parser.items("paths")
907        defaults = parser.defaults().items()
908        items = [i for i in items if i not in defaults]
909    except ConfigParser.NoSectionError:
910        items = []
911    return items
912
913def save_persistent_search_path(prefix, path):
914    """ Save the prefix, path pair. If path is None delete the
915    registered prefix.
916   
917    """
918    if isinstance(path, list):
919        path = os.path.pathsep.join(path)
920
921    user_settings_dir = Orange.utils.environ.orange_settings_dir
922    if not os.path.exists(user_settings_dir):
923        try:
924            os.makedirs(user_settings_dir)
925        except OSError:
926            pass
927
928    filename = os.path.join(user_settings_dir, "orange-search-paths.cfg")
929    parser = SafeConfigParser()
930    parser.read([filename])
931
932    if not parser.has_section("paths"):
933        parser.add_section("paths")
934
935    if path is not None:
936        parser.set("paths", prefix, path)
937    elif parser.has_option("paths", prefix):
938        # Remove the registered prefix
939        parser.remove_option("paths", prefix)
940    parser.write(open(filename, "wb"))
941
942def search_paths(prefix=None):
943    """ Return a list of the registered (prefix, path) pairs.
944    """
945    persistent_paths = persistent_search_paths()
946    paths = _session_paths + persistent_paths
947    if prefix is not None:
948        for pref, path in paths:
949            if pref == prefix:
950                return path
951        return ""
952    else:
953        return paths
954
955def set_search_path(prefix, path, persistent=False):
956    """ Associate a search path with a prefix.
957   
958    :param prefix: a prefix
959    :type prefix: str
960   
961    :param path: search path (can also be a list of path strings)
962    :type paths: str
963   
964    :param persistent: if True then the prefix-paths pair will be
965        saved between sessions (default False).
966    :type persistent: bool
967   
968    """
969    global _session_paths
970
971    if isinstance(path, list):
972        path = os.path.pathsep.join(path)
973
974    if persistent:
975        save_persistent_search_path(prefix, path)
976        # Invalidate the persistent_search_paths cache.
977        persistent_search_paths.clear()
978    else:
979        _session_paths.append((prefix, path))
980
981
982def expand_filename(prefixed_name):
983    """ Expand the prefixed filename with the full path.
984    ::
985       
986        >>> from Orange.data import io
987        >>> io.set_search_paths("docs", "/Users/aleserjavec/Documents")
988        >>> io.expand_filename("docs:my_tab_file.tab")
989        '/Users/aleserjavec/Documents/my_tab_file.tab'
990       
991       
992    """
993    prefix, filename = prefixed_name.split(":", 1) #TODO: windows drive letters.
994    paths = search_paths(prefix)
995    if paths:
996        path = paths.split(os.path.pathsep, 1)[0]
997        return os.path.join(path, filename)
998    else:
999        raise ValueError("Unknown prefix %r." % prefix)
1000
1001def find_file(prefixed_name):
1002    """ Find the prefixed filename and return its full path.
1003    """
1004    if not os.path.exists(prefixed_name):
1005        if ":" not in prefixed_name:
1006            raise ValueError("Not a prefixed name.")
1007        prefix, filename = prefixed_name.split(":", 1)
1008        paths = search_paths(prefix)
1009        if paths:
1010            for path in paths.split(os.path.pathsep):
1011                if os.path.exists(os.path.join(path, filename)):
1012                    return os.path.join(path, filename)
1013            raise ValueError("No file %r on prefixed search path %r." % \
1014                             (filename, paths))
1015        else:
1016            raise ValueError("Unknown prefix %r." % prefix)
1017    else:
1018        return prefixed_name
1019
Note: See TracBrowser for help on using the repository browser.