DiscretizeFilter.java
Upload User: rhdiban
Upload Date: 2013-08-09
Package Size: 15085k
Code Size: 33k
Category:

Windows Develop

Development Platform:

Java

  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.  *    DiscretizeFilter.java
  18.  *    Copyright (C) 1999 Eibe Frank,Len Trigg
  19.  *
  20.  */
  21. package weka.filters;
  22. import java.io.*;
  23. import java.util.*;
  24. import weka.core.*;
  25. /** 
  26.  * An instance filter that discretizes a range of numeric attributes in 
  27.  * the dataset into nominal attributes. Discretization can be either by 
  28.  * simple binning, or by Fayyad & Irani's MDL method (the default).<p>
  29.  *
  30.  * Valid filter-specific options are: <p>
  31.  *
  32.  * -B num <br>
  33.  * Specify the (maximum) number of bins to divide numeric attributes into.
  34.  * (default class-based discretisation).<p>
  35.  *
  36.  * -O <br>
  37.  * Optimizes the number of bins using a leave-one-out estimate of the 
  38.  * entropy.<p>
  39.  *
  40.  * -R col1,col2-col4,... <br>
  41.  * Specify list of columns to Discretize. First
  42.  * and last are valid indexes. (default none) <p>
  43.  *
  44.  * -V <br>
  45.  * Invert matching sense.<p>
  46.  *
  47.  * -D <br>
  48.  * Make binary nominal attributes. <p>
  49.  *
  50.  * -E <br>
  51.  * Use better encoding of split point for MDL. <p>
  52.  *   
  53.  * -K <br>
  54.  * Use Kononeko's MDL criterion. <p>
  55.  * 
  56.  * @author Len Trigg (trigg@cs.waikato.ac.nz)
  57.  * @author Eibe Frank (eibe@cs.waikato.ac.nz) (Fayyad and Irani's method)
  58.  * @version $Revision: 1.15 $
  59.  */
  60. public class DiscretizeFilter extends Filter 
  61.   implements OptionHandler, WeightedInstancesHandler {
  62.   /** Stores which columns to Discretize */
  63.   protected Range m_DiscretizeCols = new Range();
  64.   /** The number of bins to divide the attribute into */
  65.   protected int m_NumBins = 10;
  66.   /** Store the current cutpoints */
  67.   protected double [][] m_CutPoints = null;
  68.   /** True if discretisation will be done by MDL rather than binning */
  69.   protected boolean m_UseMDL = true;
  70.   /** Output binary attributes for discretized attributes. */
  71.   protected boolean m_MakeBinary = false;
  72.   /** Use better encoding of split point for MDL. */
  73.   protected boolean m_UseBetterEncoding = false;
  74.   /** Use Kononenko's MDL criterion instead of Fayyad et al.'s */
  75.   protected boolean m_UseKononenko = false;
  76.   /** Find the number of bins using cross-validated entropy. */
  77.   protected boolean m_FindNumBins = false;
  78.   /** Constructor - initialises the filter */
  79.   public DiscretizeFilter() {
  80.     setAttributeIndices("first-last");
  81.   }
  82.   /**
  83.    * Gets an enumeration describing the available options
  84.    *
  85.    * @return an enumeration of all the available options
  86.    */
  87.   public Enumeration listOptions() {
  88.     Vector newVector = new Vector(7);
  89.     newVector.addElement(new Option(
  90.               "tSpecify the (maximum) number of bins to divide numeric"
  91.       + " attributes into.n"
  92.       + "t(default class-based discretization)",
  93.               "B", 1, "-B <num>"));
  94.     newVector.addElement(new Option(
  95.               "tOptimize number of bins using leave-one-out estimaten"+
  96.       "t of estimated entropy.",
  97.               "O", 0, "-O"));
  98.     /* If we decide to implement loading and saving cutfiles like 
  99.      * the C Discretizer (which is probably not necessary)
  100.     newVector.addElement(new Option(
  101.               "tSpecify that the cutpoints should be loaded from a file.",
  102.               "L", 1, "-L <file>"));
  103.     newVector.addElement(new Option(
  104.               "tSpecify that the chosen cutpoints should be saved to a file.",
  105.               "S", 1, "-S <file>"));
  106.     */
  107.     newVector.addElement(new Option(
  108.               "tSpecify list of columns to Discretize. First"
  109.       + " and last are valid indexes.n"
  110.       + "t(default none)",
  111.               "R", 1, "-R <col1,col2-col4,...>"));
  112.     newVector.addElement(new Option(
  113.               "tInvert matching sense of column indexes.",
  114.               "V", 0, "-V"));
  115.     newVector.addElement(new Option(
  116.               "tOutput binary attributes for discretized attributes.",
  117.               "D", 0, "-D"));
  118.     newVector.addElement(new Option(
  119.               "tUse better encoding of split point for MDL.",
  120.               "E", 0, "-E"));
  121.     newVector.addElement(new Option(
  122.               "tUse Kononenko's MDL criterion.",
  123.               "K", 0, "-K"));
  124.     return newVector.elements();
  125.   }
  126.   /**
  127.    * Parses the options for this object. Valid options are: <p>
  128.    *
  129.    * -B num <br>
  130.    * Specify the (maximum) number of equal-width bins to divide
  131.    * numeric attributes into. (default class-based discretization).<p>
  132.    *
  133.    * -O
  134.    * Optimizes the number of bins using a leave-one-out estimate of the 
  135.    * entropy.
  136.    *
  137.    * -R col1,col2-col4,... <br>
  138.    * Specify list of columns to discretize. First
  139.    * and last are valid indexes. (default none) <p>
  140.    *
  141.    * -V <br>
  142.    * Invert matching sense.<p>
  143.    *
  144.    * -D <br>
  145.    * Make binary nominal attributes. <p>
  146.    *
  147.    * -E <br>
  148.    * Use better encoding of split point for MDL. <p>
  149.    *   
  150.    * -K <br>
  151.    * Use Kononeko's MDL criterion. <p>
  152.    * 
  153.    * @param options the list of options as an array of strings
  154.    * @exception Exception if an option is not supported
  155.    */
  156.   public void setOptions(String[] options) throws Exception {
  157.     setMakeBinary(Utils.getFlag('D', options));
  158.     setUseBetterEncoding(Utils.getFlag('E', options));
  159.     setUseKononenko(Utils.getFlag('K', options));
  160.     setFindNumBins(Utils.getFlag('O', options));
  161.     setInvertSelection(Utils.getFlag('V', options));
  162.     setUseMDL(true);
  163.     String numBins = Utils.getOption('B', options);
  164.     if (numBins.length() != 0) {
  165.       setBins(Integer.parseInt(numBins));
  166.       setUseMDL(false);
  167.     } else {
  168.       setBins(10);
  169.     }
  170.     
  171.     String convertList = Utils.getOption('R', options);
  172.     if (convertList.length() != 0) {
  173.       setAttributeIndices(convertList);
  174.     } else {
  175.       setAttributeIndices("first-last");
  176.     }
  177.     if (getInputFormat() != null) {
  178.       setInputFormat(getInputFormat());
  179.     }
  180.   }
  181.   /**
  182.    * Gets the current settings of the filter.
  183.    *
  184.    * @return an array of strings suitable for passing to setOptions
  185.    */
  186.   public String [] getOptions() {
  187.     String [] options = new String [11];
  188.     int current = 0;
  189.     if (getMakeBinary()) {
  190.       options[current++] = "-D";
  191.     }
  192.     if (getUseBetterEncoding()) {
  193.       options[current++] = "-E";
  194.     }
  195.     if (getUseKononenko()) {
  196.       options[current++] = "-K";
  197.     }
  198.     if (getFindNumBins()) {
  199.       options[current++] = "-O";
  200.     }
  201.     if (getInvertSelection()) {
  202.       options[current++] = "-V";
  203.     }
  204.     if (!getUseMDL()) {
  205.       options[current++] = "-B"; options[current++] = "" + getBins();
  206.     }
  207.     if (!getAttributeIndices().equals("")) {
  208.       options[current++] = "-R"; options[current++] = getAttributeIndices();
  209.     }
  210.     while (current < options.length) {
  211.       options[current++] = "";
  212.     }
  213.     return options;
  214.   }
  215.   /**
  216.    * Sets the format of the input instances.
  217.    *
  218.    * @param instanceInfo an Instances object containing the input instance
  219.    * structure (any instances contained in the object are ignored - only the
  220.    * structure is required).
  221.    * @return true if the outputFormat may be collected immediately
  222.    * @exception Exception if the input format can't be set successfully
  223.    */
  224.   public boolean setInputFormat(Instances instanceInfo) throws Exception {
  225.     super.setInputFormat(instanceInfo);
  226.     m_DiscretizeCols.setUpper(instanceInfo.numAttributes() - 1);
  227.     m_CutPoints = null;
  228.     if (m_UseMDL) {
  229.       if (instanceInfo.classIndex() < 0) {
  230. throw new UnassignedClassException("Cannot use class-based discretization: "
  231.                                            + "no class assigned to the dataset");
  232.       }
  233.       if (!instanceInfo.classAttribute().isNominal()) {
  234. throw new UnsupportedClassTypeException("Supervised discretization not possible:"
  235.                                                 + " class is not nominal!");
  236.       }
  237.     }
  238.     // If we implement loading cutfiles, then load 
  239.     //them here and set the output format
  240.     return false;
  241.   }
  242.   
  243.   /**
  244.    * Input an instance for filtering. Ordinarily the instance is processed
  245.    * and made available for output immediately. Some filters require all
  246.    * instances be read before producing output.
  247.    *
  248.    * @param instance the input instance
  249.    * @return true if the filtered instance may now be
  250.    * collected with output().
  251.    * @exception IllegalStateException if no input format has been defined.
  252.    */
  253.   public boolean input(Instance instance) {
  254.     if (getInputFormat() == null) {
  255.       throw new IllegalStateException("No input instance format defined");
  256.     }
  257.     if (m_NewBatch) {
  258.       resetQueue();
  259.       m_NewBatch = false;
  260.     }
  261.     
  262.     if (m_CutPoints != null) {
  263.       convertInstance(instance);
  264.       return true;
  265.     }
  266.     bufferInput(instance);
  267.     return false;
  268.   }
  269.   /**
  270.    * Signifies that this batch of input to the filter is finished. If the 
  271.    * filter requires all instances prior to filtering, output() may now 
  272.    * be called to retrieve the filtered instances.
  273.    *
  274.    * @return true if there are instances pending output
  275.    * @exception IllegalStateException if no input structure has been defined
  276.    */
  277.   public boolean batchFinished() {
  278.     if (getInputFormat() == null) {
  279.       throw new IllegalStateException("No input instance format defined");
  280.     }
  281.     if (m_CutPoints == null) {
  282.       calculateCutPoints();
  283.       setOutputFormat();
  284.       // If we implement saving cutfiles, save the cuts here
  285.       // Convert pending input instances
  286.       for(int i = 0; i < getInputFormat().numInstances(); i++) {
  287. convertInstance(getInputFormat().instance(i));
  288.       }
  289.     } 
  290.     flushInput();
  291.     m_NewBatch = true;
  292.     return (numPendingOutput() != 0);
  293.   }
  294.   /**
  295.    * Returns a string describing this filter
  296.    *
  297.    * @return a description of the filter suitable for
  298.    * displaying in the explorer/experimenter gui
  299.    */
  300.   public String globalInfo() {
  301.     return "An instance filter that discretizes a range of numeric"
  302.       + " attributes in the dataset into nominal attributes."
  303.       + " Discretization can be either by simple binning, or by"
  304.       + " Fayyad & Irani's MDL method (the default).";
  305.   }
  306.   
  307.   /**
  308.    * Returns the tip text for this property
  309.    *
  310.    * @return tip text for this property suitable for
  311.    * displaying in the explorer/experimenter gui
  312.    */
  313.   public String findNumBinsTipText() {
  314.     return "Optimize bins using leave-one-out.";
  315.   }
  316.   /**
  317.    * Get the value of FindNumBins.
  318.    *
  319.    * @return Value of FindNumBins.
  320.    */
  321.   public boolean getFindNumBins() {
  322.     
  323.     return m_FindNumBins;
  324.   }
  325.   
  326.   /**
  327.    * Set the value of FindNumBins.
  328.    *
  329.    * @param newFindNumBins Value to assign to FindNumBins.
  330.    */
  331.   public void setFindNumBins(boolean newFindNumBins) {
  332.     
  333.     m_UseMDL = false;
  334.     m_FindNumBins = newFindNumBins;
  335.   }
  336.   
  337.   /**
  338.    * Returns the tip text for this property
  339.    *
  340.    * @return tip text for this property suitable for
  341.    * displaying in the explorer/experimenter gui
  342.    */
  343.   public String makeBinaryTipText() {
  344.     return "Make resulting attributes binary.";
  345.   }
  346.   /**
  347.    * Gets whether binary attributes should be made for discretized ones.
  348.    *
  349.    * @return true if attributes will be binarized
  350.    */
  351.   public boolean getMakeBinary() {
  352.     return m_MakeBinary;
  353.   }
  354.   /** 
  355.    * Sets whether binary attributes should be made for discretized ones.
  356.    *
  357.    * @param makeBinary if binary attributes are to be made
  358.    */
  359.   public void setMakeBinary(boolean makeBinary) {
  360.     m_MakeBinary = makeBinary;
  361.   }
  362.   /**
  363.    * Returns the tip text for this property
  364.    *
  365.    * @return tip text for this property suitable for
  366.    * displaying in the explorer/experimenter gui
  367.    */
  368.   public String useMDLTipText() {
  369.     return "Use class-based discretization. If set to false, does"
  370.       + " not require a class attribute, and uses a fixed number"
  371.       + " of bins (according to bins setting).";
  372.   }
  373.   /**
  374.    * Gets whether MDL will be used as the discretisation method.
  375.    *
  376.    * @return true if so, false if fixed bins should be used.
  377.    */
  378.   public boolean getUseMDL() {
  379.     return m_UseMDL;
  380.   }
  381.   /** 
  382.    * Sets whether MDL will be used as the discretisation method.
  383.    *
  384.    * @param useMDL true if MDL should be used, false if fixed bins should
  385.    * be used.
  386.    */
  387.   public void setUseMDL(boolean useMDL) {
  388.     m_UseMDL = useMDL;
  389.   }
  390.   /**
  391.    * Returns the tip text for this property
  392.    *
  393.    * @return tip text for this property suitable for
  394.    * displaying in the explorer/experimenter gui
  395.    */
  396.   public String useKononenkoTipText() {
  397.     return "Use Kononenko's MDL criterion. If set to false"
  398.       + " uses the Fayyad & Irani criterion.";
  399.   }
  400.   /**
  401.    * Gets whether Kononenko's MDL criterion is to be used.
  402.    *
  403.    * @return true if Kononenko's criterion will be used.
  404.    */
  405.   public boolean getUseKononenko() {
  406.     return m_UseKononenko;
  407.   }
  408.   /** 
  409.    * Sets whether Kononenko's MDL criterion is to be used.
  410.    *
  411.    * @param useKon true if Kononenko's one is to be used
  412.    */
  413.   public void setUseKononenko(boolean useKon) {
  414.     m_UseMDL = true;
  415.     m_UseKononenko = useKon;
  416.   }
  417.   /**
  418.    * Returns the tip text for this property
  419.    *
  420.    * @return tip text for this property suitable for
  421.    * displaying in the explorer/experimenter gui
  422.    */
  423.   public String useBetterEncodingTipText() {
  424.     return "Uses a different split point encoding. Who says it's better?"
  425.       + " (Eibe fix this).";
  426.   }
  427.   /**
  428.    * Gets whether better encoding is to be used for MDL.
  429.    *
  430.    * @return true if the better MDL encoding will be used
  431.    */
  432.   public boolean getUseBetterEncoding() {
  433.     return m_UseBetterEncoding;
  434.   }
  435.   /** 
  436.    * Sets whether better encoding is to be used for MDL.
  437.    *
  438.    * @param useBetterEncoding true if better encoding to be used.
  439.    */
  440.   public void setUseBetterEncoding(boolean useBetterEncoding) {
  441.     m_UseMDL = true;
  442.     m_UseBetterEncoding = useBetterEncoding;
  443.   }
  444.   /**
  445.    * Returns the tip text for this property
  446.    *
  447.    * @return tip text for this property suitable for
  448.    * displaying in the explorer/experimenter gui
  449.    */
  450.   public String binsTipText() {
  451.     return "Number of bins for class-blind discretisation. This"
  452.       + " setting is ignored if MDL-based discretisation is used.";
  453.   }
  454.   /**
  455.    * Gets the number of bins numeric attributes will be divided into
  456.    *
  457.    * @return the number of bins.
  458.    */
  459.   public int getBins() {
  460.     return m_NumBins;
  461.   }
  462.   /**
  463.    * Sets the number of bins to divide each selected numeric attribute into
  464.    *
  465.    * @param numBins the number of bins
  466.    */
  467.   public void setBins(int numBins) {
  468.     m_UseMDL = false;
  469.     m_NumBins = numBins;
  470.   }
  471.   /**
  472.    * Returns the tip text for this property
  473.    *
  474.    * @return tip text for this property suitable for
  475.    * displaying in the explorer/experimenter gui
  476.    */
  477.   public String invertSelectionTipText() {
  478.     return "Set attribute selection mode. If false, only selected"
  479.       + " (numeric) attributes in the range will be discretized; if"
  480.       + " true, only non-selected attributes will be discretized.";
  481.   }
  482.   /**
  483.    * Gets whether the supplied columns are to be removed or kept
  484.    *
  485.    * @return true if the supplied columns will be kept
  486.    */
  487.   public boolean getInvertSelection() {
  488.     return m_DiscretizeCols.getInvert();
  489.   }
  490.   /**
  491.    * Sets whether selected columns should be removed or kept. If true the 
  492.    * selected columns are kept and unselected columns are deleted. If false
  493.    * selected columns are deleted and unselected columns are kept.
  494.    *
  495.    * @param invert the new invert setting
  496.    */
  497.   public void setInvertSelection(boolean invert) {
  498.     m_DiscretizeCols.setInvert(invert);
  499.   }
  500.   /**
  501.    * Returns the tip text for this property
  502.    *
  503.    * @return tip text for this property suitable for
  504.    * displaying in the explorer/experimenter gui
  505.    */
  506.   public String attributeIndicesTipText() {
  507.     return "Specify range of attributes to act on."
  508.       + " This is a comma separated list of attribute indices, with"
  509.       + " "first" and "last" valid values. Specify an inclusive"
  510.       + " range with "-". E.g: "first-3,5,6-10,last".";
  511.   }
  512.   /**
  513.    * Gets the current range selection
  514.    *
  515.    * @return a string containing a comma separated list of ranges
  516.    */
  517.   public String getAttributeIndices() {
  518.     return m_DiscretizeCols.getRanges();
  519.   }
  520.   /**
  521.    * Sets which attributes are to be Discretized (only numeric
  522.    * attributes among the selection will be Discretized).
  523.    *
  524.    * @param rangeList a string representing the list of attributes. Since
  525.    * the string will typically come from a user, attributes are indexed from
  526.    * 1. <br>
  527.    * eg: first-3,5,6-last
  528.    * @exception IllegalArgumentException if an invalid range list is supplied 
  529.    */
  530.   public void setAttributeIndices(String rangeList) {
  531.     m_DiscretizeCols.setRanges(rangeList);
  532.   }
  533.   /**
  534.    * Sets which attributes are to be Discretized (only numeric
  535.    * attributes among the selection will be Discretized).
  536.    *
  537.    * @param attributes an array containing indexes of attributes to Discretize.
  538.    * Since the array will typically come from a program, attributes are indexed
  539.    * from 0.
  540.    * @exception IllegalArgumentException if an invalid set of ranges
  541.    * is supplied 
  542.    */
  543.   public void setAttributeIndicesArray(int [] attributes) {
  544.     setAttributeIndices(Range.indicesToRangeList(attributes));
  545.   }
  546.   /**
  547.    * Gets the cut points for an attribute
  548.    *
  549.    * @param the index (from 0) of the attribute to get the cut points of
  550.    * @return an array containing the cutpoints (or null if the
  551.    * attribute requested isn't being Discretized
  552.    */
  553.   public double [] getCutPoints(int attributeIndex) {
  554.     if (m_CutPoints == null) {
  555.       return null;
  556.     }
  557.     return m_CutPoints[attributeIndex];
  558.   }
  559.   /** Generate the cutpoints for each attribute */
  560.   protected void calculateCutPoints() {
  561.     Instances copy = null;
  562.     m_CutPoints = new double [getInputFormat().numAttributes()] [];
  563.     for(int i = getInputFormat().numAttributes() - 1; i >= 0; i--) {
  564.       if ((m_DiscretizeCols.isInRange(i)) && 
  565.   (getInputFormat().attribute(i).isNumeric())) {
  566. if (m_UseMDL) {
  567.   // Use copy to preserve order
  568.   if (copy == null) {
  569.     copy = new Instances(getInputFormat());
  570.   }
  571.   calculateCutPointsByMDL(i, copy);
  572. } else {
  573.   if (m_FindNumBins) {
  574.     findNumBins(i);
  575.   } else {
  576.     calculateCutPointsByBinning(i);
  577.   }
  578. }
  579.       }
  580.     }
  581.   }
  582.   /**
  583.    * Set cutpoints for a single attribute using MDL.
  584.    *
  585.    * @param index the index of the attribute to set cutpoints for
  586.    */
  587.   protected void calculateCutPointsByMDL(int index,
  588.  Instances data) {
  589.     // Sort instances
  590.     data.sort(data.attribute(index));
  591.     // Find first instances that's missing
  592.     int firstMissing = data.numInstances();
  593.     for (int i = 0; i < data.numInstances(); i++) {
  594.       if (data.instance(i).isMissing(index)) {
  595.         firstMissing = i;
  596.         break;
  597.       }
  598.     }
  599.     m_CutPoints[index] = cutPointsForSubset(data, index, 0, firstMissing);
  600.   }
  601.   /** Test using Kononenko's MDL criterion. */
  602.   private boolean KononenkosMDL(double[] priorCounts,
  603. double[][] bestCounts,
  604. double numInstances,
  605. int numCutPoints) {
  606.     double distPrior, instPrior, distAfter = 0, sum, instAfter = 0;
  607.     double before, after;
  608.     int numClassesTotal;
  609.     // Number of classes occuring in the set
  610.     numClassesTotal = 0;
  611.     for (int i = 0; i < priorCounts.length; i++) {
  612.       if (priorCounts[i] > 0) {
  613. numClassesTotal++;
  614.       }
  615.     }
  616.     // Encode distribution prior to split
  617.     distPrior = SpecialFunctions.log2Binomial(numInstances 
  618.       + numClassesTotal - 1,
  619.       numClassesTotal - 1);
  620.     // Encode instances prior to split.
  621.     instPrior = SpecialFunctions.log2Multinomial(numInstances,
  622.  priorCounts);
  623.     before = instPrior + distPrior;
  624.     // Encode distributions and instances after split.
  625.     for (int i = 0; i < bestCounts.length; i++) {
  626.       sum = Utils.sum(bestCounts[i]);
  627.       distAfter += SpecialFunctions.log2Binomial(sum + numClassesTotal - 1,
  628.  numClassesTotal - 1);
  629.       instAfter += SpecialFunctions.log2Multinomial(sum,
  630.     bestCounts[i]);
  631.     }
  632.     // Coding cost after split
  633.     after = Utils.log2(numCutPoints) + distAfter + instAfter;
  634.     // Check if split is to be accepted
  635.     return (Utils.gr(before, after));
  636.   }
  637.   /** Test using Fayyad and Irani's MDL criterion. */
  638.   private boolean FayyadAndIranisMDL(double[] priorCounts,
  639.      double[][] bestCounts,
  640.      double numInstances,
  641.      int numCutPoints) {
  642.     double priorEntropy, entropy, gain; 
  643.     double entropyLeft, entropyRight, delta;
  644.     int numClassesTotal, numClassesRight, numClassesLeft;
  645.     // Compute entropy before split.
  646.     priorEntropy = ContingencyTables.entropy(priorCounts);
  647.     // Compute entropy after split.
  648.     entropy = ContingencyTables.entropyConditionedOnRows(bestCounts);
  649.     // Compute information gain.
  650.     gain = priorEntropy - entropy;
  651.     // Number of classes occuring in the set
  652.     numClassesTotal = 0;
  653.     for (int i = 0; i < priorCounts.length; i++) {
  654.       if (priorCounts[i] > 0) {
  655. numClassesTotal++;
  656.       }
  657.     }
  658.     // Number of classes occuring in the left subset
  659.     numClassesLeft = 0;
  660.     for (int i = 0; i < bestCounts[0].length; i++) {
  661.       if (bestCounts[0][i] > 0) {
  662. numClassesLeft++;
  663.       }
  664.     }
  665.     // Number of classes occuring in the right subset
  666.     numClassesRight = 0;
  667.     for (int i = 0; i < bestCounts[1].length; i++) {
  668.       if (bestCounts[1][i] > 0) {
  669. numClassesRight++;
  670.       }
  671.     }
  672.     // Entropy of the left and the right subsets
  673.     entropyLeft = ContingencyTables.entropy(bestCounts[0]);
  674.     entropyRight = ContingencyTables.entropy(bestCounts[1]);
  675.     // Compute terms for MDL formula
  676.     delta = Utils.log2(Math.pow(3, numClassesTotal) - 2) - 
  677.       (((double) numClassesTotal * priorEntropy) - 
  678.        (numClassesRight * entropyRight) - 
  679.        (numClassesLeft * entropyLeft));
  680.     // Check if split is to be accepted
  681.     return (Utils.gr(gain, (Utils.log2(numCutPoints) + delta) /
  682.      (double)numInstances));
  683.   }
  684.     
  685.   /** Selects cutpoints for sorted subset. */
  686.   private double[] cutPointsForSubset(Instances instances, int attIndex, 
  687.       int first, int lastPlusOne) { 
  688.     double[][] counts, bestCounts;
  689.     double[] priorCounts, left, right, cutPoints;
  690.     double currentCutPoint = -Double.MAX_VALUE, bestCutPoint = -1, 
  691.       currentEntropy, bestEntropy, priorEntropy, gain;
  692.     int bestIndex = -1, numInstances = 0, numCutPoints = 0;
  693.     // Compute number of instances in set
  694.     if ((lastPlusOne - first) < 2) {
  695.       return null;
  696.     }
  697.     // Compute class counts.
  698.     counts = new double[2][instances.numClasses()];
  699.     for (int i = first; i < lastPlusOne; i++) {
  700.       numInstances += instances.instance(i).weight();
  701.       counts[1][(int)instances.instance(i).classValue()] +=
  702. instances.instance(i).weight();
  703.     }
  704.     // Save prior counts
  705.     priorCounts = new double[instances.numClasses()];
  706.     System.arraycopy(counts[1], 0, priorCounts, 0, 
  707.      instances.numClasses());
  708.     // Entropy of the full set
  709.     priorEntropy = ContingencyTables.entropy(priorCounts);
  710.     bestEntropy = priorEntropy;
  711.     
  712.     // Find best entropy.
  713.     bestCounts = new double[2][instances.numClasses()];
  714.     for (int i = first; i < (lastPlusOne - 1); i++) {
  715.       counts[0][(int)instances.instance(i).classValue()] +=
  716. instances.instance(i).weight();
  717.       counts[1][(int)instances.instance(i).classValue()] -=
  718. instances.instance(i).weight();
  719.       if (Utils.sm(instances.instance(i).value(attIndex), 
  720.    instances.instance(i + 1).value(attIndex))) {
  721. currentCutPoint = instances.instance(i).value(attIndex); //+ 
  722. //instances.instance(i + 1).value(attIndex)) / 2.0;
  723. currentEntropy = ContingencyTables.entropyConditionedOnRows(counts);
  724. if (Utils.sm(currentEntropy, bestEntropy)) {
  725.   bestCutPoint = currentCutPoint;
  726.   bestEntropy = currentEntropy;
  727.   bestIndex = i;
  728.   System.arraycopy(counts[0], 0, 
  729.    bestCounts[0], 0, instances.numClasses());
  730.   System.arraycopy(counts[1], 0, 
  731.    bestCounts[1], 0, instances.numClasses()); 
  732. }
  733. numCutPoints++;
  734.       }
  735.     }
  736.     // Use worse encoding?
  737.     if (!m_UseBetterEncoding) {
  738.       numCutPoints = (lastPlusOne - first) - 1;
  739.     }
  740.     // Checks if gain is zero
  741.     gain = priorEntropy - bestEntropy;
  742.     if (Utils.eq(gain, 0)) {
  743.       return null;
  744.     }
  745.     // Check if split is to be accepted
  746.     if ((m_UseKononenko && KononenkosMDL(priorCounts, bestCounts,
  747.  numInstances, numCutPoints)) ||
  748. (!m_UseKononenko && FayyadAndIranisMDL(priorCounts, bestCounts,
  749.        numInstances, numCutPoints))) {
  750.       
  751.       // Select split points for the left and right subsets
  752.       left = cutPointsForSubset(instances, attIndex, first, bestIndex + 1);
  753.       right = cutPointsForSubset(instances, attIndex, 
  754.  bestIndex + 1, lastPlusOne);
  755.       
  756.       // Merge cutpoints and return them
  757.       if ((left == null) && (right) == null) {
  758. cutPoints = new double[1];
  759. cutPoints[0] = bestCutPoint;
  760.       } else if (right == null) {
  761. cutPoints = new double[left.length + 1];
  762. System.arraycopy(left, 0, cutPoints, 0, left.length);
  763. cutPoints[left.length] = bestCutPoint;
  764.       } else if (left == null) {
  765. cutPoints = new double[1 + right.length];
  766. cutPoints[0] = bestCutPoint;
  767. System.arraycopy(right, 0, cutPoints, 1, right.length);
  768.       } else {
  769. cutPoints =  new double[left.length + right.length + 1];
  770. cutPoints = new double[left.length + right.length + 1];
  771. System.arraycopy(left, 0, cutPoints, 0, left.length);
  772. cutPoints[left.length] = bestCutPoint;
  773. System.arraycopy(right, 0, cutPoints, left.length + 1, right.length);
  774.       }
  775.       
  776.       return cutPoints;
  777.     } else
  778.       return null;
  779.   }
  780.  
  781.   /**
  782.    * Set cutpoints for a single attribute.
  783.    *
  784.    * @param index the index of the attribute to set cutpoints for
  785.    */
  786.   protected void calculateCutPointsByBinning(int index) {
  787.     // Scan for max and min values
  788.     double max = 0, min = 1, currentVal;
  789.     Instance currentInstance;
  790.     for(int i = 0; i < getInputFormat().numInstances(); i++) {
  791.       currentInstance = getInputFormat().instance(i);
  792.       if (!currentInstance.isMissing(index)) {
  793. currentVal = currentInstance.value(index);
  794. if (max < min) {
  795.   max = min = currentVal;
  796. }
  797. if (currentVal > max) {
  798.   max = currentVal;
  799. }
  800. if (currentVal < min) {
  801.   min = currentVal;
  802. }
  803.       }
  804.     }
  805.     double binWidth = (max - min) / m_NumBins;
  806.     double [] cutPoints = null;
  807.     if ((m_NumBins > 1) && (binWidth > 0)) {
  808.       cutPoints = new double [m_NumBins - 1];
  809.       for(int i = 1; i < m_NumBins; i++) {
  810. cutPoints[i - 1] = min + binWidth * i;
  811.       }
  812.     }
  813.     m_CutPoints[index] = cutPoints;
  814.   }
  815.   /**
  816.    * Optimizes the number of bins using leave-one-out cross-validation.
  817.    *
  818.    * @param index the attribute index
  819.    */
  820.   protected void findNumBins(int index) {
  821.     double min = Double.MAX_VALUE, max = -Double.MIN_VALUE, binWidth = 0, 
  822.       entropy, bestEntropy = Double.MAX_VALUE, currentVal;
  823.     double[] distribution;
  824.     int bestNumBins  = 1;
  825.     Instance currentInstance;
  826.     // Find minimum and maximum
  827.     for (int i = 0; i < getInputFormat().numInstances(); i++) {
  828.       currentInstance = getInputFormat().instance(i);
  829.       if (!currentInstance.isMissing(index)) {
  830. currentVal = currentInstance.value(index);
  831. if (currentVal > max) {
  832.   max = currentVal;
  833. }
  834. if (currentVal < min) {
  835.   min = currentVal;
  836. }
  837.       }
  838.     }
  839.     // Find best number of bins
  840.     for (int i = 0; i < m_NumBins; i++) {
  841.       distribution = new double[i + 1];
  842.       binWidth = (max - min) / (i + 1);
  843.       // Compute distribution
  844.       for (int j = 0; j < getInputFormat().numInstances(); j++) {
  845. currentInstance = getInputFormat().instance(j);
  846. if (!currentInstance.isMissing(index)) {
  847.   for (int k = 0; k < i + 1; k++) {
  848.     if (currentInstance.value(index) <= 
  849. (min + (((double)k + 1) * binWidth))) {
  850.       distribution[k] += currentInstance.weight();
  851.       break;
  852.     }
  853.   }
  854. }
  855.       }
  856.       // Compute cross-validated entropy
  857.       entropy = 0;
  858.       for (int k = 0; k < i + 1; k++) {
  859. if (distribution[k] < 2) {
  860.   entropy = Double.MAX_VALUE;
  861.   break;
  862. }
  863. entropy -= distribution[k] * Math.log((distribution[k] - 1) / 
  864.       binWidth);
  865.       }
  866.       // Best entropy so far?
  867.       if (entropy < bestEntropy) {
  868. bestEntropy = entropy;
  869. bestNumBins = i + 1;
  870.       }
  871.     }
  872.     // Compute cut points
  873.     double [] cutPoints = null;
  874.     if ((bestNumBins > 1) && (binWidth > 0)) {
  875.       cutPoints = new double [bestNumBins - 1];
  876.       for(int i = 1; i < bestNumBins; i++) {
  877. cutPoints[i - 1] = min + binWidth * i;
  878.       }
  879.     }
  880.     m_CutPoints[index] = cutPoints;
  881.   }
  882.   /**
  883.    * Set the output format. Takes the currently defined cutpoints and 
  884.    * m_InputFormat and calls setOutputFormat(Instances) appropriately.
  885.    */
  886.   protected void setOutputFormat() {
  887.     if (m_CutPoints == null) {
  888.       setOutputFormat(null);
  889.       return;
  890.     }
  891.     FastVector attributes = new FastVector(getInputFormat().numAttributes());
  892.     int classIndex = getInputFormat().classIndex();
  893.     for(int i = 0; i < getInputFormat().numAttributes(); i++) {
  894.       if ((m_DiscretizeCols.isInRange(i)) 
  895.   && (getInputFormat().attribute(i).isNumeric())) {
  896. if (!m_MakeBinary) {
  897.   FastVector attribValues = new FastVector(1);
  898.   if (m_CutPoints[i] == null) {
  899.     attribValues.addElement("'All'");
  900.   } else {
  901.     for(int j = 0; j <= m_CutPoints[i].length; j++) {
  902.       if (j == 0) {
  903. attribValues.addElement("'(-inf-"
  904. + Utils.doubleToString(m_CutPoints[i][j], 6) + "]'");
  905.       } else if (j == m_CutPoints[i].length) {
  906. attribValues.addElement("'("
  907. + Utils.doubleToString(m_CutPoints[i][j - 1], 6) 
  908. + "-inf)'");
  909.       } else {
  910. attribValues.addElement("'("
  911. + Utils.doubleToString(m_CutPoints[i][j - 1], 6) + "-"
  912. + Utils.doubleToString(m_CutPoints[i][j], 6) + "]'");
  913.       }
  914.     }
  915.   }
  916.   attributes.addElement(new Attribute(getInputFormat().
  917.       attribute(i).name(),
  918.       attribValues));
  919. } else {
  920.   if (m_CutPoints[i] == null) {
  921.     FastVector attribValues = new FastVector(1);
  922.     attribValues.addElement("'All'");
  923.     attributes.addElement(new Attribute(getInputFormat().
  924. attribute(i).name(),
  925. attribValues));
  926.   } else {
  927.     if (i < getInputFormat().classIndex()) {
  928.       classIndex += m_CutPoints[i].length - 1;
  929.     }
  930.     for(int j = 0; j < m_CutPoints[i].length; j++) {
  931.       FastVector attribValues = new FastVector(2);
  932.       attribValues.addElement("'(-inf-"
  933.       + Utils.doubleToString(m_CutPoints[i][j], 6) + "]'");
  934.       attribValues.addElement("'("
  935.       + Utils.doubleToString(m_CutPoints[i][j], 6) + "-inf)'");
  936.       attributes.addElement(new Attribute(getInputFormat().
  937.   attribute(i).name(),
  938.   attribValues));
  939.     }
  940.   }
  941. }
  942.       } else {
  943. attributes.addElement(getInputFormat().attribute(i).copy());
  944.       }
  945.     }
  946.     Instances outputFormat = 
  947.       new Instances(getInputFormat().relationName(), attributes, 0);
  948.     outputFormat.setClassIndex(classIndex);
  949.     setOutputFormat(outputFormat);
  950.   }
  951.   /**
  952.    * Convert a single instance over. The converted instance is added to 
  953.    * the end of the output queue.
  954.    *
  955.    * @param instance the instance to convert
  956.    */
  957.   protected void convertInstance(Instance instance) {
  958.     int index = 0;
  959.     double [] vals = new double [outputFormatPeek().numAttributes()];
  960.     // Copy and convert the values
  961.     for(int i = 0; i < getInputFormat().numAttributes(); i++) {
  962.       if (m_DiscretizeCols.isInRange(i) && 
  963.   getInputFormat().attribute(i).isNumeric()) {
  964. int j;
  965. double currentVal = instance.value(i);
  966. if (m_CutPoints[i] == null) {
  967.   if (instance.isMissing(i)) {
  968.     vals[index] = Instance.missingValue();
  969.   } else {
  970.     vals[index] = 0;
  971.   }
  972.   index++;
  973. } else {
  974.   if (!m_MakeBinary) {
  975.     if (instance.isMissing(i)) {
  976.       vals[index] = Instance.missingValue();
  977.     } else {
  978.       for (j = 0; j < m_CutPoints[i].length; j++) {
  979. if (currentVal <= m_CutPoints[i][j]) {
  980.   break;
  981. }
  982.       }
  983.               vals[index] = j;
  984.     }
  985.     index++;
  986.   } else {
  987.     for (j = 0; j < m_CutPoints[i].length; j++) {
  988.       if (instance.isMissing(i)) {
  989.                 vals[index] = Instance.missingValue();
  990.       } else if (currentVal <= m_CutPoints[i][j]) {
  991.                 vals[index] = 0;
  992.       } else {
  993.                 vals[index] = 1;
  994.       }
  995.       index++;
  996.     }
  997.   }   
  998. }
  999.       } else {
  1000.         vals[index] = instance.value(i);
  1001. index++;
  1002.       }
  1003.     }
  1004.     
  1005.     Instance inst = null;
  1006.     if (instance instanceof SparseInstance) {
  1007.       inst = new SparseInstance(instance.weight(), vals);
  1008.     } else {
  1009.       inst = new Instance(instance.weight(), vals);
  1010.     }
  1011.     copyStringValues(inst, false, instance.dataset(), getInputStringIndex(),
  1012.                      getOutputFormat(), getOutputStringIndex());
  1013.     inst.setDataset(getOutputFormat());
  1014.     push(inst);
  1015.   }
  1016.   /**
  1017.    * Main method for testing this class.
  1018.    *
  1019.    * @param argv should contain arguments to the filter: use -h for help
  1020.    */
  1021.   public static void main(String [] argv) {
  1022.     try {
  1023.       if (Utils.getFlag('b', argv)) {
  1024.   Filter.batchFilterFile(new DiscretizeFilter(), argv);
  1025.       } else {
  1026. Filter.filterFile(new DiscretizeFilter(), argv);
  1027.       }
  1028.     } catch (Exception ex) {
  1029.       System.out.println(ex.getMessage());
  1030.     }
  1031.   }
  1032. }