source: orange/Orange/data/io.py @ 9799:088015b568d9

Revision 9799:088015b568d9, 31.3 KB checked in by Miha Stajdohar <miha.stajdohar@…>, 2 years ago (diff)

Set attribute_load_status.

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