source: orange/Orange/data/io.py @ 10957:660b3532fb57

Revision 10957:660b3532fb57, 36.4 KB checked in by Ales Erjavec <ales.erjavec@…>, 21 months ago (diff)

Handle csv.Sniffer errors.

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