v0.3.9b:
[mldemos:baraks-mldemos.git] / _IOPlugins / CSVImport / parser.cpp
1 /*********************************************************************
2 MLDemos: A User-Friendly visualization toolkit for machine learning
3 Copyright (C) 2011 Chrstophe Paccolat
4 Contact: mldemos@b4silio.com
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public License,
8 version 3 as published by the Free Software Foundation.
9
10 This library is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free
17 Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 *********************************************************************/
19 #include <QDebug>
20 #include "parser.h"
21
22 /* CSVRow stuff */
23
24 std::istream& operator>>(std::istream& str,CSVRow& data)
25 {
26     data.readNextRow(str);
27     return str;
28 }
29
30 std::string const& CSVRow::operator[](std::size_t index) const
31 {
32     return m_data[index];
33 }
34 std::string CSVRow::at(size_t column) const
35 {
36     return m_data.at(column);
37 }
38 std::string CSVRow::getFirstCell() const
39 {
40         if(!m_data.size()) return string();
41         return m_data.at(0);
42 }
43 std::string CSVRow::getLastCell() const
44 {
45         if(!m_data.size()) return string();
46         return m_data.back();
47 }
48 std::vector<std::string> CSVRow::getParsedLine() const
49 {
50     return m_data;
51 }
52 std::size_t CSVRow::size() const
53 {
54     return m_data.size();
55 }
56
57 void CSVRow::readNextRow(std::istream& str)
58 {
59     // read line from file
60     std::string line;
61     std::getline(str,line);
62
63     // convert to stream
64     std::stringstream lineStream(line);
65     std::string cell;
66
67     // update row (array) content
68     m_data.clear();
69     while(std::getline(lineStream,cell,','))
70     {
71         m_data.push_back(cell);
72     }
73 }
74
75 unsigned int getType(string input)
76 {
77     if (is<unsigned int>(input))    return UNSIGNED_INT_TYPE;
78     if (is<int>(input))             return INT_TYPE;
79     if (is<float>(input))           return FLOAT_TYPE;
80     if (is<char>(input))            return CHAR_TYPE;
81     if (is<string>(input))          return STRING_TYPE;
82                                     return UNKNOWN_TYPE;
83 }
84
85 /* CSVIterator stuff */
86
87 CSVIterator::CSVIterator(std::istream& str)  :m_str(str.good()?&str:NULL)
88 {
89     ++(*this);
90 }
91
92 CSVIterator::CSVIterator():m_str(NULL)
93 {
94 }
95
96 bool CSVIterator::eof()
97 {
98     return (m_str == NULL);
99 }
100
101 // Pre Increment
102 CSVIterator& CSVIterator::operator++()
103 {
104     if (m_str)
105     {
106         (*m_str) >> m_row;
107         m_str = m_str->good()?m_str:NULL;
108     }
109     return *this;
110 }
111 // Post increment
112 CSVIterator CSVIterator::operator++(int)
113 {
114     CSVIterator tmp(*this);
115     ++(*this);
116     return tmp;
117 }
118
119 CSVRow const& CSVIterator::operator*()   const
120 {
121     return m_row;
122 }
123
124 CSVRow const* CSVIterator::operator->()  const
125 {
126     return &m_row;
127 }
128
129 bool CSVIterator::operator!=(CSVIterator const& rhs)
130 {
131     return !((*this) == rhs);
132 }
133
134 bool CSVIterator::operator==(CSVIterator const& rhs)
135 {
136     return ((this == &rhs) || ((this->m_str == NULL) && (rhs.m_str == NULL)));
137 }
138
139 /* CSVParser stuff */
140 CSVParser::CSVParser()
141 {
142     outputLabelColumn = 2;
143 }
144
145 void CSVParser::clear()
146 {
147     outputLabelColumn = 0;
148     classLabels.clear();
149     data.clear();
150     dataTypes.clear();
151 }
152
153 void CSVParser::parse(const char* fileName)
154 {
155     // init
156     file.open(fileName);
157     if(!file.is_open()) return;
158
159     // Parse CSV input file
160     for(CSVIterator parser(file); !parser.eof(); ++parser)
161     {
162         if(!parser->size()) continue;
163                 vector<string> parsed = parser->getParsedLine();
164                 if(!parsed.size()) continue;
165                 // Fill dataset
166                 data.push_back(parsed);
167     }
168
169     cout << "Parsing done, read " << data.size() << " entries" << endl;
170     cout << "Found " << data.at(0).size()-1 << " input labels" << endl;
171
172     // look for data types
173     for(size_t i = 0; i < data.at(1).size(); i++)
174     {
175         // Look for a non-empty cell
176         // start with 2nd row as first might be input labels
177         size_t testRow = 1;
178         while(data.at(testRow).at(i) == "?") testRow++;
179
180         // The whole column is missing data...
181         if (testRow == data.size())
182         {
183             cout << "WebImport: Warning: Found empty column" << endl;
184             // TODO delete it
185         }
186
187         // save input types
188         dataTypes.push_back(getType(data.at(testRow).at(i)));
189     }
190
191     // Read output (class) labels
192     getOutputLabelTypes(true);
193     file.close();
194 }
195
196 void CSVParser::setOutputColumn(unsigned int column)
197 {
198     outputLabelColumn = column;
199    //getOutputLabelTypes(true); // need to update output label types
200 }
201
202 map<string,unsigned int> CSVParser::getOutputLabelTypes(bool reparse)
203 {
204     if (!reparse) return classLabels;
205     unsigned int id = 0;
206     pair<map<string,unsigned int>::iterator,bool> ret;
207     // Use by default the last column as output class
208     if ((data.size() && outputLabelColumn == -1) || outputLabelColumn >= data.size()) outputLabelColumn = data.at(0).size()-1;
209     for(vector<vector<string> >::iterator it = data.begin(); it<data.end(); it++)
210     {
211         ret = classLabels.insert( pair<string,unsigned int>(it->at(outputLabelColumn),id) );
212         if (ret.second == true) id++; // new class found
213     }
214     return classLabels;
215 }
216
217 vector<size_t> CSVParser::getMissingValIndex()
218 {
219     vector<size_t> missingValIndex;
220     size_t nbCols = data.at(0).size();
221     for (size_t i = 0; i < data.size(); i++)
222         for (size_t j = 0; j < nbCols; j++)
223             if (data[i][j] == "?") missingValIndex.push_back(i);
224     return missingValIndex;
225 }
226
227 bool CSVParser::hasData()
228 {
229     return data.size();
230 }
231
232 void CSVParser::cleanData(unsigned int acceptedTypes)
233 {
234     vector<string>::iterator it_str;
235     vector<unsigned int>::iterator it_uint = dataTypes.begin();
236     for(size_t i = 0; i < dataTypes.size(); i++)
237         if (!(dataTypes[i]&acceptedTypes) &&  // data type does not correspond to a requested one
238            (i != outputLabelColumn))       // output labels are stored separately, ignore
239         {
240             cout << "Removing colum " << i << " of type " << dataTypes[i] <<  " ... ";
241             for(size_t j = 0; j < data.size(); j++)
242             {
243                 /* @note it seems that if we have --i instead of (i-1), the compiler produces bad code (SIGSEGV) */
244                 it_str = data.at(j).begin() + (i-1);
245                 data.at(j).erase(it_str); // delete the column
246             }
247             cout << "and matching type reference ...  " ;
248             it_uint = dataTypes.begin() + (i-1);
249             dataTypes.erase(it_uint); // delete the input to stay consistant
250             i--; if (i < outputLabelColumn) outputLabelColumn--;
251         }
252 }
253
254 pair<vector<fvec>,ivec> CSVParser::getData(ivec excludeIndex)
255 {
256     if(!data.size()) return pair<vector<fvec>,ivec>();
257     vector<fvec> samples(data.size());
258     ivec labels(data.size());
259     int count = data.size();
260     int dim = data[0].size();
261     outputLabelColumn = min(dim-1, outputLabelColumn);
262     vector< map<string,int> > labelMaps(dim);
263     ivec labelCounters(dim,0);
264     pair<map<string,int>::iterator,bool> ret;
265     FOR(i, data.size())
266     {
267         // check if it's always a number
268         fvec& sample = samples[i];
269         sample.resize(dim-1);
270         FOR(j, dim)
271         {
272             QString s(data[i][j].c_str());
273             bool ok;
274             float val = s.toFloat(&ok);
275             if(j==outputLabelColumn || !ok)
276             {
277                 val = (float)labelCounters[j];
278                 ret = labelMaps[j].insert(pair<string,int>(data[i][j], labelCounters[j]));
279                 if(ret.second) labelCounters[j]++;
280             }
281             if(j==outputLabelColumn)
282             {
283                                 labels[i] = (int)val;
284                 continue;
285             }
286             int index = j < outputLabelColumn ? j : j-1;
287             sample[index] = val;
288         }
289     }
290     if(!excludeIndex.size()) return pair<vector<fvec>,ivec>(samples,labels);
291     vector<fvec> newSamples(data.size());
292     int newDim = dim - excludeIndex.size();
293     FOR(i, excludeIndex.size())
294     {
295         // if it's the output we can ignore it but we need to reincrement the number of dimensions
296         if(excludeIndex[i] == outputLabelColumn)
297         {
298             newDim++;
299             break;
300         }
301     }
302     vector<bool> bExclude(dim, false);
303     FOR(i, excludeIndex.size())
304     {
305         bExclude[excludeIndex[i]] = true;
306     }
307     FOR(i, data.size())
308     {
309         newSamples[i].resize(newDim);
310         int nD = 0;
311         FOR(d, dim)
312         {
313             if(bExclude[d] || d == outputLabelColumn) continue;
314             if (d < outputLabelColumn) newSamples[i][nD] = samples[i][d];
315             else if(d > outputLabelColumn) newSamples[i][nD] = samples[i][d-1];
316             nD++;
317         }
318     }
319     return pair<vector<fvec>,ivec>(newSamples,labels);
320 }
321
322
323