~ubuntu-branches/ubuntu/precise/weka/precise

« back to all changes in this revision

Viewing changes to weka/filters/unsupervised/attribute/NumericToNominal.java

  • Committer: Bazaar Package Importer
  • Author(s): Soeren Sonnenburg
  • Date: 2008-02-24 09:18:45 UTC
  • Revision ID: james.westby@ubuntu.com-20080224091845-1l8zy6fm6xipbzsr
Tags: upstream-3.5.7+tut1
ImportĀ upstreamĀ versionĀ 3.5.7+tut1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *    This program is free software; you can redistribute it and/or modify
 
3
 *    it under the terms of the GNU General Public License as published by
 
4
 *    the Free Software Foundation; either version 2 of the License, or
 
5
 *    (at your option) any later version.
 
6
 *
 
7
 *    This program is distributed in the hope that it will be useful,
 
8
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
9
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
10
 *    GNU General Public License for more details.
 
11
 *
 
12
 *    You should have received a copy of the GNU General Public License
 
13
 *    along with this program; if not, write to the Free Software
 
14
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
15
 */
 
16
 
 
17
/*
 
18
 * NumericToNominal.java
 
19
 * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
 
20
 */
 
21
 
 
22
package weka.filters.unsupervised.attribute;
 
23
 
 
24
import weka.core.Attribute;
 
25
import weka.core.Capabilities;
 
26
import weka.core.FastVector;
 
27
import weka.core.Instance;
 
28
import weka.core.Instances;
 
29
import weka.core.Option;
 
30
import weka.core.Range;
 
31
import weka.core.SparseInstance;
 
32
import weka.core.Utils;
 
33
import weka.core.Capabilities.Capability;
 
34
import weka.filters.SimpleBatchFilter;
 
35
 
 
36
import java.util.Collections;
 
37
import java.util.Enumeration;
 
38
import java.util.HashSet;
 
39
import java.util.Vector;
 
40
 
 
41
/**
 
42
 <!-- globalinfo-start -->
 
43
 * A filter for turning numeric attributes into nominal ones. Unlike discretization, it just takes all numeric values and adds them to the list of nominal values of that attribute. Useful after CSV imports, to enforce certain attributes to become nominal, e.g., the class attribute, containing values from 1 to 5.
 
44
 * <p/>
 
45
 <!-- globalinfo-end -->
 
46
 * 
 
47
 <!-- options-start -->
 
48
 * Valid options are: <p/>
 
49
 * 
 
50
 * <pre> -R &lt;col1,col2-col4,...&gt;
 
51
 *  Specifies list of columns to Discretize. First and last are valid indexes.
 
52
 *  (default: first-last)</pre>
 
53
 * 
 
54
 * <pre> -V
 
55
 *  Invert matching sense of column indexes.</pre>
 
56
 * 
 
57
 <!-- options-end -->
 
58
 *
 
59
 * @author  fracpete (fracpete at waikato dot ac dot nz)
 
60
 * @version $Revision: 1.2 $
 
61
 */
 
62
public class NumericToNominal
 
63
  extends SimpleBatchFilter {
 
64
 
 
65
  /** for serialization */
 
66
  private static final long serialVersionUID = -6614630932899796239L;
 
67
 
 
68
  /** the maximum number of decimals to use */
 
69
  protected final static int MAX_DECIMALS = 6;
 
70
  
 
71
  /** Stores which columns to turn into nominals */
 
72
  protected Range m_Cols = new Range("first-last");
 
73
 
 
74
  /** The default columns to turn into nominals */
 
75
  protected String m_DefaultCols = "first-last";
 
76
 
 
77
  /**
 
78
   * Returns a string describing this filter
 
79
   *
 
80
   * @return            a description of the filter suitable for
 
81
   *                    displaying in the explorer/experimenter gui
 
82
   */
 
83
  public String globalInfo() {
 
84
    return 
 
85
        "A filter for turning numeric attributes into nominal ones. Unlike "
 
86
      + "discretization, it just takes all numeric values and adds them to "
 
87
      + "the list of nominal values of that attribute. Useful after CSV "
 
88
      + "imports, to enforce certain attributes to become nominal, e.g., "
 
89
      + "the class attribute, containing values from 1 to 5.";
 
90
  }
 
91
 
 
92
  /**
 
93
   * Gets an enumeration describing the available options.
 
94
   *
 
95
   * @return            an enumeration of all the available options.
 
96
   */
 
97
  public Enumeration listOptions() {
 
98
    Vector result = new Vector();
 
99
 
 
100
    result.addElement(new Option(
 
101
        "\tSpecifies list of columns to Discretize. First"
 
102
        + " and last are valid indexes.\n"
 
103
        + "\t(default: first-last)",
 
104
        "R", 1, "-R <col1,col2-col4,...>"));
 
105
 
 
106
    result.addElement(new Option(
 
107
        "\tInvert matching sense of column indexes.",
 
108
        "V", 0, "-V"));
 
109
 
 
110
    return result.elements();
 
111
  }
 
112
 
 
113
  /**
 
114
   * Parses a given list of options. <p/>
 
115
   * 
 
116
   <!-- options-start -->
 
117
   * Valid options are: <p/>
 
118
   * 
 
119
   * <pre> -R &lt;col1,col2-col4,...&gt;
 
120
   *  Specifies list of columns to Discretize. First and last are valid indexes.
 
121
   *  (default: first-last)</pre>
 
122
   * 
 
123
   * <pre> -V
 
124
   *  Invert matching sense of column indexes.</pre>
 
125
   * 
 
126
   <!-- options-end -->
 
127
   *
 
128
   * @param options the list of options as an array of strings
 
129
   * @throws Exception if an option is not supported
 
130
   */
 
131
  public void setOptions(String[] options) throws Exception {
 
132
    String      tmpStr;
 
133
 
 
134
    super.setOptions(options);
 
135
    
 
136
    setInvertSelection(Utils.getFlag('V', options));
 
137
 
 
138
    tmpStr = Utils.getOption('R', options);
 
139
    if (tmpStr.length() != 0)
 
140
      setAttributeIndices(tmpStr);
 
141
    else
 
142
      setAttributeIndices(m_DefaultCols);
 
143
 
 
144
    if (getInputFormat() != null)
 
145
      setInputFormat(getInputFormat());
 
146
  }
 
147
 
 
148
  /**
 
149
   * Gets the current settings of the filter.
 
150
   *
 
151
   * @return an array of strings suitable for passing to setOptions
 
152
   */
 
153
  public String[] getOptions() {
 
154
    int       i;
 
155
    Vector    result;
 
156
    String[]  options;
 
157
 
 
158
    result = new Vector();
 
159
    options = super.getOptions();
 
160
    for (i = 0; i < options.length; i++)
 
161
      result.add(options[i]);
 
162
 
 
163
    if (!getAttributeIndices().equals("")) {
 
164
      result.add("-R");
 
165
      result.add(getAttributeIndices());
 
166
    }
 
167
 
 
168
    if (getInvertSelection())
 
169
      result.add("-V");
 
170
 
 
171
    return (String[]) result.toArray(new String[result.size()]);          
 
172
  }
 
173
 
 
174
  /**
 
175
   * Returns the tip text for this property
 
176
   *
 
177
   * @return            tip text for this property suitable for
 
178
   *                    displaying in the explorer/experimenter gui
 
179
   */
 
180
  public String invertSelectionTipText() {
 
181
    return 
 
182
        "Set attribute selection mode. If false, only selected"
 
183
      + " (numeric) attributes in the range will be 'nominalized'; if"
 
184
      + " true, only non-selected attributes will be 'nominalized'.";
 
185
  }
 
186
 
 
187
  /**
 
188
   * Gets whether the supplied columns are to be worked on or the others.
 
189
   *
 
190
   * @return            true if the supplied columns will be worked on
 
191
   */
 
192
  public boolean getInvertSelection() {
 
193
    return m_Cols.getInvert();
 
194
  }
 
195
 
 
196
  /**
 
197
   * Sets whether selected columns should be worked on or all the others apart
 
198
   * from these. If true all the other columns are considered for 
 
199
   * "nominalization".
 
200
   *
 
201
   * @param value       the new invert setting
 
202
   */
 
203
  public void setInvertSelection(boolean value) {
 
204
    m_Cols.setInvert(value);
 
205
  }
 
206
 
 
207
  /**
 
208
   * Returns the tip text for this property
 
209
   *
 
210
   * @return            tip text for this property suitable for
 
211
   *                    displaying in the explorer/experimenter gui
 
212
   */
 
213
  public String attributeIndicesTipText() {
 
214
    return "Specify range of attributes to act on."
 
215
      + " This is a comma separated list of attribute indices, with"
 
216
      + " \"first\" and \"last\" valid values. Specify an inclusive"
 
217
      + " range with \"-\". E.g: \"first-3,5,6-10,last\".";
 
218
  }
 
219
 
 
220
  /**
 
221
   * Gets the current range selection
 
222
   *
 
223
   * @return            a string containing a comma separated list of ranges
 
224
   */
 
225
  public String getAttributeIndices() {
 
226
    return m_Cols.getRanges();
 
227
  }
 
228
 
 
229
  /**
 
230
   * Sets which attributes are to be "nominalized" (only numeric
 
231
   * attributes among the selection will be transformed).
 
232
   *
 
233
   * @param value       a string representing the list of attributes. Since
 
234
   *                    the string will typically come from a user, attributes 
 
235
   *                    are indexed from 1. <br> eg: first-3,5,6-last
 
236
   * @throws IllegalArgumentException if an invalid range list is supplied 
 
237
   */
 
238
  public void setAttributeIndices(String value) {
 
239
    m_Cols.setRanges(value);
 
240
  }
 
241
 
 
242
  /**
 
243
   * Sets which attributes are to be transoformed to nominal. (only numeric
 
244
   * attributes among the selection will be transformed).
 
245
   *
 
246
   * @param value       an array containing indexes of attributes to nominalize.
 
247
   *                    Since the array will typically come from a program, 
 
248
   *                    attributes are indexed from 0.
 
249
   * @throws IllegalArgumentException if an invalid set of ranges is supplied 
 
250
   */
 
251
  public void setAttributeIndicesArray(int[] value) {
 
252
    setAttributeIndices(Range.indicesToRangeList(value));
 
253
  }
 
254
 
 
255
  /** 
 
256
   * Returns the Capabilities of this filter.
 
257
   *
 
258
   * @return            the capabilities of this object
 
259
   * @see               Capabilities
 
260
   */
 
261
  public Capabilities getCapabilities() {
 
262
    Capabilities result = super.getCapabilities();
 
263
 
 
264
    // attributes
 
265
    result.enableAllAttributes();
 
266
    result.enable(Capability.MISSING_VALUES);
 
267
    
 
268
    // class
 
269
    result.enableAllClasses();
 
270
    result.enable(Capability.MISSING_CLASS_VALUES);
 
271
    result.enable(Capability.NO_CLASS);
 
272
    
 
273
    return result;
 
274
  }
 
275
 
 
276
  /**
 
277
   * Determines the output format based on the input format and returns 
 
278
   * this. In case the output format cannot be returned immediately, i.e.,
 
279
   * immediateOutputFormat() returns false, then this method will be called
 
280
   * from batchFinished().
 
281
   *
 
282
   * @param inputFormat     the input format to base the output format on
 
283
   * @return                the output format
 
284
   * @throws Exception      in case the determination goes wrong
 
285
   * @see   #hasImmediateOutputFormat()
 
286
   * @see   #batchFinished()
 
287
   */
 
288
  protected Instances determineOutputFormat(Instances inputFormat)
 
289
      throws Exception {
 
290
    
 
291
    Instances   data;
 
292
    Instances   result;
 
293
    FastVector  atts;
 
294
    FastVector  values;
 
295
    HashSet     hash;
 
296
    int         i;
 
297
    int         n;
 
298
    boolean     isDate;
 
299
    Instance    inst;
 
300
    Vector      sorted;
 
301
 
 
302
    m_Cols.setUpper(inputFormat.numAttributes() - 1);
 
303
    data = new Instances(inputFormat);
 
304
    atts = new FastVector();
 
305
    for (i = 0; i < data.numAttributes(); i++) {
 
306
      if (!m_Cols.isInRange(i) || !data.attribute(i).isNumeric()) {
 
307
        atts.addElement(data.attribute(i));
 
308
        continue;
 
309
      }
 
310
      
 
311
      // date attribute?
 
312
      isDate = (data.attribute(i).type() == Attribute.DATE);
 
313
      
 
314
      // determine all available attribtues in dataset
 
315
      hash   = new HashSet();
 
316
      for (n = 0; n < data.numInstances(); n++) {
 
317
        inst = data.instance(n);
 
318
        if (inst.isMissing(i))
 
319
          continue;
 
320
        
 
321
        if (isDate)
 
322
          hash.add(inst.stringValue(i));
 
323
        else
 
324
          hash.add(new Double(inst.value(i)));
 
325
      }
 
326
      
 
327
      // sort values
 
328
      sorted = new Vector();
 
329
      for (Object o: hash)
 
330
        sorted.add(o);
 
331
      Collections.sort(sorted);
 
332
      
 
333
      // create attribute from sorted values
 
334
      values = new FastVector();
 
335
      for (Object o: sorted) {
 
336
        if (isDate)
 
337
          values.addElement(
 
338
              o.toString());
 
339
        else
 
340
          values.addElement(
 
341
              Utils.doubleToString(((Double) o).doubleValue(), MAX_DECIMALS));
 
342
      }
 
343
      atts.addElement(new Attribute(data.attribute(i).name(), values));
 
344
    }
 
345
    
 
346
    result = new Instances(inputFormat.relationName(), atts, 0);
 
347
    result.setClassIndex(inputFormat.classIndex());
 
348
    
 
349
    return result;
 
350
  }
 
351
 
 
352
  /**
 
353
   * Processes the given data (may change the provided dataset) and returns
 
354
   * the modified version. This method is called in batchFinished().
 
355
   *
 
356
   * @param instances   the data to process
 
357
   * @return            the modified data
 
358
   * @throws Exception  in case the processing goes wrong
 
359
   * @see               #batchFinished()
 
360
   */
 
361
  protected Instances process(Instances instances) throws Exception {
 
362
    Instances   result;
 
363
    int         i;
 
364
    int         n;
 
365
    double[]    values;
 
366
    String      value;
 
367
    Instance    inst;
 
368
    Instance    newInst;
 
369
    
 
370
    // we need the complete input data!
 
371
    if (!isFirstBatchDone())
 
372
      setOutputFormat(determineOutputFormat(getInputFormat()));
 
373
    
 
374
    result = new Instances(getOutputFormat());
 
375
    
 
376
    for (i = 0; i < instances.numInstances(); i++) {
 
377
      inst   = instances.instance(i);
 
378
      values = inst.toDoubleArray();
 
379
      
 
380
      for (n = 0; n < values.length; n++) {
 
381
        if (    !m_Cols.isInRange(n)
 
382
             || !instances.attribute(n).isNumeric() 
 
383
             || inst.isMissing(n) )
 
384
          continue;
 
385
 
 
386
        // get index of value
 
387
        if (instances.attribute(n).type() == Attribute.DATE)
 
388
          value = inst.stringValue(n);
 
389
        else
 
390
          value = Utils.doubleToString(inst.value(n), MAX_DECIMALS);
 
391
        
 
392
        values[n] = result.attribute(n).indexOfValue(value);
 
393
      }
 
394
      
 
395
      // generate new instance
 
396
      if (inst instanceof SparseInstance)
 
397
        newInst = new SparseInstance(inst.weight(), values);
 
398
      else
 
399
        newInst = new Instance(inst.weight(), values);
 
400
      
 
401
      // copy possible string, relational values
 
402
      newInst.setDataset(getOutputFormat());
 
403
      copyValues(newInst, false, inst.dataset(), getOutputFormat());
 
404
      
 
405
      result.add(newInst);
 
406
    }
 
407
    
 
408
    return result;
 
409
  }
 
410
 
 
411
  /**
 
412
   * Runs the filter with the given parameters. Use -h to list options.
 
413
   * 
 
414
   * @param args        the commandline options
 
415
   */
 
416
  public static void main(String[] args) {
 
417
    runFilter(new NumericToNominal(), args);
 
418
  }
 
419
}