ARFF reader for MATLAB

Recently I needed to export some simulation data, generated with MATLAB, to Weka, a very interesting Machine Learning tool written in Java.

EDIT: for more info about the ARFF reader look at the ARFF reader/writer page.

So I started to read some documentation on Weka website because I wanted to learn more and use all the possibilities offered by Weka preferred file format: ARFF.

One can ask why I’m not using the really simple CSV file format. The reason is as simple as CSV, it can’t represent nominal-specification attributes (like class style one’s) in a straightforward way.

For these reasons I wrote a couple of MATLAB’s utility functions to read and write data from ARFF files. Currently supported extensions are .arff and .arff.gz (using MATLAB’s embedded gzip/gunzip functions with temp files).

Here we see the ARFF reader’s source code, in a next post we’re going to see ARFF writer’s one together a couple of usage examples.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
% ARFF_READ - Read content of an ARFF file to a MATLAB's struct array.
%
%   [DATA, relname, nomspec] = ARFF_READ(arff_file)
%       arff_file => input file (.arff / .arff.gz extension)
%       relname => relation name (string)
%       DATA => struct array representing data and attributes (n x attrs)
%       nomspec => struct array defining nominal-specification attributes
%
%   NOTES:
%       See ARFF_WRITE to read notes about relname and nomspec.
%       See ARFF format specification on WEKA site.

% Authors:
%   Valerio De Carolis          <valerio.decarolis@gmail.com>
%
%  28 September 2012 - University of Rome "La Sapienza"

function [data, relname, nomspec] = arff_read(arff_file)

    if nargin < 1
        error('MATLAB:input','Not enough inputs!');
    end
   
    if isempty(arff_file)
        error('MATLAB:input','Bad file name!');
    end
   
    % check file extention
    [~, ~, ext] = fileparts(arff_file);
   
    if strcmpi(ext,'.arff')
       
        % open file
        fid = fopen(arff_file, 'r+t');
       
    elseif strcmpi(ext,'.gz')
       
        % temporary working dir
        outdir = tempdir;
       
        % decompress
        dec_files = gunzip(arff_file, outdir);
       
        if ~isempty(dec_files)
            fid = fopen(dec_files{1}, 'r+t');
        else
            error('%s is not a valid arff_file', arff_file);
        end            
   
    else
        error('%s is not a valid arff_file', arff_file);
    end    
   
    if fid == -1
        error('MATLAB:file','File not found!');
    end  
   
    % read relname
    relname = [];
   
    while isempty(relname)
        tline = fgetl(fid);
       
        if ~ischar(tline)
            error('MATLAB:file','ARFF file not recognized!');
            fclose(fid);
        end
       
        % avoid parsing @DATA and skip blank lines
        if length(tline) > 9 && tline(1) == '@' && strcmpi(tline(2:9),'RELATION')
            relname = tline(11:end);
            break;
        end            
    end
   
    % read attributes
    fields = {};
    ftypes = [];
   
    floop = 1;
    fn = 1;
   
    while floop
        tline = fgetl(fid);
       
        if ~ischar(tline)
            break;
        end
       
        % avoid parsing @DATA and skip blank lines
        if length(tline) > 5 && tline(1) == '@' && strcmpi(tline(2:10),'ATTRIBUTE')
           
            %at = strfind(tline, ' ');
            %
            %if length(at) < 2
            %    error('MATLAB:file','ARFF file not recognized!');
            %end
            %
            %fields{fn} = tline(at(1)+1:at(2)-1);
            %typedef = tline(at(2)+1:end);
           
            % parsing using textscan? (good for data, less for attributes)
            A = textscan(tline,'%s %s %s','Whitespace',' \t\b{},');
           
            if isempty(A{1}) || isempty(A{2}) || isempty(A{3})
               error('MATLAB:file','ARFF file not recognized!');
               fclose(fid);
            end
           
            if size(A{1},1) == 1
                fields{fn} = char(A{2});
                typedef = char(A{3});
            else
                fields{fn} = char(A{2}(1));
                bt = strfind(tline,'{');
                typedef = tline(bt(1):end);
            end
           
            if typedef(1) == '{' && typedef(end) == '}'
                ftypes(fn) = 1;
                %nomspec.(fields{fn}) = typedef;
               
                % out is a cell with parsed classes assuming { x, x, x } format  
                out = textscan(typedef, '%s', 'Delimiter', ' ,{}', 'MultipleDelimsAsOne', 1);
               
                % expand cell (avoid cell of cell)
                nomspec.(fields{fn}) = out{:};
            else
               if strcmpi(typedef,'NUMERIC')
                   ftypes(fn) = 0;
               elseif strcmpi(typedef,'STRING')
                   ftypes(fn) = 2;
               else
                   dt = strfind(typedef, ' ');
                   
                   if ~isempty(dt) && strcmpi(typedef(1:dt(1)-1), 'DATE')
                       ftypes(fn) = 3;
                       % implement date-format parsing
                   else
                       error('MATLAB:file','ARFF file not recognized!');
                       fclose(fid);
                   end
               end
            end
           
            fn = fn + 1;
        end
       
    end
   
    % create data struct
    data = struct();
   
    for fn = 1 : length(fields)
        data.(fields{fn}) = [];
    end
   
    % store empty struct
    data_tmpl = data;
       
    % rewind file
    fseek(fid,0,-1);
       
    % seek data
    has_data = 0;
   
    while floop
        tline = fgetl(fid);
       
        if length(tline) == 5 && strcmpi(tline(1:5),'@DATA')
            has_data = 1;
            break;
        end
       
        if ~ischar(tline)
            break;
        end
    end
   
    if has_data == 1
       
        dcnt = 1;
       
        while floop
            tline = fgetl(fid);

            if length(tline) > 1
               
                % find values
                vt = strfind(tline,',');
               
                % init with empty struct
                data(dcnt) = data_tmpl;
               
                for k = 1 : length(vt) + 1
               
                    if k == 1
                        if isempty(vt)
                            content = tline(1:end);
                        else
                            content = tline(1:vt(k)-1);
                        end
                    elseif k <= length(vt)
                        content = tline(vt(k-1)+1:vt(k)-1);
                    else
                        content = tline(vt(k-1)+1:end);
                    end

                    switch ftypes(k)
                        case 0
                            data(dcnt).(fields{k}) = str2double( content ); %str2num( content );
                        case 3
                            data(dcnt).(fields{k}) = datenum( content(2:end-1), 'yyyy-mm-dd HH:MM:SS' );
                        otherwise
                            data(dcnt).(fields{k}) = content;
                    end
               
                end
               
                dcnt = dcnt + 1;
               
            end

            if ~ischar(tline)
                break;
            end
        end
       
    end
   
    % close file
    fclose(fid);
   
    % remove temporary decompressed file
    if exist('dec_files','var') && ~isempty(dec_files)
        dec_files{1}
        delete(dec_files{1});
    end

end

% References:
%   [1]: http://www.cs.waikato.ac.nz/ml/weka/arff.html

Also read...