RuleStats.java
Upload User: rhdiban
Upload Date: 2013-08-09
Package Size: 15085k
Code Size: 29k
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.  *    RuleStats.java
  18.  *    Copyright (C) 2001 Xin Xu
  19.  */
  20. package weka.classifiers.rules;
  21. import java.util.Enumeration;
  22. import java.util.Random;
  23. import weka.core.FastVector;
  24. import weka.core.Instances;
  25. import weka.core.Instance;
  26. import weka.core.Attribute;
  27. import weka.core.AttributeStats;
  28. import weka.core.Utils;
  29. /**
  30.  * This class implements the statistics functions used in the 
  31.  * propositional rule learner, from the simpler ones like count of
  32.  * true/false positive/negatives, filter data based on the ruleset, etc.
  33.  * to the more sophisticated ones such as MDL calculation and rule
  34.  * variants generation for each rule in the ruleset. <p>
  35.  *
  36.  * Obviously the statistics functions listed above need the specific
  37.  * data and the specific ruleset, which are given in order to instantiate
  38.  * an object of this class. <p>
  39.  *  
  40.  * @author Xin Xu (xx5@cs.waikato.ac.nz)
  41.  * @version $Revision: 1.2 $
  42.  */
  43. public class RuleStats{
  44.   /** The data on which the stats calculation is based */
  45.   private Instances m_Data;
  46.   /** The specific ruleset in question */
  47.   private FastVector m_Ruleset;
  48.   /** The simple stats of each rule */
  49.   private FastVector m_SimpleStats;
  50.   /** The set of instances filtered by the ruleset */
  51.   private FastVector m_Filtered;
  52.     
  53.   /** The total number of possible conditions that could
  54.    *  appear in a rule */
  55.   private double m_Total;
  56.  
  57.   /** The redundancy factor in theory description length */
  58.   private static double REDUNDANCY_FACTOR = 0.5;
  59.     
  60.   /** The theory weight in the MDL calculation */
  61.   private double MDL_THEORY_WEIGHT = 1.0;
  62.     /** The class distributions predicted by each rule */
  63.     private FastVector m_Distributions;
  64.   /** Default constructor */
  65.   public RuleStats(){
  66.     m_Data = null;
  67.     m_Ruleset = null;
  68.     m_SimpleStats = null;
  69.     m_Filtered = null;
  70.     m_Distributions = null;
  71.     m_Total = -1;
  72.   }
  73.     
  74.   /** 
  75.    * Constructor that provides ruleset and data 
  76.    *
  77.    * @param data the data
  78.    * @param rules the ruleset
  79.    */
  80.   public RuleStats(Instances data, FastVector rules){
  81.     this();
  82.     m_Data = data;
  83.     m_Ruleset = rules;
  84.   }
  85.   /**
  86.    * Set the number of all conditions that could appear 
  87.    * in a rule in this RuleStats object, if the number set
  88.    * is smaller than 0 (typically -1), then it calcualtes
  89.    * based on the data store 
  90.    *
  91.    * @param total the set number
  92.    */
  93.   public void setNumAllConds(double total){
  94.     if(total < 0)
  95.       m_Total = numAllConditions(m_Data);    
  96.     else
  97.       m_Total = total;
  98.   }
  99.     
  100.   /** 
  101.    * Set the data of the stats, overwriting the old one if any 
  102.    *
  103.    * @param data the data to be set
  104.    */
  105.   public void setData(Instances data){
  106.     m_Data = data;
  107.   }
  108.     
  109.   /** 
  110.    * Get the data of the stats 
  111.    *
  112.    * @return the data 
  113.    */
  114.   public Instances getData(){
  115.     return m_Data;
  116.   }
  117.     
  118.   /** 
  119.    * Set the ruleset of the stats, overwriting the old one if any 
  120.    *      
  121.    * @param rules the set of rules to be set
  122.    */
  123.   public void setRuleset(FastVector rules){
  124.     m_Ruleset = rules;
  125.   }
  126.     
  127.   /** 
  128.    * Get the ruleset of the stats 
  129.    *      
  130.    * @return the set of rules 
  131.    */
  132.   public FastVector getRuleset(){
  133.     return m_Ruleset;
  134.   }
  135.   /** 
  136.    * Get the size of the ruleset in the stats 
  137.    *      
  138.    * @return the size of ruleset 
  139.    */
  140.   public int getRulesetSize(){
  141.     return m_Ruleset.size();
  142.   }
  143.   
  144.   /** 
  145.    * Get the simple stats of one rule, including 6 parameters:
  146.    * 0: coverage; 1:uncoverage; 2: true positive; 3: true negatives;
  147.    * 4: false positives; 5: false negatives
  148.    *
  149.    * @param index the index of the rule
  150.    * @return the stats
  151.    */
  152.   public double[] getSimpleStats(int index){
  153.     if((m_SimpleStats != null) && (index < m_SimpleStats.size()))
  154.       return (double[])m_SimpleStats.elementAt(index);
  155.     return null;
  156.   }    
  157.   /**
  158.    * Get the data after filtering the given rule
  159.    *
  160.    * @param index the index of the rule
  161.    * @return the data covered and uncovered by the rule
  162.    */
  163.   public Instances[] getFiltered(int index){
  164.     if((m_Filtered != null) && (index < m_Filtered.size()))
  165.       return (Instances[])m_Filtered.elementAt(index);
  166.     return null;
  167.   }    
  168.     /**
  169.      * Get the class distribution predicted by the rule in
  170.      * given position
  171.      *
  172.      * @param index the position index of the rule
  173.      * @return the class distributions
  174.      */
  175.     public double[] getDistributions(int index){
  176. if((m_Distributions != null) && (index < m_Distributions.size()))
  177.     return (double[])m_Distributions.elementAt(index);
  178. return null;
  179.     }    
  180.     
  181.   /**
  182.    * Set the weight of theory in MDL calcualtion
  183.    *
  184.    * @param weight the weight to be set
  185.    */
  186.   public void setMDLTheoryWeight(double weight){
  187.     MDL_THEORY_WEIGHT = weight;
  188.   } 
  189.   /**
  190.    * Compute the number of all possible conditions that could 
  191.    * appear in a rule of a given data.  For nominal attributes,
  192.    * it's the number of values that could appear; for numeric 
  193.    * attributes, it's the number of values * 2, i.e. <= and >=
  194.    * are counted as different possible conditions.
  195.    *
  196.    * @param data the given data
  197.    * @return number of all conditions of the data
  198.    */
  199.   public static double numAllConditions(Instances data){
  200.     double total = 0;
  201.     Enumeration attEnum = data.enumerateAttributes();
  202.     while(attEnum.hasMoreElements()){
  203.       Attribute att= (Attribute)attEnum.nextElement();
  204.       if(att.isNominal())
  205. total += (double)att.numValues();
  206.       else
  207. total += 2.0 * (double)data.numDistinctValues(att);
  208.     }
  209.     return total;
  210.   }
  211.   /**
  212.    * Filter the data according to the ruleset and compute the basic
  213.    * stats: coverage/uncoverage, true/false positive/negatives of 
  214.    * each rule
  215.    */
  216.   public void countData(){
  217.     if((m_Filtered != null) ||
  218.        (m_Ruleset == null)  ||
  219.        (m_Data == null))
  220.       return;
  221.     int size = m_Ruleset.size();
  222.     m_Filtered = new FastVector(size);
  223.     m_SimpleStats = new FastVector(size);
  224.     m_Distributions = new FastVector(size);
  225.     Instances data = new Instances(m_Data);
  226.     for(int i=0; i < size; i++){
  227.       double[] stats = new double[6];  // 6 statistics parameters
  228.       double[] classCounts = new double[m_Data.classAttribute().numValues()];
  229.       Instances[] filtered = computeSimpleStats(i, data, stats, classCounts);
  230.       m_Filtered.addElement(filtered);
  231.       m_SimpleStats.addElement(stats);
  232.       m_Distributions.addElement(classCounts);
  233.       data = filtered[1];  // Data not covered
  234.     }
  235.   }
  236.     
  237.     /**
  238.      * Count data from the position index in the ruleset
  239.      * assuming that given data are not covered by the rules
  240.      * in position 0...(index-1), and the statistics of these
  241.      * rules are provided.<br>
  242.      * This procedure is typically useful when a temporary 
  243.      * object of RuleStats is constructed in order to efficiently
  244.      * calculate the relative DL of rule in position index, 
  245.      * thus all other stuff is not needed.
  246.      *
  247.      * @param index the given position
  248.      * @param uncovered the data not covered by rules before index
  249.      * @param prevRuleStats the provided stats of previous rules
  250.      */
  251.     public void countData(int index, Instances uncovered, 
  252.   double[][] prevRuleStats){
  253. if((m_Filtered != null) ||
  254.    (m_Ruleset == null))
  255.     return;
  256. int size = m_Ruleset.size();
  257. m_Filtered = new FastVector(size);
  258. m_SimpleStats = new FastVector(size);
  259. Instances[] data = new Instances[2];
  260. data[1] = uncovered;
  261. for(int i=0; i < index; i++){
  262.     m_SimpleStats.addElement(prevRuleStats[i]);
  263.     if(i+1 == index)
  264. m_Filtered.addElement(data);
  265.     else
  266. m_Filtered.addElement(new Object()); // Stuff sth.
  267. }
  268. for(int j=index; j < size; j++){
  269.     double[] stats = new double[6];  // 6 statistics parameters
  270.     Instances[] filtered = computeSimpleStats(j, data[1], stats, null);
  271.     m_Filtered.addElement(filtered);
  272.     m_SimpleStats.addElement(stats);
  273.     data = filtered;  // Data not covered
  274. }
  275.     }
  276.     
  277.   /**
  278.    * Find all the instances in the dataset covered/not covered by 
  279.    * the rule in given index, and the correponding simple statistics
  280.    * and predicted class distributions are stored in the given double array,
  281.    * which can be obtained by getSimpleStats() and getDistributions().<br>
  282.    * 
  283.    * @param index the given index, assuming correct
  284.    * @param insts the dataset to be covered by the rule
  285.    * @param stats the given double array to hold stats, side-effected
  286.    * @param dist the given array to hold class distributions, side-effected
  287.    *             if null, the distribution is not necessary 
  288.    * @return the instances covered and not covered by the rule
  289.    */
  290.   private Instances[] computeSimpleStats(int index, Instances insts, 
  291.  double[] stats, double[] dist){
  292.     Rule rule = (Rule)m_Ruleset.elementAt(index);
  293.     Instances[] data = new Instances[2];
  294.     data[0] = new Instances(insts, insts.numInstances());
  295.     data[1] = new Instances(insts, insts.numInstances());
  296.     for(int i=0; i<insts.numInstances(); i++){
  297.       Instance datum = insts.instance(i);
  298.       double weight = datum.weight();
  299.       if(rule.covers(datum)){
  300. data[0].add(datum);        // Covered by this rule
  301. stats[0] += weight;        // Coverage
  302. if((int)datum.classValue() == (int)rule.getConsequent())
  303.   stats[2] += weight;    // True positives
  304. else
  305.   stats[4] += weight;    // False positives
  306. if(dist != null)
  307.     dist[(int)datum.classValue()] += weight;
  308.       }
  309.       else{
  310. data[1].add(datum);        // Not covered by this rule
  311. stats[1] += weight; 
  312. if((int)datum.classValue() != (int)rule.getConsequent())
  313.   stats[3] += weight;    // True negatives
  314. else
  315.   stats[5] += weight;    // False negatives     
  316.       }
  317.     }
  318.     
  319.     return data;
  320.   }
  321.     
  322.     
  323.   /** 
  324.    * Add a rule to the ruleset and update the stats
  325.    *
  326.    * @param the rule to be added
  327.    */
  328.   public void addAndUpdate(Rule lastRule){
  329.     if(m_Ruleset == null)
  330.       m_Ruleset = new FastVector();
  331.     m_Ruleset.addElement(lastRule);
  332.   
  333.     Instances data = (m_Filtered == null) ?
  334.       m_Data : ((Instances[])m_Filtered.lastElement())[1];
  335.     double[] stats = new double[6];
  336.     double[] classCounts = new double[m_Data.classAttribute().numValues()];
  337.     Instances[] filtered = 
  338. computeSimpleStats(m_Ruleset.size()-1, data, stats, classCounts);
  339.     
  340.     if(m_Filtered == null)
  341. m_Filtered = new FastVector();
  342.     m_Filtered.addElement(filtered);
  343.     if(m_SimpleStats == null)
  344.       m_SimpleStats = new FastVector();
  345.     m_SimpleStats.addElement(stats);
  346.     if(m_Distributions == null)
  347. m_Distributions = new FastVector();
  348.     m_Distributions.addElement(classCounts);
  349.   }
  350.     
  351.     
  352.   /**
  353.    * Subset description length: <br>
  354.    * S(t,k,p) = -k*log2(p)-(n-k)log2(1-p)
  355.    *
  356.    * Details see Quilan: "MDL and categorical theories (Continued)",ML95
  357.    *
  358.    * @param t the number of elements in a known set
  359.    * @param k the number of elements in a subset
  360.    * @param p the expected proportion of subset known by recipient
  361.    */
  362.   public static double subsetDL(double t, double k, double p){
  363.     double rt = Utils.gr(p, 0.0) ? (- k*Utils.log2(p)) : 0.0;
  364.     rt -= (t-k)*Utils.log2(1-p);
  365.     return rt;
  366.   }
  367.     
  368.     
  369.   /** 
  370.    * The description length of the theory for a given rule.  Computed as:<br>
  371.    *                 0.5* [||k||+ S(t, k, k/t)]<br>
  372.    * where k is the number of antecedents of the rule; t is the total
  373.    * possible antecedents that could appear in a rule; ||K|| is the 
  374.    * universal prior for k , log2*(k) and S(t,k,p) = -k*log2(p)-(n-k)log2(1-p)
  375.    * is the subset encoding length.<p>
  376.    *
  377.    * Details see Quilan: "MDL and categorical theories (Continued)",ML95
  378.    *
  379.    * @param index the index of the given rule (assuming correct)
  380.    * @exception if index out of range or object not initialized yet
  381.    * @return the theory DL, weighted if weight != 1.0
  382.    */
  383.   public double theoryDL(int index){
  384.     double k = ((Rule)m_Ruleset.elementAt(index)).size();
  385.     if(k == 0)
  386.       return 0.0;
  387.     double tdl = Utils.log2(k);                    
  388.     if(k > 1)                           // Approximation
  389.       tdl += 2.0 * Utils.log2(tdl);   // of log2 star
  390.     tdl += subsetDL(m_Total, k, k/m_Total);
  391.     //System.out.println("!!!theory: "+MDL_THEORY_WEIGHT * REDUNDANCY_FACTOR * tdl);
  392.     return MDL_THEORY_WEIGHT * REDUNDANCY_FACTOR * tdl;
  393.   }
  394.      
  395.   /** 
  396.    * The description length of data given the parameters of the data
  397.    * based on the ruleset. <p>
  398.    * Details see Quinlan: "MDL and categorical theories (Continued)",ML95<p>
  399.    *
  400.    * @param expFPOverErr expected FP/(FP+FN)
  401.    * @param cover coverage
  402.    * @param uncover uncoverage
  403.    * @param fp False Positive
  404.    * @param fn False Negative
  405.    */
  406.   public static double dataDL(double expFPOverErr, double cover, 
  407.       double uncover, double fp, double fn){
  408.     double totalBits = Utils.log2(cover+uncover+1.0); // how many data?
  409.     double coverBits, uncoverBits; // What's the error?
  410.     double expErr;                 // Expected FP or FN
  411.     if(Utils.gr(cover, uncover)){
  412.       expErr = expFPOverErr*(fp+fn);
  413.       coverBits = subsetDL(cover, fp, expErr/cover);
  414.       uncoverBits = Utils.gr(uncover, 0.0) ? 
  415. subsetDL(uncover, fn, fn/uncover) : 0.0;     
  416.     }
  417.     else{
  418.       expErr = (1.0-expFPOverErr)*(fp+fn);
  419.       coverBits = Utils.gr(cover, 0.0) ? 
  420. subsetDL(cover, fp, fp/cover) : 0.0;
  421.       uncoverBits = subsetDL(uncover, fn, expErr/uncover);
  422.     }
  423.     /*
  424.       System.err.println("!!!cover: " + cover + "|uncover" + uncover +
  425.       "|coverBits: "+coverBits+"|uncBits: "+ uncoverBits+
  426.       "|FPRate: "+expFPOverErr + "|expErr: "+expErr+
  427.       "|fp: "+fp+"|fn: "+fn+"|total: "+totalBits);
  428.     */
  429.     return (totalBits + coverBits + uncoverBits);
  430.   }
  431.     
  432.   /**
  433.    * Calculate the potential to decrease DL of the ruleset,
  434.    * i.e. the possible DL that could be decreased by deleting
  435.    * the rule whose index and simple statstics are given.  
  436.    * If there's no potentials (i.e. smOrEq 0 && error rate < 0.5),
  437.    * it returns NaN. <p>
  438.    *
  439.    * The way this procedure does is copied from original RIPPER
  440.    * implementation and is quite bizzare because it 
  441.    * does not update the following rules' stats recursively 
  442.    * any more when testing each rule, which means it assumes
  443.    * after deletion no data covered by the following rules (or
  444.    * regards the deleted rule as the last rule).  Reasonable 
  445.    * assumption?<p>
  446.    *
  447.    * @param index the index of the rule in m_Ruleset to be deleted
  448.    * @param expFPOverErr expected FP/(FP+FN)
  449.    * @param rulesetStat the simple statistics of the ruleset, updated
  450.    *                    if the rule should be deleted
  451.    * @param ruleStat the simple statistics of the rule to be deleted
  452.    * @param checkErr whether check if error rate >= 0.5
  453.    * @return the potential DL that could be decreased
  454.    */
  455.   public double potential(int index, double expFPOverErr, 
  456.   double[] rulesetStat, double[] ruleStat,
  457.   boolean checkErr){
  458.     //System.out.println("!!!inside potential: ");
  459.     // Restore the stats if deleted
  460.     double pcov = rulesetStat[0] - ruleStat[0];
  461.     double puncov = rulesetStat[1] + ruleStat[0];
  462.     double pfp = rulesetStat[4] - ruleStat[4];
  463.     double pfn = rulesetStat[5] + ruleStat[2];
  464.     double dataDLWith = dataDL(expFPOverErr, rulesetStat[0], 
  465.        rulesetStat[1], rulesetStat[4], 
  466.        rulesetStat[5]);
  467.     double theoryDLWith = theoryDL(index);
  468.     double dataDLWithout = dataDL(expFPOverErr, pcov, puncov, pfp, pfn);
  469.     double potential = dataDLWith + theoryDLWith - dataDLWithout;
  470.     double err = ruleStat[4] / ruleStat[0];
  471.     /*System.out.println("!!!"+dataDLWith +" | "+ 
  472.       theoryDLWith + " | " 
  473.       +dataDLWithout+"|"+ruleStat[4] + " / " + ruleStat[0]);
  474.     */
  475.     boolean overErr = Utils.grOrEq(err, 0.5);
  476.     if(!checkErr)
  477.       overErr = false;
  478.     if(Utils.grOrEq(potential, 0.0) || overErr){ 
  479.       // If deleted, update ruleset stats.  Other stats do not matter
  480.       rulesetStat[0] = pcov;
  481.       rulesetStat[1] = puncov;
  482.       rulesetStat[4] = pfp;
  483.       rulesetStat[5] = pfn;
  484.       return potential;
  485.     }
  486.     else
  487.       return Double.NaN;
  488.   }
  489.     
  490.     
  491.   /**
  492.    * Compute the minimal data description length of the ruleset
  493.    * if the rule in the given position is deleted.<br>
  494.    * The min_data_DL_if_deleted = data_DL_if_deleted - potential
  495.    *
  496.    * @param index the index of the rule in question
  497.    * @param expFPRate expected FP/(FP+FN), used in dataDL calculation
  498.    * @param checkErr whether check if error rate >= 0.5
  499.    * @param return the minDataDL
  500.    */
  501.   public double minDataDLIfDeleted(int index, double expFPRate,
  502.    boolean checkErr){
  503.     //System.out.println("!!!Enter without: ");
  504.     double[] rulesetStat = new double[6]; // Stats of ruleset if deleted
  505.     int more = m_Ruleset.size() - 1 - index; // How many rules after?
  506.     FastVector indexPlus = new FastVector(more); // Their stats
  507.     // 0...(index-1) are OK
  508.     for(int j=0; j<index; j++){
  509.       // Covered stats are cumulative
  510.       rulesetStat[0] += ((double[])m_SimpleStats.elementAt(j))[0];
  511.       rulesetStat[2] += ((double[])m_SimpleStats.elementAt(j))[2];
  512.       rulesetStat[4] += ((double[])m_SimpleStats.elementAt(j))[4];
  513.     }
  514.     // Recount data from index+1
  515.     Instances data = (index == 0) ?  
  516.       m_Data : ((Instances[])m_Filtered.elementAt(index-1))[1];
  517.     //System.out.println("!!!without: " + data.sumOfWeights());
  518.     for(int j=(index+1); j<m_Ruleset.size(); j++){
  519.       double[] stats = new double[6];
  520.       Instances[] split = computeSimpleStats(j, data, stats, null);
  521.       indexPlus.addElement(stats);
  522.       rulesetStat[0] += stats[0];
  523.       rulesetStat[2] += stats[2];
  524.       rulesetStat[4] += stats[4];    
  525.       data = split[1];
  526.     }
  527.     // Uncovered stats are those of the last rule
  528.     if(more > 0){
  529.       rulesetStat[1] = ((double[])indexPlus.lastElement())[1];
  530.       rulesetStat[3] = ((double[])indexPlus.lastElement())[3];
  531.       rulesetStat[5] = ((double[])indexPlus.lastElement())[5];
  532.     }
  533.     else if(index > 0){
  534.       rulesetStat[1] = 
  535. ((double[])m_SimpleStats.elementAt(index-1))[1];
  536.       rulesetStat[3] =
  537. ((double[])m_SimpleStats.elementAt(index-1))[3];
  538.       rulesetStat[5] = 
  539. ((double[])m_SimpleStats.elementAt(index-1))[5];
  540.     }
  541.     else{ // Null coverage
  542.       rulesetStat[1] = ((double[])m_SimpleStats.elementAt(0))[0] +
  543. ((double[])m_SimpleStats.elementAt(0))[1];
  544.       rulesetStat[3] = ((double[])m_SimpleStats.elementAt(0))[3] +
  545. ((double[])m_SimpleStats.elementAt(0))[4];
  546.       rulesetStat[5] = ((double[])m_SimpleStats.elementAt(0))[2] +
  547. ((double[])m_SimpleStats.elementAt(0))[5];     
  548.     }
  549.     // Potential 
  550.     double potential = 0;
  551.     for(int k=index+1; k<m_Ruleset.size(); k++){
  552.       double[] ruleStat = (double[])indexPlus.elementAt(k-index-1);
  553.       double ifDeleted = potential(k, expFPRate, rulesetStat, 
  554.    ruleStat, checkErr);
  555.       if(!Double.isNaN(ifDeleted))
  556. potential += ifDeleted;
  557.     }
  558.     // Data DL of the ruleset without the rule
  559.     // Note that ruleset stats has already been updated to reflect 
  560.     // deletion if any potential
  561.     double dataDLWithout = dataDL(expFPRate, rulesetStat[0], 
  562.   rulesetStat[1], rulesetStat[4], 
  563.   rulesetStat[5]);
  564.     //System.out.println("!!!without: "+dataDLWithout + " |potential: "+
  565.     //    potential);
  566.     // Why subtract potential again?  To reflect change of theory DL??
  567.     return (dataDLWithout - potential);
  568.   }    
  569.     
  570.     
  571.   /**
  572.    * Compute the minimal data description length of the ruleset
  573.    * if the rule in the given position is NOT deleted.<br>
  574.    * The min_data_DL_if_n_deleted = data_DL_if_n_deleted - potential
  575.    *
  576.    * @param index the index of the rule in question
  577.    * @param expFPRate expected FP/(FP+FN), used in dataDL calculation
  578.    * @param checkErr whether check if error rate >= 0.5
  579.    * @param return the minDataDL
  580.    */
  581.   public double minDataDLIfExists(int index, double expFPRate,
  582.   boolean checkErr){
  583.     // System.out.println("!!!Enter with: ");
  584.     double[] rulesetStat = new double[6]; // Stats of ruleset if rule exists
  585.     for(int j=0; j<m_SimpleStats.size(); j++){
  586.       // Covered stats are cumulative
  587.       rulesetStat[0] += ((double[])m_SimpleStats.elementAt(j))[0];
  588.       rulesetStat[2] += ((double[])m_SimpleStats.elementAt(j))[2];
  589.       rulesetStat[4] += ((double[])m_SimpleStats.elementAt(j))[4];
  590.       if(j == m_SimpleStats.size()-1){ // Last rule
  591. rulesetStat[1] = ((double[])m_SimpleStats.elementAt(j))[1];
  592. rulesetStat[3] = ((double[])m_SimpleStats.elementAt(j))[3];
  593. rulesetStat[5] = ((double[])m_SimpleStats.elementAt(j))[5];
  594.       }     
  595.     }
  596.     // Potential 
  597.     double potential = 0;
  598.     for(int k=index+1; k<m_SimpleStats.size(); k++){
  599.       double[] ruleStat = (double[])getSimpleStats(k);
  600.       double ifDeleted = potential(k, expFPRate, rulesetStat, 
  601.    ruleStat, checkErr);
  602.       if(!Double.isNaN(ifDeleted))
  603. potential += ifDeleted;
  604.     }
  605.     // Data DL of the ruleset without the rule
  606.     // Note that ruleset stats has already been updated to reflect deletion
  607.     // if any potential
  608.     double dataDLWith = dataDL(expFPRate, rulesetStat[0], 
  609.        rulesetStat[1], rulesetStat[4], 
  610.        rulesetStat[5]);
  611.     //System.out.println("!!!with: "+dataDLWith + " |potential: "+
  612.     //    potential);
  613.     return (dataDLWith - potential);
  614.   }
  615.     
  616.     
  617.   /**
  618.    * The description length (DL) of the ruleset relative to if the
  619.    * rule in the given position is deleted, which is obtained by: <br>
  620.    * MDL if the rule exists - MDL if the rule does not exist <br>
  621.    * Note the minimal possible DL of the ruleset is calculated(i.e. some
  622.    * other rules may also be deleted) instead of the DL of the current
  623.    * ruleset.<p>
  624.    *
  625.    * @param index the given position of the rule in question 
  626.    *              (assuming correct)
  627.    * @param expFPRate expected FP/(FP+FN), used in dataDL calculation
  628.    * @param checkErr whether check if error rate >= 0.5
  629.    * @return the relative DL
  630.    */
  631.   public double relativeDL(int index, double expFPRate, boolean checkErr){ 
  632.  
  633.     return (minDataDLIfExists(index, expFPRate, checkErr) 
  634.     + theoryDL(index) - 
  635.     minDataDLIfDeleted(index, expFPRate, checkErr));
  636.   }  
  637.     
  638.     
  639.   /**
  640.    * Try to reduce the DL of the ruleset by testing removing the rules
  641.    * one by one in reverse order and update all the stats
  642.    * @param expFPRate expected FP/(FP+FN), used in dataDL calculation
  643.    * @param checkErr whether check if error rate >= 0.5
  644.    */
  645.   public void reduceDL(double expFPRate, boolean checkErr){
  646.     boolean needUpdate = false;
  647.     double[] rulesetStat = new double[6];
  648.     for(int j=0; j<m_SimpleStats.size(); j++){
  649.       // Covered stats are cumulative
  650.       rulesetStat[0] += ((double[])m_SimpleStats.elementAt(j))[0];
  651.       rulesetStat[2] += ((double[])m_SimpleStats.elementAt(j))[2];
  652.       rulesetStat[4] += ((double[])m_SimpleStats.elementAt(j))[4];
  653.       if(j == m_SimpleStats.size()-1){ // Last rule
  654. rulesetStat[1] = ((double[])m_SimpleStats.elementAt(j))[1];
  655. rulesetStat[3] = ((double[])m_SimpleStats.elementAt(j))[3];
  656. rulesetStat[5] = ((double[])m_SimpleStats.elementAt(j))[5];
  657.       }     
  658.     }
  659.     // Potential 
  660.     double potential = 0;
  661.     for(int k=m_SimpleStats.size()-1; k>=0; k--){
  662.     
  663.       double[] ruleStat = (double[])m_SimpleStats.elementAt(k);
  664.       // rulesetStat updated
  665.       double ifDeleted = potential(k, expFPRate, rulesetStat, 
  666.    ruleStat, checkErr);
  667.       if(!Double.isNaN(ifDeleted)){  
  668. /*System.err.println("!!!deleted ("+k+"): save "+ifDeleted
  669.   +" | "+rulesetStat[0]
  670.   +" | "+rulesetStat[1]
  671.   +" | "+rulesetStat[4]
  672.   +" | "+rulesetStat[5]);
  673. */
  674. if(k == (m_SimpleStats.size()-1))
  675.     removeLast();
  676. else{
  677.     m_Ruleset.removeElementAt(k);
  678.     needUpdate = true;
  679. }
  680.       }
  681.     }
  682.     if(needUpdate){
  683.       m_Filtered = null;
  684.       m_SimpleStats = null;
  685.       countData();
  686.     }
  687.   }
  688.     
  689.   /**
  690.    * Remove the last rule in the ruleset as well as it's stats.
  691.    * It might be useful when the last rule was added for testing
  692.    * purpose and then the test failed
  693.    */
  694.   public void removeLast(){
  695.     int last = m_Ruleset.size()-1;
  696.     m_Ruleset.removeElementAt(last);
  697.     m_Filtered.removeElementAt(last);
  698.     m_SimpleStats.removeElementAt(last);
  699.     if(m_Distributions != null)
  700. m_Distributions.removeElementAt(last);
  701.   }
  702.   /**
  703.    * Static utility function to count the data covered by the 
  704.    * rules after the given index in the given rules, and then
  705.    * remove them.  It returns the data not covered by the
  706.    * successive rules.
  707.    *
  708.    * @param data the data to be processed
  709.    * @param rules the ruleset
  710.    * @param index the given index
  711.    * @return the data after processing
  712.    */
  713.   public static Instances rmCoveredBySuccessives(Instances data, FastVector rules, int index){
  714.     Instances rt = new Instances(data, 0);
  715.     for(int i=0; i < data.numInstances(); i++){
  716.       Instance datum = data.instance(i);
  717.       boolean covered = false;     
  718.     
  719.       for(int j=index+1; j<rules.size();j++){
  720. Rule rule = (Rule)rules.elementAt(j);
  721. if(rule.covers(datum)){
  722.   covered = true;
  723.   break;
  724. }
  725.       }
  726.       if(!covered)
  727. rt.add(datum);
  728.     }
  729.     return rt;
  730.   } 
  731.     
  732.   /** 
  733.    * Stratify the given data into the given number of bags based on the class
  734.    * values.  It differs from the <code>Instances.stratify(int fold)</code>
  735.    * that before stratification it sorts the instances according to the 
  736.    * class order in the header file.  It assumes no missing values in the class.
  737.    * 
  738.    * @param data the given data
  739.    * @param folds the given number of folds
  740.    * @param rand the random object used to randomize the instances
  741.    * @return the stratified instances
  742.    */
  743.   public static final Instances stratify(Instances data, int folds, Random rand){
  744.     if(!data.classAttribute().isNominal())
  745.       return data;
  746.     Instances result = new Instances(data, 0);
  747.     Instances[] bagsByClasses = new Instances[data.numClasses()];
  748.     for(int i=0; i < bagsByClasses.length; i++)
  749.       bagsByClasses[i] = new Instances(data, 0);
  750.     // Sort by class
  751.     for(int j=0; j < data.numInstances(); j++){
  752.       Instance datum = data.instance(j);
  753.       bagsByClasses[(int)datum.classValue()].add(datum);
  754.     }
  755.     // Randomize each class
  756.     for(int j=0; j < bagsByClasses.length; j++)
  757.       bagsByClasses[j].randomize(rand);
  758.     for(int k=0; k < folds; k++){
  759.       int offset = k, bag = 0;
  760.     oneFold:
  761.       while (true){
  762. while(offset >= bagsByClasses[bag].numInstances()){
  763.   offset -= bagsByClasses[bag].numInstances();
  764.   if (++bag >= bagsByClasses.length)// Next bag
  765.     break oneFold;          
  766. }
  767.     
  768. result.add(bagsByClasses[bag].instance(offset));
  769. offset += folds;
  770.       }
  771.     }
  772.     return result;
  773.   }
  774.   /** 
  775.    * Compute the combined DL of the ruleset in this class, i.e. theory 
  776.    * DL and data DL.  Note this procedure computes the combined DL
  777.    * according to the current status of the ruleset in this class
  778.    * 
  779.    * @param expFPRate expected FP/(FP+FN), used in dataDL calculation
  780.    * @param predicted the default classification if ruleset covers null
  781.    * @return the combined class
  782.    */
  783.   public double combinedDL(double expFPRate, double predicted){
  784.     double rt = 0;
  785.     
  786.     if(getRulesetSize() > 0) {
  787.       double[] stats = (double[])m_SimpleStats.lastElement();
  788.       for(int j=getRulesetSize()-2; j >= 0; j--){
  789. stats[0] += getSimpleStats(j)[0];
  790. stats[2] += getSimpleStats(j)[2];
  791. stats[4] += getSimpleStats(j)[4];
  792.       }
  793.       rt += dataDL(expFPRate, stats[0], stats[1], 
  794.    stats[4], stats[5]);     // Data DL      
  795.     }
  796.     else{ // Null coverage ruleset
  797.       double fn = 0.0;
  798.       for(int j=0; j < m_Data.numInstances(); j++)
  799. if((int)m_Data.instance(j).classValue() == (int)predicted)
  800.   fn += m_Data.instance(j).weight();
  801.       rt += dataDL(expFPRate, 0.0, m_Data.sumOfWeights(), 0.0, fn);
  802.     }     
  803.     
  804.     for(int i=0; i<getRulesetSize(); i++) // Theory DL
  805.       rt += theoryDL(i);     
  806.     
  807.     return rt;
  808.   }
  809.   
  810.   /** 
  811.    * Patition the data into 2, first of which has (numFolds-1)/numFolds of
  812.    * the data and the second has 1/numFolds of the data
  813.    *
  814.    * 
  815.    * @param data the given data
  816.    * @param numFolds the given number of folds
  817.    * @return the patitioned instances
  818.    */
  819.   public static final Instances[] partition(Instances data, int numFolds){
  820.     Instances[] rt = new Instances[2];
  821.     int splits = data.numInstances() * (numFolds - 1) / numFolds;
  822.     
  823.     rt[0] = new Instances(data, 0, splits);
  824.     rt[1] = new Instances(data, splits, data.numInstances()-splits);
  825.     
  826.     return rt;
  827.   }  
  828. }