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

« back to all changes in this revision

Viewing changes to weka/filters/unsupervised/attribute/AddCluster.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
 *    AddCluster.java
 
19
 *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
 
20
 *
 
21
 */
 
22
 
 
23
package weka.filters.unsupervised.attribute;
 
24
 
 
25
import weka.clusterers.Clusterer;
 
26
import weka.core.Attribute;
 
27
import weka.core.Capabilities;
 
28
import weka.core.FastVector;
 
29
import weka.core.Instance;
 
30
import weka.core.Instances;
 
31
import weka.core.Option;
 
32
import weka.core.OptionHandler;
 
33
import weka.core.Range;
 
34
import weka.core.SparseInstance;
 
35
import weka.core.Utils;
 
36
import weka.filters.Filter;
 
37
import weka.filters.UnsupervisedFilter;
 
38
 
 
39
import java.util.Enumeration;
 
40
import java.util.Vector;
 
41
 
 
42
/** 
 
43
 <!-- globalinfo-start -->
 
44
 * A filter that adds a new nominal attribute representing the cluster assigned to each instance by the specified clustering algorithm.
 
45
 * <p/>
 
46
 <!-- globalinfo-end -->
 
47
 * 
 
48
 <!-- options-start -->
 
49
 * Valid options are: <p/>
 
50
 * 
 
51
 * <pre> -W &lt;clusterer specification&gt;
 
52
 *  Full class name of clusterer to use, followed
 
53
 *  by scheme options. eg:
 
54
 *   "weka.clusterers.SimpleKMeans -N 3"
 
55
 *  (default: weka.clusterers.SimpleKMeans)</pre>
 
56
 * 
 
57
 * <pre> -I &lt;att1,att2-att4,...&gt;
 
58
 *  The range of attributes the clusterer should ignore.
 
59
 * </pre>
 
60
 * 
 
61
 <!-- options-end -->
 
62
 *
 
63
 * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
 
64
 * @version $Revision: 1.10 $
 
65
 */
 
66
public class AddCluster 
 
67
  extends Filter 
 
68
  implements UnsupervisedFilter, OptionHandler {
 
69
  
 
70
  /** for serialization */
 
71
  static final long serialVersionUID = 7414280611943807337L;
 
72
 
 
73
  /** The clusterer used to do the cleansing */
 
74
  protected Clusterer m_Clusterer = new weka.clusterers.SimpleKMeans();
 
75
 
 
76
  /** Range of attributes to ignore */
 
77
  protected Range m_IgnoreAttributesRange = null;
 
78
 
 
79
  /** Filter for removing attributes */
 
80
  protected Filter m_removeAttributes = new Remove();
 
81
 
 
82
  /** 
 
83
   * Returns the Capabilities of this filter, makes sure that the class is
 
84
   * never set (for the clusterer).
 
85
   *
 
86
   * @param data        the data to use for customization
 
87
   * @return            the capabilities of this object, based on the data
 
88
   * @see               #getCapabilities()
 
89
   */
 
90
  public Capabilities getCapabilities(Instances data) {
 
91
    Instances   newData;
 
92
    
 
93
    newData = new Instances(data, 0);
 
94
    newData.setClassIndex(-1);
 
95
    
 
96
    return super.getCapabilities(newData);
 
97
  }
 
98
 
 
99
  /** 
 
100
   * Returns the Capabilities of this filter.
 
101
   *
 
102
   * @return            the capabilities of this object
 
103
   * @see               Capabilities
 
104
   */
 
105
  public Capabilities getCapabilities() {
 
106
    Capabilities result = m_Clusterer.getCapabilities();
 
107
    
 
108
    result.setMinimumNumberInstances(0);
 
109
    
 
110
    return result;
 
111
  }
 
112
  
 
113
  /**
 
114
   * tests the data whether the filter can actually handle it
 
115
   * 
 
116
   * @param instanceInfo        the data to test
 
117
   * @throws Exception          if the test fails
 
118
   */
 
119
  protected void testInputFormat(Instances instanceInfo) throws Exception {
 
120
    getCapabilities(instanceInfo).testWithFail(removeIgnored(instanceInfo));
 
121
  }
 
122
 
 
123
  /**
 
124
   * Sets the format of the input instances.
 
125
   *
 
126
   * @param instanceInfo an Instances object containing the input instance
 
127
   * structure (any instances contained in the object are ignored - only the
 
128
   * structure is required).
 
129
   * @return true if the outputFormat may be collected immediately
 
130
   * @throws Exception if the inputFormat can't be set successfully 
 
131
   */ 
 
132
  public boolean setInputFormat(Instances instanceInfo) throws Exception {
 
133
    
 
134
    super.setInputFormat(instanceInfo);
 
135
    m_removeAttributes = null;
 
136
 
 
137
    return false;
 
138
  }
 
139
 
 
140
  /**
 
141
   * filters all attributes that should be ignored
 
142
   * 
 
143
   * @param data        the data to filter
 
144
   * @return            the filtered data
 
145
   * @throws Exception  if filtering fails
 
146
   */
 
147
  protected Instances removeIgnored(Instances data) throws Exception {
 
148
    Instances result = data;
 
149
    
 
150
    if (m_IgnoreAttributesRange != null || data.classIndex() >= 0) {
 
151
      m_removeAttributes = new Remove();
 
152
      String rangeString = "";
 
153
      if (m_IgnoreAttributesRange != null) {
 
154
        rangeString += m_IgnoreAttributesRange.getRanges();
 
155
      }
 
156
      if (data.classIndex() >= 0) {
 
157
        if (rangeString.length() > 0) {
 
158
          rangeString += "," + (data.classIndex() + 1);
 
159
        } else {
 
160
          rangeString = "" + (data.classIndex() + 1);
 
161
        }
 
162
      }
 
163
      ((Remove) m_removeAttributes).setAttributeIndices(rangeString);
 
164
      ((Remove) m_removeAttributes).setInvertSelection(false);
 
165
      m_removeAttributes.setInputFormat(data);
 
166
      result = Filter.useFilter(data, m_removeAttributes);
 
167
    }
 
168
    
 
169
    return result;
 
170
  }
 
171
  
 
172
  /**
 
173
   * Signify that this batch of input to the filter is finished.
 
174
   *
 
175
   * @return true if there are instances pending output
 
176
   * @throws IllegalStateException if no input structure has been defined 
 
177
   */  
 
178
  public boolean batchFinished() throws Exception {
 
179
 
 
180
    if (getInputFormat() == null) {
 
181
      throw new IllegalStateException("No input instance format defined");
 
182
    }
 
183
 
 
184
    Instances toFilter = getInputFormat();
 
185
    
 
186
    if (!isFirstBatchDone()) {
 
187
      // filter out attributes if necessary
 
188
      Instances toFilterIgnoringAttributes = removeIgnored(toFilter);
 
189
 
 
190
      // build the clusterer
 
191
      m_Clusterer.buildClusterer(toFilterIgnoringAttributes);
 
192
 
 
193
      // create output dataset with new attribute
 
194
      Instances filtered = new Instances(toFilter, 0); 
 
195
      FastVector nominal_values = new FastVector(m_Clusterer.numberOfClusters());
 
196
      for (int i=0; i<m_Clusterer.numberOfClusters(); i++) {
 
197
        nominal_values.addElement("cluster" + (i+1)); 
 
198
      }
 
199
      filtered.insertAttributeAt(new Attribute("cluster", nominal_values),
 
200
          filtered.numAttributes());
 
201
 
 
202
      setOutputFormat(filtered);
 
203
    }
 
204
 
 
205
    // build new dataset
 
206
    for (int i=0; i<toFilter.numInstances(); i++) {
 
207
      convertInstance(toFilter.instance(i));
 
208
    }
 
209
    
 
210
    flushInput();
 
211
    m_NewBatch = true;
 
212
    m_FirstBatchDone = true;
 
213
 
 
214
    return (numPendingOutput() != 0);
 
215
  }
 
216
 
 
217
  /**
 
218
   * Input an instance for filtering. Ordinarily the instance is processed
 
219
   * and made available for output immediately. Some filters require all
 
220
   * instances be read before producing output.
 
221
   *
 
222
   * @param instance the input instance
 
223
   * @return true if the filtered instance may now be
 
224
   * collected with output().
 
225
   * @throws IllegalStateException if no input format has been defined.
 
226
   */
 
227
  public boolean input(Instance instance) throws Exception {
 
228
 
 
229
    if (getInputFormat() == null) {
 
230
      throw new IllegalStateException("No input instance format defined");
 
231
    }
 
232
    if (m_NewBatch) {
 
233
      resetQueue();
 
234
      m_NewBatch = false;
 
235
    }
 
236
    
 
237
    if (outputFormatPeek() != null) {
 
238
      convertInstance(instance);
 
239
      return true;
 
240
    }
 
241
 
 
242
    bufferInput(instance);
 
243
    return false;
 
244
  }
 
245
 
 
246
  /**
 
247
   * Convert a single instance over. The converted instance is added to 
 
248
   * the end of the output queue.
 
249
   *
 
250
   * @param instance the instance to convert
 
251
   * @throws Exception if something goes wrong
 
252
   */
 
253
  protected void convertInstance(Instance instance) throws Exception {
 
254
    Instance original, processed;
 
255
    original = instance;
 
256
 
 
257
    // copy values
 
258
    double[] instanceVals = new double[instance.numAttributes()+1];
 
259
    for(int j = 0; j < instance.numAttributes(); j++) {
 
260
      instanceVals[j] = original.value(j);
 
261
    }
 
262
    Instance filteredI = null;
 
263
    if (m_removeAttributes != null) {
 
264
      m_removeAttributes.input(instance);
 
265
      filteredI = m_removeAttributes.output();
 
266
    } else {
 
267
      filteredI = instance;
 
268
    }
 
269
 
 
270
    // add cluster to end
 
271
    instanceVals[instance.numAttributes()]
 
272
      = m_Clusterer.clusterInstance(filteredI);
 
273
 
 
274
    // create new instance
 
275
    if (original instanceof SparseInstance) {
 
276
      processed = new SparseInstance(original.weight(), instanceVals);
 
277
    } else {
 
278
      processed = new Instance(original.weight(), instanceVals);
 
279
    }
 
280
 
 
281
    processed.setDataset(instance.dataset());
 
282
    copyValues(processed, false, instance.dataset(), getOutputFormat());
 
283
    processed.setDataset(getOutputFormat());
 
284
      
 
285
    push(processed);
 
286
  }
 
287
 
 
288
  /**
 
289
   * Returns an enumeration describing the available options.
 
290
   *
 
291
   * @return an enumeration of all the available options.
 
292
   */
 
293
  public Enumeration listOptions() {
 
294
    
 
295
    Vector newVector = new Vector(2);
 
296
    
 
297
    newVector.addElement(new Option(
 
298
              "\tFull class name of clusterer to use, followed\n"
 
299
              + "\tby scheme options. eg:\n"
 
300
              + "\t\t\"weka.clusterers.SimpleKMeans -N 3\"\n"
 
301
              + "\t(default: weka.clusterers.SimpleKMeans)",
 
302
              "W", 1, "-W <clusterer specification>"));
 
303
    
 
304
    newVector.addElement(new Option(
 
305
              "\tThe range of attributes the clusterer should ignore.\n",
 
306
              "I", 1,"-I <att1,att2-att4,...>"));
 
307
 
 
308
    return newVector.elements();
 
309
  }
 
310
 
 
311
 
 
312
  /**
 
313
   * Parses a given list of options. <p/>
 
314
   * 
 
315
   <!-- options-start -->
 
316
   * Valid options are: <p/>
 
317
   * 
 
318
   * <pre> -W &lt;clusterer specification&gt;
 
319
   *  Full class name of clusterer to use, followed
 
320
   *  by scheme options. eg:
 
321
   *   "weka.clusterers.SimpleKMeans -N 3"
 
322
   *  (default: weka.clusterers.SimpleKMeans)</pre>
 
323
   * 
 
324
   * <pre> -I &lt;att1,att2-att4,...&gt;
 
325
   *  The range of attributes the clusterer should ignore.
 
326
   * </pre>
 
327
   * 
 
328
   <!-- options-end -->
 
329
   *
 
330
   * @param options the list of options as an array of strings
 
331
   * @throws Exception if an option is not supported
 
332
   */
 
333
  public void setOptions(String[] options) throws Exception {
 
334
 
 
335
    String clustererString = Utils.getOption('W', options);
 
336
    if (clustererString.length() == 0)
 
337
      clustererString = weka.clusterers.SimpleKMeans.class.getName();
 
338
    String[] clustererSpec = Utils.splitOptions(clustererString);
 
339
    if (clustererSpec.length == 0) {
 
340
      throw new Exception("Invalid clusterer specification string");
 
341
    }
 
342
    String clustererName = clustererSpec[0];
 
343
    clustererSpec[0] = "";
 
344
    setClusterer(Clusterer.forName(clustererName, clustererSpec));
 
345
        
 
346
    setIgnoredAttributeIndices(Utils.getOption('I', options));
 
347
 
 
348
    Utils.checkForRemainingOptions(options);
 
349
  }
 
350
 
 
351
  /**
 
352
   * Gets the current settings of the filter.
 
353
   *
 
354
   * @return an array of strings suitable for passing to setOptions
 
355
   */
 
356
  public String [] getOptions() {
 
357
 
 
358
    String [] options = new String [5];
 
359
    int current = 0;
 
360
 
 
361
    options[current++] = "-W"; options[current++] = "" + getClustererSpec();
 
362
    
 
363
    if (!getIgnoredAttributeIndices().equals("")) {
 
364
      options[current++] = "-I"; options[current++] = getIgnoredAttributeIndices();
 
365
    }
 
366
 
 
367
    while (current < options.length) {
 
368
      options[current++] = "";
 
369
    }
 
370
    return options;
 
371
  }
 
372
 
 
373
  /**
 
374
   * Returns a string describing this filter
 
375
   *
 
376
   * @return a description of the filter suitable for
 
377
   * displaying in the explorer/experimenter gui
 
378
   */
 
379
  public String globalInfo() {
 
380
 
 
381
    return "A filter that adds a new nominal attribute representing the cluster "
 
382
      + "assigned to each instance by the specified clustering algorithm.";
 
383
  }
 
384
 
 
385
  /**
 
386
   * Returns the tip text for this property
 
387
   *
 
388
   * @return tip text for this property suitable for
 
389
   * displaying in the explorer/experimenter gui
 
390
   */
 
391
  public String clustererTipText() {
 
392
 
 
393
    return "The clusterer to assign clusters with.";
 
394
  }
 
395
 
 
396
  /**
 
397
   * Sets the clusterer to assign clusters with.
 
398
   *
 
399
   * @param clusterer The clusterer to be used (with its options set).
 
400
   */
 
401
  public void setClusterer(Clusterer clusterer) {
 
402
 
 
403
    m_Clusterer = clusterer;
 
404
  }
 
405
  
 
406
  /**
 
407
   * Gets the clusterer used by the filter.
 
408
   *
 
409
   * @return The clusterer being used.
 
410
   */
 
411
  public Clusterer getClusterer() {
 
412
 
 
413
    return m_Clusterer;
 
414
  }
 
415
 
 
416
  /**
 
417
   * Gets the clusterer specification string, which contains the class name of
 
418
   * the clusterer and any options to the clusterer.
 
419
   *
 
420
   * @return the clusterer string.
 
421
   */
 
422
  protected String getClustererSpec() {
 
423
    
 
424
    Clusterer c = getClusterer();
 
425
    if (c instanceof OptionHandler) {
 
426
      return c.getClass().getName() + " "
 
427
        + Utils.joinOptions(((OptionHandler)c).getOptions());
 
428
    }
 
429
    return c.getClass().getName();
 
430
  }
 
431
 
 
432
  /**
 
433
   * Returns the tip text for this property
 
434
   *
 
435
   * @return tip text for this property suitable for
 
436
   * displaying in the explorer/experimenter gui
 
437
   */
 
438
  public String ignoredAttributeIndicesTipText() {
 
439
 
 
440
    return "The range of attributes to be ignored by the clusterer. eg: first-3,5,9-last";
 
441
  }
 
442
 
 
443
  /**
 
444
   * Gets ranges of attributes to be ignored.
 
445
   *
 
446
   * @return a string containing a comma-separated list of ranges
 
447
   */
 
448
  public String getIgnoredAttributeIndices() {
 
449
 
 
450
    if (m_IgnoreAttributesRange == null) {
 
451
      return "";
 
452
    } else {
 
453
      return m_IgnoreAttributesRange.getRanges();
 
454
    }
 
455
  }
 
456
 
 
457
  /**
 
458
   * Sets the ranges of attributes to be ignored. If provided string
 
459
   * is null, no attributes will be ignored.
 
460
   *
 
461
   * @param rangeList a string representing the list of attributes. 
 
462
   * eg: first-3,5,6-last
 
463
   * @throws IllegalArgumentException if an invalid range list is supplied 
 
464
   */
 
465
  public void setIgnoredAttributeIndices(String rangeList) {
 
466
 
 
467
    if ((rangeList == null) || (rangeList.length() == 0)) {
 
468
      m_IgnoreAttributesRange = null;
 
469
    } else {
 
470
      m_IgnoreAttributesRange = new Range();
 
471
      m_IgnoreAttributesRange.setRanges(rangeList);
 
472
    }
 
473
  }
 
474
 
 
475
  /**
 
476
   * Main method for testing this class.
 
477
   *
 
478
   * @param argv should contain arguments to the filter: use -h for help
 
479
   */
 
480
  public static void main(String [] argv) {
 
481
    runFilter(new AddCluster(), argv);
 
482
  }
 
483
}