Python读写arff文件开源工具(liac-arff)的增强

jopen 10年前

     最近项目中需要用Python读写arff文件, 因此熟悉了下python读写arff的开源工具——liac-arff,并根据自己的需求增强了下它的功能。

      Liac-arff是巴西Rio Grande do Sul联邦大学的Connectionist Artificial Intelligence实验室开发的。项目地址。在Github上,该项目的星星多达14个(囧)。看来,用Python处理arff文件的人蛮少的。不管人多还是人少,自己的项目有需求,还是学习下这个工具。

      使用liac-arff, 我们可以用如下代码读入arff文件。

>>> import arff
>>> data = arff.load(open('wheater.arff', 'rb'))
>>> data
{
    u'attributes': [
        (u'outlook', [u'sunny', u'overcast', u'rainy']),
        (u'temperature', u'REAL'),
        (u'humidity', u'REAL'),
        (u'windy', [u'TRUE', u'FALSE']),
        (u'play', [u'yes', u'no'])],
    u'data': [
        [u'sunny', 85.0, 85.0, u'FALSE', u'no'],
        [u'sunny', 80.0, 90.0, u'TRUE', u'no'],
        [u'overcast', 83.0, 86.0, u'FALSE', u'yes'],
        [u'rainy', 70.0, 96.0, u'FALSE', u'yes'],
        [u'rainy', 68.0, 80.0, u'FALSE', u'yes'],
        [u'rainy', 65.0, 70.0, u'TRUE', u'no'],
    u'description': u'',
    u'relation': u'weather'
}

      我们也可以用如下代码,将obj转成可以写入arff文件的字符串。

>>> print arff.dumps(data)
@RELATION weather

@ATTRIBUTE outlook {sunny, overcast, rainy}
@ATTRIBUTE temperature REAL
@ATTRIBUTE humidity REAL
@ATTRIBUTE windy {TRUE, FALSE}
@ATTRIBUTE play {yes, no}

@DATA
sunny,85.0,85.0,FALSE,no
sunny,80.0,90.0,TRUE,no
overcast,83.0,86.0,FALSE,yes
rainy,70.0,96.0,FALSE,yes
rainy,68.0,80.0,FALSE,yes
rainy,65.0,70.0,TRUE,no
%
%
%

      除了load和dumps, liac-arff还提供了两个接口loads和dump。下表总结了四个接口的用法。

liac-arff接口函数 用法
load(fp) 从fp读入arff数据,并解析成python表示arff数据的对象obj.
load(str) 解析str得到obj
dump(obj, fp) 将obj存入fp
dumps(obj) 将obj编码成字符串返回

      干货结束,开始夹带私货(狐狸尾巴露出来了,囧)。自己的项目需要能够一次读入一部分arff数据,而liac-arff并没有提供这个功能。因此我fork了这个项目,并修改了一些代码。 新项目的地址。 代码的改动主要有两项,1)增加ArffDecoder.iter_decode方法, 2)修改了ArffEncoder.iter_encode方法。这些代码改动没有影响原来接口。也就是说,load、loads、dump和dumps的用法没有变化。

      第一个改动是新加ArffDecoder.iter_decode(self, file, encode_nominal = False, obj = None, batch = 20). 其中file是要读入的arff文件,encode_nominal我也不知道是什么鬼(这个参数是我从arffDecoder.decode参数表中抄 过来的), obj是python内部表示arff数据的对象,batch表示一次性读入多少实例。

      当我们第一次调用iter_decode方法时,obj需要置成None, 此时iter_decode方法会读取arff文件,并解析arff信息(包括relations, attributes)和batch个实例。当不是第一次调用时,我们可以用之前调用返回结果作为obj, 此时iter_decode方法只解析batch个实例。当然不同调用之间,file必须统一。以下是一个示例,假设test.arff文件中的内容:

@RELATION weather

@ATTRIBUTE outlook {sunny, overcast, rainy}
@ATTRIBUTE temperature REAL
@ATTRIBUTE humidity REAL
@ATTRIBUTE windy {TRUE, FALSE}
@ATTRIBUTE play {yes, no}

@DATA
sunny,85.0,85.0,FALSE,no
sunny,80.0,90.0,TRUE,no
overcast,83.0,86.0,FALSE,yes

      我们运行如下代码:

>>> f = open("test.arff");
>>> decoder = ArffDecoder();
>>> obj1 = decoder.iter_decode(f, obj = None, batch = 2);
>>> obj2 = decoder.iter_decode(f, obj = obj1, batch = 2);

      此时的结果是,

>>> obj1
{
    u'attributes': [
        (u'outlook', [u'sunny', u'overcast', u'rainy']),
        (u'temperature', u'REAL'),
        (u'humidity', u'REAL'),
        (u'windy', [u'TRUE', u'FALSE']),
        (u'play', [u'yes', u'no'])],
    u'data': [
        [u'sunny', 85.0, 85.0, u'FALSE', u'no'],
        [u'sunny', 80.0, 90.0, u'TRUE', u'no']],
    u'description': u'',
    u'relation': u'weather'
}
>>> obj2
{
    u'attributes': [
        (u'outlook', [u'sunny', u'overcast', u'rainy']),
        (u'temperature', u'REAL'),
        (u'humidity', u'REAL'),
        (u'windy', [u'TRUE', u'FALSE']),
        (u'play', [u'yes', u'no'])],
    u'data': [
        [u'overcast', 83.0, 86.0, u'FALSE', u'yes']],
    u'description': u'',
    u'relation': u'weather'
}

      明显地,obj1包含前面两个实例,obj2包含最后一个实例。

      第二个改动是修改了ArffEncoder.iter_encode(self, obj, is_first_call = True), 其中obj是python内部表示arff数据的对象,is_first_call是我加上的参数,指示是否为第一次调用。当 is_first_call=True,即第一次调用, iter_encode方法和原来的iter_encode一样,将obj里的所有信息(relation,attributes等)和数据,都编码成 arff文件内容; 当is_first_call = False,即后续调用中,iter_encode只将obj中的数据进行编码。通过使用修改后的iter_encode,你在只有一部分数据时,就可以 将数据写入文件,而不必等所有数据到位。当所有数据很多,难以全部放入内存时,这种做法将很有用。以下是一个示例,假设

>>> obj
{
    u'attributes': [
        (u'outlook', [u'sunny', u'overcast', u'rainy']),
        (u'temperature', u'REAL'),
        (u'humidity', u'REAL'),
        (u'windy', [u'TRUE', u'FALSE']),
        (u'play', [u'yes', u'no'])],
    u'data': [
        [u'sunny', 85.0, 85.0, u'FALSE', u'no'],
        [u'sunny', 80.0, 90.0, u'TRUE', u'no']],
    u'description': u'',
    u'relation': u'weather'
}

      如果is_first_call = True, 则iter_encode将编码所有信息和数据。

>>> encoder = ArffEncoder();
>>> result = encoder.iter_encode(obj, is_first_call = True);
>>> for i in result:
 . . .     print i+u'\n',;
 . . .
@RELATION weather

@ATTRIBUTE outlook {sunny, overcast, rainy}
@ATTRIBUTE temperature REAL
@ATTRIBUTE humidity REAL
@ATTRIBUTE windy {TRUE, FALSE}
@ATTRIBUTE play {yes, no}

@DATA
sunny,85.0,85.0,FALSE,no
sunny,80.0,90.0,TRUE,no
%
%
%

      如果is_first_call = False, 则iter_encode只编码数据。

>>> encoder = ArffEncoder();
>>> result = encoder.iter_encode(obj, is_first_call = False);
>>> for i in result:
 . . .     print i+u'\n',;
 . . .
sunny,85.0,85.0,FALSE,no
sunny,80.0,90.0,TRUE,no
%
%
%

来自:http://www.rustle.us/?p=603