source: src/main/java/weka/attributeSelection/ChiSquaredAttributeEval.java @ 5

Last change on this file since 5 was 4, checked in by gnappo, 14 years ago

Import di weka.

File size: 13.4 KB
Line 
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 *    ChiSquaredAttributeEval.java
19 *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
20 *
21 */
22
23package weka.attributeSelection;
24
25import weka.core.Capabilities;
26import weka.core.ContingencyTables;
27import weka.core.Instance;
28import weka.core.Instances;
29import weka.core.Option;
30import weka.core.OptionHandler;
31import weka.core.RevisionUtils;
32import weka.core.Utils;
33import weka.core.Capabilities.Capability;
34import weka.filters.Filter;
35import weka.filters.supervised.attribute.Discretize;
36import weka.filters.unsupervised.attribute.NumericToBinary;
37
38import java.util.Enumeration;
39import java.util.Vector;
40
41/**
42 <!-- globalinfo-start -->
43 * ChiSquaredAttributeEval :<br/>
44 * <br/>
45 * Evaluates the worth of an attribute by computing the value of the chi-squared statistic with respect to the class.<br/>
46 * <p/>
47 <!-- globalinfo-end -->
48 *
49 <!-- options-start -->
50 * Valid options are: <p/>
51 *
52 * <pre> -M
53 *  treat missing values as a seperate value.</pre>
54 *
55 * <pre> -B
56 *  just binarize numeric attributes instead
57 *  of properly discretizing them.</pre>
58 *
59 <!-- options-end -->
60 *
61 * @author Eibe Frank (eibe@cs.waikato.ac.nz)
62 * @version $Revision: 5447 $
63 * @see Discretize
64 * @see NumericToBinary
65 */
66public class ChiSquaredAttributeEval
67  extends ASEvaluation
68  implements AttributeEvaluator, OptionHandler {
69 
70  /** for serialization */
71  static final long serialVersionUID = -8316857822521717692L;
72
73  /** Treat missing values as a seperate value */
74  private boolean m_missing_merge;
75
76  /** Just binarize numeric attributes */
77  private boolean m_Binarize;
78
79  /** The chi-squared value for each attribute */
80  private double[] m_ChiSquareds;
81
82  /**
83   * Returns a string describing this attribute evaluator
84   * @return a description of the evaluator suitable for
85   * displaying in the explorer/experimenter gui
86   */
87  public String globalInfo() {
88    return "ChiSquaredAttributeEval :\n\nEvaluates the worth of an attribute "
89      +"by computing the value of the chi-squared statistic with respect to the class.\n";
90  }
91
92  /**
93   * Constructor
94   */
95  public ChiSquaredAttributeEval () {
96    resetOptions();
97  }
98
99  /**
100   * Returns an enumeration describing the available options
101   * @return an enumeration of all the available options
102   **/
103  public Enumeration listOptions () {
104    Vector newVector = new Vector(2);
105    newVector.addElement(new Option("\ttreat missing values as a seperate " 
106                                    + "value.", "M", 0, "-M"));
107    newVector.addElement(new Option("\tjust binarize numeric attributes instead \n" 
108                                    +"\tof properly discretizing them.", "B", 0, 
109                                    "-B"));
110    return  newVector.elements();
111  }
112
113
114  /**
115   * Parses a given list of options. <p/>
116   *
117   <!-- options-start -->
118   * Valid options are: <p/>
119   *
120   * <pre> -M
121   *  treat missing values as a seperate value.</pre>
122   *
123   * <pre> -B
124   *  just binarize numeric attributes instead
125   *  of properly discretizing them.</pre>
126   *
127   <!-- options-end -->
128   *
129   * @param options the list of options as an array of strings
130   * @throws Exception if an option is not supported
131   */
132  public void setOptions (String[] options)
133    throws Exception {
134
135    resetOptions();
136    setMissingMerge(!(Utils.getFlag('M', options)));
137    setBinarizeNumericAttributes(Utils.getFlag('B', options));
138  }
139
140
141  /**
142   * Gets the current settings.
143   *
144   * @return an array of strings suitable for passing to setOptions()
145   */
146  public String[] getOptions () {
147    String[] options = new String[2];
148    int current = 0;
149
150    if (!getMissingMerge()) {
151      options[current++] = "-M";
152    }
153    if (getBinarizeNumericAttributes()) {
154      options[current++] = "-B";
155    }
156
157    while (current < options.length) {
158      options[current++] = "";
159    }
160
161    return  options;
162  }
163
164  /**
165   * Returns the tip text for this property
166   * @return tip text for this property suitable for
167   * displaying in the explorer/experimenter gui
168   */
169  public String binarizeNumericAttributesTipText() {
170    return "Just binarize numeric attributes instead of properly discretizing them.";
171  }
172
173  /**
174   * Binarize numeric attributes.
175   *
176   * @param b true=binarize numeric attributes
177   */
178  public void setBinarizeNumericAttributes (boolean b) {
179    m_Binarize = b;
180  }
181
182
183  /**
184   * get whether numeric attributes are just being binarized.
185   *
186   * @return true if missing values are being distributed.
187   */
188  public boolean getBinarizeNumericAttributes () {
189    return  m_Binarize;
190  }
191
192  /**
193   * Returns the tip text for this property
194   * @return tip text for this property suitable for
195   * displaying in the explorer/experimenter gui
196   */
197  public String missingMergeTipText() {
198    return "Distribute counts for missing values. Counts are distributed "
199      +"across other values in proportion to their frequency. Otherwise, "
200      +"missing is treated as a separate value.";
201  }
202
203  /**
204   * distribute the counts for missing values across observed values
205   *
206   * @param b true=distribute missing values.
207   */
208  public void setMissingMerge (boolean b) {
209    m_missing_merge = b;
210  }
211
212
213  /**
214   * get whether missing values are being distributed or not
215   *
216   * @return true if missing values are being distributed.
217   */
218  public boolean getMissingMerge () {
219    return  m_missing_merge;
220  }
221
222  /**
223   * Returns the capabilities of this evaluator.
224   *
225   * @return            the capabilities of this evaluator
226   * @see               Capabilities
227   */
228  public Capabilities getCapabilities() {
229    Capabilities result = super.getCapabilities();
230    result.disableAll();
231   
232    // attributes
233    result.enable(Capability.NOMINAL_ATTRIBUTES);
234    result.enable(Capability.NUMERIC_ATTRIBUTES);
235    result.enable(Capability.DATE_ATTRIBUTES);
236    result.enable(Capability.MISSING_VALUES);
237   
238    // class
239    result.enable(Capability.NOMINAL_CLASS);
240    result.enable(Capability.MISSING_CLASS_VALUES);
241   
242    return result;
243  }
244
245  /**
246   * Initializes a chi-squared attribute evaluator.
247   * Discretizes all attributes that are numeric.
248   *
249   * @param data set of instances serving as training data
250   * @throws Exception if the evaluator has not been
251   * generated successfully
252   */
253  public void buildEvaluator (Instances data)
254    throws Exception {
255   
256    // can evaluator handle data?
257    getCapabilities().testWithFail(data);
258
259    int classIndex = data.classIndex();
260    int numInstances = data.numInstances();
261   
262    if (!m_Binarize) {
263      Discretize disTransform = new Discretize();
264      disTransform.setUseBetterEncoding(true);
265      disTransform.setInputFormat(data);
266      data = Filter.useFilter(data, disTransform);
267    } else {
268      NumericToBinary binTransform = new NumericToBinary();
269      binTransform.setInputFormat(data);
270      data = Filter.useFilter(data, binTransform);
271    }     
272    int numClasses = data.attribute(classIndex).numValues();
273
274    // Reserve space and initialize counters
275    double[][][] counts = new double[data.numAttributes()][][];
276    for (int k = 0; k < data.numAttributes(); k++) {
277      if (k != classIndex) {
278        int numValues = data.attribute(k).numValues();
279        counts[k] = new double[numValues + 1][numClasses + 1];
280      }
281    }
282
283    // Initialize counters
284    double[] temp = new double[numClasses + 1];
285    for (int k = 0; k < numInstances; k++) {
286      Instance inst = data.instance(k);
287      if (inst.classIsMissing()) {
288        temp[numClasses] += inst.weight();
289      } else {
290        temp[(int)inst.classValue()] += inst.weight();
291      }
292    }
293    for (int k = 0; k < counts.length; k++) {
294      if (k != classIndex) {
295        for (int i = 0; i < temp.length; i++) {
296          counts[k][0][i] = temp[i];
297        }
298      }
299    }
300
301    // Get counts
302    for (int k = 0; k < numInstances; k++) {
303      Instance inst = data.instance(k);
304      for (int i = 0; i < inst.numValues(); i++) {
305        if (inst.index(i) != classIndex) {
306          if (inst.isMissingSparse(i) || inst.classIsMissing()) {
307            if (!inst.isMissingSparse(i)) {
308              counts[inst.index(i)][(int)inst.valueSparse(i)][numClasses] += 
309                inst.weight();
310              counts[inst.index(i)][0][numClasses] -= inst.weight();
311            } else if (!inst.classIsMissing()) {
312              counts[inst.index(i)][data.attribute(inst.index(i)).numValues()]
313                [(int)inst.classValue()] += inst.weight();
314              counts[inst.index(i)][0][(int)inst.classValue()] -= 
315                inst.weight();
316            } else {
317              counts[inst.index(i)][data.attribute(inst.index(i)).numValues()]
318                [numClasses] += inst.weight();
319              counts[inst.index(i)][0][numClasses] -= inst.weight();
320            }
321          } else {
322            counts[inst.index(i)][(int)inst.valueSparse(i)]
323              [(int)inst.classValue()] += inst.weight();
324            counts[inst.index(i)][0][(int)inst.classValue()] -= inst.weight();
325          }
326        }
327      }
328    }
329
330    // distribute missing counts if required
331    if (m_missing_merge) {
332     
333      for (int k = 0; k < data.numAttributes(); k++) {
334        if (k != classIndex) {
335          int numValues = data.attribute(k).numValues();
336
337          // Compute marginals
338          double[] rowSums = new double[numValues];
339          double[] columnSums = new double[numClasses];
340          double sum = 0;
341          for (int i = 0; i < numValues; i++) {
342            for (int j = 0; j < numClasses; j++) {
343              rowSums[i] += counts[k][i][j];
344              columnSums[j] += counts[k][i][j];
345            }
346            sum += rowSums[i];
347          }
348
349          if (Utils.gr(sum, 0)) {
350            double[][] additions = new double[numValues][numClasses];
351
352            // Compute what needs to be added to each row
353            for (int i = 0; i < numValues; i++) {
354              for (int j = 0; j  < numClasses; j++) {
355                additions[i][j] = (rowSums[i] / sum) * counts[k][numValues][j];
356              }
357            }
358           
359            // Compute what needs to be added to each column
360            for (int i = 0; i < numClasses; i++) {
361              for (int j = 0; j  < numValues; j++) {
362                additions[j][i] += (columnSums[i] / sum) * 
363                  counts[k][j][numClasses];
364              }
365            }
366           
367            // Compute what needs to be added to each cell
368            for (int i = 0; i < numClasses; i++) {
369              for (int j = 0; j  < numValues; j++) {
370                additions[j][i] += (counts[k][j][i] / sum) * 
371                  counts[k][numValues][numClasses];
372              }
373            }
374           
375            // Make new contingency table
376            double[][] newTable = new double[numValues][numClasses];
377            for (int i = 0; i < numValues; i++) {
378              for (int j = 0; j < numClasses; j++) {
379                newTable[i][j] = counts[k][i][j] + additions[i][j];
380              }
381            }
382            counts[k] = newTable;
383          }
384        }
385      }
386    }
387
388    // Compute chi-squared values
389    m_ChiSquareds = new double[data.numAttributes()];
390    for (int i = 0; i < data.numAttributes(); i++) {
391      if (i != classIndex) {
392        m_ChiSquareds[i] = ContingencyTables.
393          chiVal(ContingencyTables.reduceMatrix(counts[i]), false); 
394      }
395    }
396  }
397
398  /**
399   * Reset options to their default values
400   */
401  protected void resetOptions () {
402    m_ChiSquareds = null;
403    m_missing_merge = true;
404    m_Binarize = false;
405  }
406
407
408  /**
409   * evaluates an individual attribute by measuring its
410   * chi-squared value.
411   *
412   * @param attribute the index of the attribute to be evaluated
413   * @return the chi-squared value
414   * @throws Exception if the attribute could not be evaluated
415   */
416  public double evaluateAttribute (int attribute)
417    throws Exception {
418
419    return m_ChiSquareds[attribute];
420  }
421
422  /**
423   * Describe the attribute evaluator
424   * @return a description of the attribute evaluator as a string
425   */
426  public String toString () {
427    StringBuffer text = new StringBuffer();
428
429    if (m_ChiSquareds == null) {
430      text.append("Chi-squared attribute evaluator has not been built");
431    }
432    else {
433      text.append("\tChi-squared Ranking Filter");
434      if (!m_missing_merge) {
435        text.append("\n\tMissing values treated as seperate");
436      }
437      if (m_Binarize) {
438        text.append("\n\tNumeric attributes are just binarized");
439      }
440    }
441   
442    text.append("\n");
443    return  text.toString();
444  }
445 
446  /**
447   * Returns the revision string.
448   *
449   * @return            the revision
450   */
451  public String getRevision() {
452    return RevisionUtils.extract("$Revision: 5447 $");
453  }
454
455  /**
456   * Main method.
457   *
458   * @param args the options
459   */
460  public static void main (String[] args) {
461    runEvaluator(new ChiSquaredAttributeEval(), args);
462  }
463}
Note: See TracBrowser for help on using the repository browser.