CheckClassifier.java
Upload User: rhdiban
Upload Date: 2013-08-09
Package Size: 15085k
Code Size: 47k
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.  *    CheckClassifier.java
  18.  *    Copyright (C) 1999 Len Trigg
  19.  *
  20.  */
  21. package weka.classifiers;
  22. import weka.classifiers.bayes.NaiveBayes;
  23. import weka.classifiers.rules.ZeroR;
  24. import java.io.*;
  25. import java.util.*;
  26. import weka.core.*;
  27. /**
  28.  * Class for examining the capabilities and finding problems with 
  29.  * classifiers. If you implement a classifier using the WEKA.libraries,
  30.  * you should run the checks on it to ensure robustness and correct
  31.  * operation. Passing all the tests of this object does not mean
  32.  * bugs in the classifier don't exist, but this will help find some
  33.  * common ones. <p>
  34.  * 
  35.  * Typical usage: <p>
  36.  * <code>java weka.classifiers.CheckClassifier -W classifier_name 
  37.  * classifier_options </code><p>
  38.  * 
  39.  * CheckClassifier reports on the following:
  40.  * <ul>
  41.  *    <li> Classifier abilities <ul>
  42.  *         <li> Possible command line options to the classifier
  43.  *         <li> Whether the classifier is a distributionClassifier
  44.  *         <li> Whether the classifier can predict nominal and/or predict 
  45.  *              numeric class attributes. Warnings will be displayed if 
  46.  *              performance is worse than ZeroR
  47.  *         <li> Whether the classifier can be trained incrementally
  48.  *         <li> Whether the classifier can handle numeric predictor attributes
  49.  *         <li> Whether the classifier can handle nominal predictor attributes
  50.  *         <li> Whether the classifier can handle string predictor attributes
  51.  *         <li> Whether the classifier can handle missing predictor values
  52.  *         <li> Whether the classifier can handle missing class values
  53.  *         <li> Whether a nominal classifier only handles 2 class problems
  54.  *         <li> Whether the classifier can handle instance weights
  55.  *         </ul>
  56.  *    <li> Correct functioning <ul>
  57.  *         <li> Correct initialisation during buildClassifier (i.e. no result
  58.  *              changes when buildClassifier called repeatedly)
  59.  *         <li> Whether incremental training produces the same results
  60.  *              as during non-incremental training (which may or may not 
  61.  *              be OK)
  62.  *         <li> Whether the classifier alters the data pased to it 
  63.  *              (number of instances, instance order, instance weights, etc)
  64.  *         </ul>
  65.  *    <li> Degenerate cases <ul>
  66.  *         <li> building classifier with zero training instances
  67.  *         <li> all but one predictor attribute values missing
  68.  *         <li> all predictor attribute values missing
  69.  *         <li> all but one class values missing
  70.  *         <li> all class values missing
  71.  *         </ul>
  72.  *    </ul>
  73.  * Running CheckClassifier with the debug option set will output the 
  74.  * training and test datasets for any failed tests.<p>
  75.  *
  76.  * Valid options are:<p>
  77.  *
  78.  * -D <br>
  79.  * Turn on debugging output.<p>
  80.  *
  81.  * -W classname <br>
  82.  * Specify the full class name of a classifier to perform the 
  83.  * tests on (required).<p>
  84.  *
  85.  * Options after -- are passed to the designated classifier.<p>
  86.  *
  87.  * @author Len Trigg (trigg@cs.waikato.ac.nz)
  88.  * @version $Revision: 1.14 $
  89.  */
  90. public class CheckClassifier implements OptionHandler {
  91.   /*** The classifier to be examined */
  92.   protected Classifier m_Classifier = new weka.classifiers.rules.ZeroR();
  93.   /** The options to be passed to the base classifier. */
  94.   protected String [] m_ClassifierOptions;
  95.   /** The results of the analysis as a string */
  96.   protected String m_AnalysisResults;
  97.   /** Debugging mode, gives extra output if true */
  98.   protected boolean m_Debug;
  99.   /**
  100.    * Returns an enumeration describing the available options.
  101.    *
  102.    * @return an enumeration of all the available options.
  103.    */
  104.   public Enumeration listOptions() {
  105.     Vector newVector = new Vector(2);
  106.     newVector.addElement(new Option(
  107.       "tTurn on debugging output.",
  108.       "D", 0, "-D"));
  109.     newVector.addElement(new Option(
  110.       "tFull name of the classifier analysed.n"
  111.       +"teg: weka.classifiers.bayes.NaiveBayes",
  112.       "W", 1, "-W"));
  113.     if ((m_Classifier != null) 
  114. && (m_Classifier instanceof OptionHandler)) {
  115.       newVector.addElement(new Option("", "", 0, 
  116.       "nOptions specific to classifier "
  117.       + m_Classifier.getClass().getName()
  118.       + ":"));
  119.       Enumeration enum = ((OptionHandler)m_Classifier).listOptions();
  120.       while (enum.hasMoreElements())
  121. newVector.addElement(enum.nextElement());
  122.     }
  123.     return newVector.elements();
  124.   }
  125.   /**
  126.    * Parses a given list of options. Valid options are:<p>
  127.    *
  128.    * -D <br>
  129.    * Turn on debugging output.<p>
  130.    *
  131.    * -W classname <br>
  132.    * Specify the full class name of a classifier to perform the 
  133.    * tests on (required).<p>
  134.    *
  135.    * Options after -- are passed to the designated classifier 
  136.    *
  137.    * @param options the list of options as an array of strings
  138.    * @exception Exception if an option is not supported
  139.    */
  140.   public void setOptions(String[] options) throws Exception {
  141.     setDebug(Utils.getFlag('D', options));
  142.     
  143.     String classifierName = Utils.getOption('W', options);
  144.     if (classifierName.length() == 0) {
  145.       throw new Exception("A classifier must be specified with"
  146.   + " the -W option.");
  147.     }
  148.     setClassifier(Classifier.forName(classifierName,
  149.      Utils.partitionOptions(options)));
  150.   }
  151.   /**
  152.    * Gets the current settings of the CheckClassifier.
  153.    *
  154.    * @return an array of strings suitable for passing to setOptions
  155.    */
  156.   public String [] getOptions() {
  157.     String [] classifierOptions = new String [0];
  158.     if ((m_Classifier != null) && 
  159. (m_Classifier instanceof OptionHandler)) {
  160.       classifierOptions = ((OptionHandler)m_Classifier).getOptions();
  161.     }
  162.     String [] options = new String [classifierOptions.length + 4];
  163.     int current = 0;
  164.     if (getDebug()) {
  165.       options[current++] = "-D";
  166.     }
  167.     if (getClassifier() != null) {
  168.       options[current++] = "-W";
  169.       options[current++] = getClassifier().getClass().getName();
  170.     }
  171.     options[current++] = "--";
  172.     System.arraycopy(classifierOptions, 0, options, current, 
  173.      classifierOptions.length);
  174.     current += classifierOptions.length;
  175.     while (current < options.length) {
  176.       options[current++] = "";
  177.     }
  178.     return options;
  179.   }
  180.   /**
  181.    * Begin the tests, reporting results to System.out
  182.    */
  183.   public void doTests() {
  184.     if (getClassifier() == null) {
  185.       System.out.println("n=== No classifier set ===");
  186.       return;
  187.     }
  188.     System.out.println("n=== Check on Classifier: "
  189.        + getClassifier().getClass().getName()
  190.        + " ===n");
  191.     // Start tests
  192.     canTakeOptions();
  193.     boolean updateableClassifier = updateableClassifier();
  194.     boolean distributionClassifier = distributionClassifier();
  195.     boolean weightedInstancesHandler = weightedInstancesHandler();
  196.     testsPerClassType(false, updateableClassifier, weightedInstancesHandler);
  197.     testsPerClassType(true, updateableClassifier, weightedInstancesHandler);
  198.   }
  199.   /**
  200.    * Set debugging mode
  201.    *
  202.    * @param debug true if debug output should be printed
  203.    */
  204.   public void setDebug(boolean debug) {
  205.     m_Debug = debug;
  206.   }
  207.   /**
  208.    * Get whether debugging is turned on
  209.    *
  210.    * @return true if debugging output is on
  211.    */
  212.   public boolean getDebug() {
  213.     return m_Debug;
  214.   }
  215.   /**
  216.    * Set the classifier for boosting. 
  217.    *
  218.    * @param newClassifier the Classifier to use.
  219.    */
  220.   public void setClassifier(Classifier newClassifier) {
  221.     m_Classifier = newClassifier;
  222.   }
  223.   /**
  224.    * Get the classifier used as the classifier
  225.    *
  226.    * @return the classifier used as the classifier
  227.    */
  228.   public Classifier getClassifier() {
  229.     return m_Classifier;
  230.   }
  231.   /**
  232.    * Test method for this class
  233.    */
  234.   public static void main(String [] args) {
  235.     try {
  236.       CheckClassifier check = new CheckClassifier();
  237.       try {
  238. check.setOptions(args);
  239. Utils.checkForRemainingOptions(args);
  240.       } catch (Exception ex) {
  241. String result = ex.getMessage() + "nCheckClassifier Options:nn";
  242. Enumeration enum = check.listOptions();
  243. while (enum.hasMoreElements()) {
  244.   Option option = (Option) enum.nextElement();
  245.   result += option.synopsis() + "n" + option.description() + "n";
  246. }
  247. throw new Exception(result);
  248.       }
  249.       check.doTests();
  250.     } catch (Exception ex) {
  251.       System.err.println(ex.getMessage());
  252.     }
  253.   }
  254.   /**
  255.    * Run a battery of tests for a given class attribute type
  256.    *
  257.    * @param numericClass true if the class attribute should be numeric
  258.    * @param updateable true if the classifier is updateable
  259.    * @param weighted true if the classifier says it handles weights
  260.    */
  261.   protected void testsPerClassType(boolean numericClass, boolean updateable,
  262.    boolean weighted) {
  263.     boolean PNom = canPredict(true, false, numericClass);
  264.     boolean PNum = canPredict(false, true, numericClass);
  265.     if (PNom || PNum) {
  266.       if (weighted) {
  267. instanceWeights(PNom, PNum, numericClass);
  268.       }
  269.       if (!numericClass) {
  270. canHandleNClasses(PNom, PNum, 4);
  271.       }
  272.       canHandleZeroTraining(PNom, PNum, numericClass);
  273.       boolean handleMissingPredictors = canHandleMissing(PNom, PNum, 
  274.  numericClass, 
  275.  true, false, 20);
  276.       if (handleMissingPredictors) {
  277. canHandleMissing(PNom, PNum, numericClass, true, false, 100);
  278.       }
  279.       boolean handleMissingClass = canHandleMissing(PNom, PNum, numericClass, 
  280.     false, true, 20);
  281.       if (handleMissingClass) {
  282. canHandleMissing(PNom, PNum, numericClass, false, true, 100);
  283.       }
  284.       correctBuildInitialisation(PNom, PNum, numericClass);
  285.       datasetIntegrity(PNom, PNum, numericClass,
  286.        handleMissingPredictors, handleMissingClass);
  287.       doesntUseTestClassVal(PNom, PNum, numericClass);
  288.       if (updateable) {
  289. updatingEquality(PNom, PNum, numericClass);
  290.       }
  291.     }
  292.     /*
  293.      * Robustness / Correctness:
  294.      *    Whether the classifier can handle string predictor attributes
  295.      */
  296.   }
  297.   /**
  298.    * Checks whether the scheme can take command line options.
  299.    *
  300.    * @return true if the classifier can take options
  301.    */
  302.   protected boolean canTakeOptions() {
  303.     System.out.print("options...");
  304.     if (m_Classifier instanceof OptionHandler) {
  305.       System.out.println("yes");
  306.       if (m_Debug) {
  307. System.out.println("n=== Full report ===");
  308. Enumeration enum = ((OptionHandler)m_Classifier).listOptions();
  309. while (enum.hasMoreElements()) {
  310.   Option option = (Option) enum.nextElement();
  311.   System.out.print(option.synopsis() + "n" 
  312.    + option.description() + "n");
  313. }
  314. System.out.println("n");
  315.       }
  316.       return true;
  317.     }
  318.     System.out.println("no");
  319.     return false;
  320.   }
  321.   
  322.   /**
  323.    * Checks whether the scheme is a distribution classifier.
  324.    *
  325.    * @return true if the classifier produces distributions
  326.    */
  327.   protected boolean distributionClassifier() {
  328.     System.out.print("distribution classifier...");
  329.     if (m_Classifier instanceof DistributionClassifier) {
  330.       System.out.println("yes");
  331.       return true;
  332.     }
  333.     System.out.println("no");
  334.     return false;
  335.   }
  336.   /**
  337.    * Checks whether the scheme can build models incrementally.
  338.    *
  339.    * @return true if the classifier can train incrementally
  340.    */
  341.   protected boolean updateableClassifier() {
  342.     System.out.print("updateable classifier...");
  343.     if (m_Classifier instanceof UpdateableClassifier) {
  344.       System.out.println("yes");
  345.       return true;
  346.     }
  347.     System.out.println("no");
  348.     return false;
  349.   }
  350.   /**
  351.    * Checks whether the scheme says it can handle instance weights.
  352.    *
  353.    * @return true if the classifier handles instance weights
  354.    */
  355.   protected boolean weightedInstancesHandler() {
  356.     System.out.print("weighted instances classifier...");
  357.     if (m_Classifier instanceof WeightedInstancesHandler) {
  358.       System.out.println("yes");
  359.       return true;
  360.     }
  361.     System.out.println("no");
  362.     return false;
  363.   }
  364.   /**
  365.    * Checks basic prediction of the scheme, for simple non-troublesome
  366.    * datasets.
  367.    *
  368.    * @param nominalPredictor if true use nominal predictor attributes
  369.    * @param numericPredictor if true use numeric predictor attributes
  370.    * @param numericClass if true use a numeric class attribute otherwise a
  371.    * nominal class attribute
  372.    * @return true if the test was passed
  373.    */
  374.   protected boolean canPredict(boolean nominalPredictor,
  375.        boolean numericPredictor, 
  376.        boolean numericClass) {
  377.     System.out.print("basic predict");
  378.     printAttributeSummary(nominalPredictor, numericPredictor, numericClass);
  379.     System.out.print("...");
  380.     FastVector accepts = new FastVector();
  381.     accepts.addElement("nominal");
  382.     accepts.addElement("numeric");
  383.     int numTrain = 20, numTest = 20, numClasses = 2, missingLevel = 0;
  384.     boolean predictorMissing = false, classMissing = false;
  385.     return runBasicTest(nominalPredictor, numericPredictor, numericClass, 
  386. missingLevel, predictorMissing, classMissing,
  387. numTrain, numTest, numClasses, 
  388. accepts);
  389.   }
  390.   /**
  391.    * Checks whether nominal schemes can handle more than two classes.
  392.    * If a scheme is only designed for two-class problems it should
  393.    * throw an appropriate exception for multi-class problems.
  394.    *
  395.    * @param nominalPredictor if true use nominal predictor attributes
  396.    * @param numericPredictor if true use numeric predictor attributes
  397.    * @param numClasses the number of classes to test
  398.    * @return true if the test was passed
  399.    */
  400.   protected boolean canHandleNClasses(boolean nominalPredictor,
  401.       boolean numericPredictor, 
  402.       int numClasses) {
  403.     System.out.print("more than two class problems");
  404.     printAttributeSummary(nominalPredictor, numericPredictor, false);
  405.     System.out.print("...");
  406.     FastVector accepts = new FastVector();
  407.     accepts.addElement("number");
  408.     accepts.addElement("class");
  409.     int numTrain = 20, numTest = 20, missingLevel = 0;
  410.     boolean predictorMissing = false, classMissing = false;
  411.     return runBasicTest(nominalPredictor, numericPredictor, false, 
  412. missingLevel, predictorMissing, classMissing,
  413. numTrain, numTest, numClasses, 
  414. accepts);
  415.   }
  416.   /**
  417.    * Checks whether the scheme can handle zero training instances.
  418.    *
  419.    * @param nominalPredictor if true use nominal predictor attributes
  420.    * @param numericPredictor if true use numeric predictor attributes
  421.    * @param numericClass if true use a numeric class attribute otherwise a
  422.    * nominal class attribute
  423.    * @return true if the test was passed
  424.    */
  425.   protected boolean canHandleZeroTraining(boolean nominalPredictor,
  426.   boolean numericPredictor, 
  427.   boolean numericClass) {
  428.     System.out.print("handle zero training instances");
  429.     printAttributeSummary(nominalPredictor, numericPredictor, numericClass);
  430.     System.out.print("...");
  431.     FastVector accepts = new FastVector();
  432.     accepts.addElement("train");
  433.     accepts.addElement("value");
  434.     int numTrain = 0, numTest = 20, numClasses = 2, missingLevel = 0;
  435.     boolean predictorMissing = false, classMissing = false;
  436.     return runBasicTest(nominalPredictor, numericPredictor, numericClass, 
  437. missingLevel, predictorMissing, classMissing,
  438. numTrain, numTest, numClasses, 
  439. accepts);
  440.   }
  441.   /**
  442.    * Checks whether the scheme correctly initialises models when 
  443.    * buildClassifier is called. This test calls buildClassifier with
  444.    * one training dataset and records performance on a test set. 
  445.    * buildClassifier is then called on a training set with different
  446.    * structure, and then again with the original training set. The
  447.    * performance on the test set is compared with the original results
  448.    * and any performance difference noted as incorrect build initialisation.
  449.    *
  450.    * @param nominalPredictor if true use nominal predictor attributes
  451.    * @param numericPredictor if true use numeric predictor attributes
  452.    * @param numericClass if true use a numeric class attribute otherwise a
  453.    * nominal class attribute
  454.    * @return true if the test was passed
  455.    */
  456.   protected boolean correctBuildInitialisation(boolean nominalPredictor,
  457.        boolean numericPredictor, 
  458.        boolean numericClass) {
  459.     System.out.print("correct initialisation during buildClassifier");
  460.     printAttributeSummary(nominalPredictor, numericPredictor, numericClass);
  461.     System.out.print("...");
  462.     int numTrain = 20, numTest = 20, numClasses = 2, missingLevel = 0;
  463.     boolean predictorMissing = false, classMissing = false;
  464.     Instances train1 = null;
  465.     Instances test1 = null;
  466.     Instances train2 = null;
  467.     Instances test2 = null;
  468.     Classifier classifier = null;
  469.     Evaluation evaluation1A = null;
  470.     Evaluation evaluation1B = null;
  471.     Evaluation evaluation2 = null;
  472.     boolean built = false;
  473.     int stage = 0;
  474.     try {
  475.       // Make two sets of train/test splits with different 
  476.       // numbers of attributes
  477.       train1 = makeTestDataset(42, numTrain, 
  478.        nominalPredictor ? 2 : 0,
  479.        numericPredictor ? 1 : 0, 
  480.        numClasses, 
  481.        numericClass);
  482.       train2 = makeTestDataset(84, numTrain, 
  483.        nominalPredictor ? 3 : 0,
  484.        numericPredictor ? 2 : 0, 
  485.        numClasses, 
  486.        numericClass);
  487.       test1 = makeTestDataset(24, numTest,
  488.       nominalPredictor ? 2 : 0,
  489.       numericPredictor ? 1 : 0, 
  490.       numClasses, 
  491.       numericClass);
  492.       test2 = makeTestDataset(48, numTest,
  493.       nominalPredictor ? 3 : 0,
  494.       numericPredictor ? 2 : 0, 
  495.       numClasses, 
  496.       numericClass);
  497.       if (nominalPredictor) {
  498. train1.deleteAttributeAt(0);
  499. test1.deleteAttributeAt(0);
  500. train2.deleteAttributeAt(0);
  501. test2.deleteAttributeAt(0);
  502.       }
  503.       if (missingLevel > 0) {
  504. addMissing(train1, missingLevel, predictorMissing, classMissing);
  505. addMissing(test1, Math.min(missingLevel,50), predictorMissing, 
  506.    classMissing);
  507. addMissing(train2, missingLevel, predictorMissing, classMissing);
  508. addMissing(test2, Math.min(missingLevel,50), predictorMissing, 
  509.    classMissing);
  510.       }
  511.       classifier = Classifier.makeCopies(getClassifier(), 1)[0];
  512.       evaluation1A = new Evaluation(train1);
  513.       evaluation1B = new Evaluation(train1);
  514.       evaluation2 = new Evaluation(train2);
  515.     } catch (Exception ex) {
  516.       throw new Error("Error setting up for tests: " + ex.getMessage());
  517.     }
  518.     try {
  519.       stage = 0;
  520.       classifier.buildClassifier(train1);
  521.       built = true;
  522.       if (!testWRTZeroR(classifier, evaluation1A, train1, test1)) {
  523. throw new Exception("Scheme performs worse than ZeroR");
  524.       }
  525.       stage = 1;
  526.       built = false;
  527.       classifier.buildClassifier(train2);
  528.       built = true;
  529.       if (!testWRTZeroR(classifier, evaluation2, train2, test2)) {
  530. throw new Exception("Scheme performs worse than ZeroR");
  531.       }
  532.       stage = 2;
  533.       built = false;
  534.       classifier.buildClassifier(train1);
  535.       built = true;
  536.       if (!testWRTZeroR(classifier, evaluation1B, train1, test1)) {
  537. throw new Exception("Scheme performs worse than ZeroR");
  538.       }
  539.       stage = 3;
  540.       if (!evaluation1A.equals(evaluation1B)) {
  541. if (m_Debug) {
  542.   System.out.println("n=== Full report ===n"
  543. + evaluation1A.toSummaryString("nFirst buildClassifier()",
  544.        true)
  545. + "nn");
  546.   System.out.println(
  547.                 evaluation1B.toSummaryString("nSecond buildClassifier()",
  548.      true)
  549. + "nn");
  550. }
  551. throw new Exception("Results differ between buildClassifier calls");
  552.       }
  553.       System.out.println("yes");
  554.       if (false && m_Debug) {
  555. System.out.println("n=== Full report ===n"
  556.                 + evaluation1A.toSummaryString("nFirst buildClassifier()",
  557.        true)
  558. + "nn");
  559. System.out.println(
  560.                 evaluation1B.toSummaryString("nSecond buildClassifier()",
  561.      true)
  562. + "nn");
  563.       }
  564.       return true;
  565.     } catch (Exception ex) {
  566.       String msg = ex.getMessage().toLowerCase();
  567.       if (msg.indexOf("worse than zeror") >= 0) {
  568. System.out.println("warning: performs worse than ZeroR");
  569.       } else {
  570. System.out.println("no");
  571.       }
  572.       if (m_Debug) {
  573. System.out.println("n=== Full Report ===");
  574. System.out.print("Problem during");
  575. if (built) {
  576.   System.out.print(" testing");
  577. } else {
  578.   System.out.print(" training");
  579. }
  580. switch (stage) {
  581. case 0:
  582.   System.out.print(" of dataset 1");
  583.   break;
  584. case 1:
  585.   System.out.print(" of dataset 2");
  586.   break;
  587. case 2:
  588.   System.out.print(" of dataset 1 (2nd build)");
  589.   break;
  590. case 3:
  591.   System.out.print(", comparing results from builds of dataset 1");
  592.   break;   
  593. }
  594. System.out.println(": " + ex.getMessage() + "n");
  595. System.out.println("here are the datasets:n");
  596. System.out.println("=== Train1 Dataset ===n"
  597.    + train1.toString() + "n");
  598. System.out.println("=== Test1 Dataset ===n"
  599.    + test1.toString() + "nn");
  600. System.out.println("=== Train2 Dataset ===n"
  601.    + train2.toString() + "n");
  602. System.out.println("=== Test2 Dataset ===n"
  603.    + test2.toString() + "nn");
  604.       }
  605.     }
  606.     return false;
  607.   }
  608.   /**
  609.    * Checks basic missing value handling of the scheme. If the missing
  610.    * values cause an exception to be thrown by the scheme, this will be
  611.    * recorded.
  612.    *
  613.    * @param nominalPredictor if true use nominal predictor attributes
  614.    * @param numericPredictor if true use numeric predictor attributes
  615.    * @param numericClass if true use a numeric class attribute otherwise a
  616.    * nominal class attribute
  617.    * @param predictorMissing true if the missing values may be in 
  618.    * the predictors
  619.    * @param classMissing true if the missing values may be in the class
  620.    * @param level the percentage of missing values
  621.    * @return true if the test was passed
  622.    */
  623.   protected boolean canHandleMissing(boolean nominalPredictor,
  624.      boolean numericPredictor, 
  625.      boolean numericClass,
  626.      boolean predictorMissing,
  627.      boolean classMissing,
  628.      int missingLevel) {
  629.     if (missingLevel == 100) {
  630.       System.out.print("100% ");
  631.     }
  632.     System.out.print("missing");
  633.     if (predictorMissing) {
  634.       System.out.print(" predictor");
  635.       if (classMissing) {
  636. System.out.print(" and");
  637.       }
  638.     }
  639.     if (classMissing) {
  640.       System.out.print(" class");
  641.     }
  642.     System.out.print(" values");
  643.     printAttributeSummary(nominalPredictor, numericPredictor, numericClass);
  644.     System.out.print("...");
  645.     FastVector accepts = new FastVector();
  646.     accepts.addElement("missing");
  647.     accepts.addElement("value");
  648.     accepts.addElement("train");
  649.     int numTrain = 20, numTest = 20, numClasses = 2;
  650.     return runBasicTest(nominalPredictor, numericPredictor, numericClass, 
  651. missingLevel, predictorMissing, classMissing,
  652. numTrain, numTest, numClasses, 
  653. accepts);
  654.   }
  655.   /**
  656.    * Checks whether an updateable scheme produces the same model when
  657.    * trained incrementally as when batch trained. The model itself
  658.    * cannot be compared, so we compare the evaluation on test data
  659.    * for both models. It is possible to get a false positive on this
  660.    * test (likelihood depends on the classifier).
  661.    *
  662.    * @param nominalPredictor if true use nominal predictor attributes
  663.    * @param numericPredictor if true use numeric predictor attributes
  664.    * @param numericClass if true use a numeric class attribute otherwise a
  665.    * nominal class attribute
  666.    * @return true if the test was passed
  667.    */
  668.   protected boolean updatingEquality(boolean nominalPredictor,
  669.      boolean numericPredictor, 
  670.      boolean numericClass) {
  671.     System.out.print("incremental training produces the same results"
  672.      + " as batch training");
  673.     printAttributeSummary(nominalPredictor, numericPredictor, numericClass);
  674.     System.out.print("...");
  675.     int numTrain = 20, numTest = 20, numClasses = 2, missingLevel = 0;
  676.     boolean predictorMissing = false, classMissing = false;
  677.     Instances train = null;
  678.     Instances test = null;
  679.     Classifier [] classifiers = null;
  680.     Evaluation evaluationB = null;
  681.     Evaluation evaluationI = null;
  682.     boolean built = false;
  683.     try {
  684.       train = makeTestDataset(42, numTrain, 
  685.       nominalPredictor ? 2 : 0,
  686.       numericPredictor ? 1 : 0, 
  687.       numClasses, 
  688.       numericClass);
  689.       test = makeTestDataset(24, numTest,
  690.      nominalPredictor ? 2 : 0,
  691.      numericPredictor ? 1 : 0, 
  692.      numClasses, 
  693.      numericClass);
  694.       if (nominalPredictor) {
  695. train.deleteAttributeAt(0);
  696. test.deleteAttributeAt(0);
  697.       }
  698.       if (missingLevel > 0) {
  699. addMissing(train, missingLevel, predictorMissing, classMissing);
  700. addMissing(test, Math.min(missingLevel, 50), predictorMissing, 
  701.    classMissing);
  702.       }
  703.       classifiers = Classifier.makeCopies(getClassifier(), 2);
  704.       evaluationB = new Evaluation(train);
  705.       evaluationI = new Evaluation(train);
  706.       classifiers[0].buildClassifier(train);
  707.       testWRTZeroR(classifiers[0], evaluationB, train, test);
  708.     } catch (Exception ex) {
  709.       throw new Error("Error setting up for tests: " + ex.getMessage());
  710.     }
  711.     try {
  712.       classifiers[1].buildClassifier(new Instances(train, 0));
  713.       for (int i = 0; i < train.numInstances(); i++) {
  714. ((UpdateableClassifier)classifiers[1]).updateClassifier(
  715.              train.instance(i));
  716.       }
  717.       built = true;
  718.       testWRTZeroR(classifiers[1], evaluationI, train, test);
  719.       if (!evaluationB.equals(evaluationI)) {
  720. System.out.println("no");
  721. if (m_Debug) {
  722.   System.out.println("n=== Full Report ===");
  723.   System.out.println("Results differ between batch and "
  724.      + "incrementally built models.n"
  725.      + "Depending on the classifier, this may be OK");
  726.   System.out.println("Here are the results:n");
  727.   System.out.println(evaluationB.toSummaryString(
  728.      "nbatch built resultsn", true));
  729.   System.out.println(evaluationI.toSummaryString(
  730.                              "nincrementally built resultsn", true));
  731.   System.out.println("Here are the datasets:n");
  732.   System.out.println("=== Train Dataset ===n"
  733.      + train.toString() + "n");
  734.   System.out.println("=== Test Dataset ===n"
  735.      + test.toString() + "nn");
  736. }
  737. return false;
  738.       }
  739.       System.out.println("yes");
  740.       return true;
  741.     } catch (Exception ex) {
  742.       System.out.print("Problem during");
  743.       if (built) {
  744. System.out.print(" testing");
  745.       } else {
  746. System.out.print(" training");
  747.       }
  748.       System.out.println(": " + ex.getMessage() + "n");
  749.     }
  750.     return false;
  751.   }
  752.   /**
  753.    * Checks whether the classifier erroneously uses the class
  754.    * value of test instances (if provided). Runs the classifier with
  755.    * test instance class values set to missing and compares with results
  756.    * when test instance class values are left intact.
  757.    *
  758.    * @param nominalPredictor if true use nominal predictor attributes
  759.    * @param numericPredictor if true use numeric predictor attributes
  760.    * @param numericClass if true use a numeric class attribute otherwise a
  761.    * nominal class attribute
  762.    * @return true if the test was passed
  763.    */
  764.   protected boolean doesntUseTestClassVal(boolean nominalPredictor,
  765.   boolean numericPredictor, 
  766.   boolean numericClass) {
  767.     System.out.print("classifier ignores test instance class vals");
  768.     printAttributeSummary(nominalPredictor, numericPredictor, numericClass);
  769.     System.out.print("...");
  770.     int numTrain = 40, numTest = 20, numClasses = 2, missingLevel = 0;
  771.     boolean predictorMissing = false, classMissing = false;
  772.     Instances train = null;
  773.     Instances test = null;
  774.     Classifier [] classifiers = null;
  775.     Evaluation evaluationB = null;
  776.     Evaluation evaluationI = null;
  777.     boolean evalFail = false;
  778.     try {
  779.       train = makeTestDataset(43, numTrain, 
  780.       nominalPredictor ? 3 : 0,
  781.       numericPredictor ? 2 : 0, 
  782.       numClasses, 
  783.       numericClass);
  784.       test = makeTestDataset(24, numTest,
  785.      nominalPredictor ? 3 : 0,
  786.      numericPredictor ? 2 : 0, 
  787.      numClasses, 
  788.      numericClass);
  789.       if (nominalPredictor) {
  790. train.deleteAttributeAt(0);
  791. test.deleteAttributeAt(0);
  792.       }
  793.       if (missingLevel > 0) {
  794. addMissing(train, missingLevel, predictorMissing, classMissing);
  795. addMissing(test, Math.min(missingLevel, 50), predictorMissing, 
  796.    classMissing);
  797.       }
  798.       classifiers = Classifier.makeCopies(getClassifier(), 2);
  799.       evaluationB = new Evaluation(train);
  800.       evaluationI = new Evaluation(train);
  801.       classifiers[0].buildClassifier(train);
  802.       classifiers[1].buildClassifier(train);
  803.     } catch (Exception ex) {
  804.       throw new Error("Error setting up for tests: " + ex.getMessage());
  805.     }
  806.     try {
  807.       // Now set test values to missing when predicting
  808.       for (int i = 0; i < test.numInstances(); i++) {
  809. Instance testInst = test.instance(i);
  810. Instance classMissingInst = (Instance)testInst.copy();
  811.         classMissingInst.setDataset(test);
  812. classMissingInst.setClassMissing();
  813. if (classifiers[0] instanceof DistributionClassifier) {
  814.   double [] dist0 = ((DistributionClassifier)classifiers[0]).
  815.     distributionForInstance(testInst);
  816.   double [] dist1 = ((DistributionClassifier)classifiers[1]).
  817.     distributionForInstance(classMissingInst);
  818.   for (int j = 0; j < dist0.length; j++) {
  819.     if (dist0[j] != dist1[j]) {
  820.       throw new Exception("Prediction different for instance " 
  821.   + (i + 1));
  822.     }
  823.   }
  824. } else {
  825.   double pred0 = classifiers[0].classifyInstance(testInst);
  826.   double pred1 = classifiers[1].classifyInstance(classMissingInst);
  827.   if (pred0 != pred1) {
  828.     throw new Exception("Prediction different for instance " 
  829. + (i + 1));
  830.   }
  831. }
  832.       }
  833.       System.out.println("yes");
  834.       return true;
  835.     } catch (Exception ex) {
  836.       System.out.println("no");
  837.       if (m_Debug) {
  838. System.out.println("n=== Full Report ===");
  839. if (evalFail) {
  840.   System.out.println("Results differ between non-missing and "
  841.      + "missing test class values.");
  842. } else {
  843.   System.out.print("Problem during testing");
  844.   System.out.println(": " + ex.getMessage() + "n");
  845. }
  846. System.out.println("Here are the datasets:n");
  847. System.out.println("=== Train Dataset ===n"
  848.    + train.toString() + "n");
  849. System.out.println("=== Train Weights ===n");
  850. for (int i = 0; i < train.numInstances(); i++) {
  851.   System.out.println(" " + (i + 1) 
  852.      + "    " + train.instance(i).weight());
  853. }
  854. System.out.println("=== Test Dataset ===n"
  855.    + test.toString() + "nn");
  856. System.out.println("(test weights all 1.0n");
  857.       }
  858.     }
  859.     return false;
  860.   }
  861.   /**
  862.    * Checks whether the classifier can handle instance weights.
  863.    * This test compares the classifier performance on two datasets
  864.    * that are identical except for the training weights. If the 
  865.    * results change, then the classifier must be using the weights. It
  866.    * may be possible to get a false positive from this test if the 
  867.    * weight changes aren't significant enough to induce a change
  868.    * in classifier performance (but the weights are chosen to minimize
  869.    * the likelihood of this).
  870.    *
  871.    * @param nominalPredictor if true use nominal predictor attributes
  872.    * @param numericPredictor if true use numeric predictor attributes
  873.    * @param numericClass if true use a numeric class attribute otherwise a
  874.    * nominal class attribute
  875.    * @return true if the test was passed
  876.    */
  877.   protected boolean instanceWeights(boolean nominalPredictor,
  878.     boolean numericPredictor, 
  879.     boolean numericClass) {
  880.     System.out.print("classifier uses instance weights");
  881.     printAttributeSummary(nominalPredictor, numericPredictor, numericClass);
  882.     System.out.print("...");
  883.     int numTrain = 40, numTest = 20, numClasses = 2, missingLevel = 0;
  884.     boolean predictorMissing = false, classMissing = false;
  885.     Instances train = null;
  886.     Instances test = null;
  887.     Classifier [] classifiers = null;
  888.     Evaluation evaluationB = null;
  889.     Evaluation evaluationI = null;
  890.     boolean built = false;
  891.     boolean evalFail = false;
  892.     try {
  893.       train = makeTestDataset(43, numTrain, 
  894.       nominalPredictor ? 3 : 0,
  895.       numericPredictor ? 2 : 0, 
  896.       numClasses, 
  897.       numericClass);
  898.       test = makeTestDataset(24, numTest,
  899.      nominalPredictor ? 3 : 0,
  900.      numericPredictor ? 2 : 0, 
  901.      numClasses, 
  902.      numericClass);
  903.       if (nominalPredictor) {
  904. train.deleteAttributeAt(0);
  905. test.deleteAttributeAt(0);
  906.       }
  907.       if (missingLevel > 0) {
  908. addMissing(train, missingLevel, predictorMissing, classMissing);
  909. addMissing(test, Math.min(missingLevel, 50), predictorMissing, 
  910.    classMissing);
  911.       }
  912.       classifiers = Classifier.makeCopies(getClassifier(), 2);
  913.       evaluationB = new Evaluation(train);
  914.       evaluationI = new Evaluation(train);
  915.       classifiers[0].buildClassifier(train);
  916.       testWRTZeroR(classifiers[0], evaluationB, train, test);
  917.     } catch (Exception ex) {
  918.       throw new Error("Error setting up for tests: " + ex.getMessage());
  919.     }
  920.     try {
  921.       // Now modify instance weights and re-built/test
  922.       for (int i = 0; i < train.numInstances(); i++) {
  923. train.instance(i).setWeight(0);
  924.       }
  925.       Random random = new Random(1);
  926.       for (int i = 0; i < train.numInstances() / 2; i++) {
  927. int inst = Math.abs(random.nextInt()) % train.numInstances();
  928. int weight = Math.abs(random.nextInt()) % 10 + 1;
  929. train.instance(inst).setWeight(weight);
  930.       }
  931.       classifiers[1].buildClassifier(train);
  932.       built = true;
  933.       testWRTZeroR(classifiers[1], evaluationI, train, test);
  934.       if (evaluationB.equals(evaluationI)) {
  935. // System.out.println("no");
  936. evalFail = true;
  937. throw new Exception("evalFail");
  938.       }
  939.       System.out.println("yes");
  940.       return true;
  941.     } catch (Exception ex) {
  942.       System.out.println("no");
  943.       if (m_Debug) {
  944. System.out.println("n=== Full Report ===");
  945. if (evalFail) {
  946.   System.out.println("Results don't differ between non-weighted and "
  947.      + "weighted instance models.");
  948.   System.out.println("Here are the results:n");
  949.   System.out.println(evaluationB.toSummaryString("nboth methodsn",
  950.  true));
  951. } else {
  952.   System.out.print("Problem during");
  953.   if (built) {
  954.     System.out.print(" testing");
  955.   } else {
  956.     System.out.print(" training");
  957.   }
  958.   System.out.println(": " + ex.getMessage() + "n");
  959. }
  960. System.out.println("Here are the datasets:n");
  961. System.out.println("=== Train Dataset ===n"
  962.    + train.toString() + "n");
  963. System.out.println("=== Train Weights ===n");
  964. for (int i = 0; i < train.numInstances(); i++) {
  965.   System.out.println(" " + (i + 1) 
  966.      + "    " + train.instance(i).weight());
  967. }
  968. System.out.println("=== Test Dataset ===n"
  969.    + test.toString() + "nn");
  970. System.out.println("(test weights all 1.0n");
  971.       }
  972.     }
  973.     return false;
  974.   }
  975.   /**
  976.    * Checks whether the scheme alters the training dataset during
  977.    * training. If the scheme needs to modify the training
  978.    * data it should take a copy of the training data. Currently checks
  979.    * for changes to header structure, number of instances, order of
  980.    * instances, instance weights.
  981.    *
  982.    * @param nominalPredictor if true use nominal predictor attributes
  983.    * @param numericPredictor if true use numeric predictor attributes
  984.    * @param numericClass if true use a numeric class attribute otherwise a
  985.    * nominal class attribute
  986.    * @param predictorMissing true if we know the classifier can handle
  987.    * (at least) moderate missing predictor values
  988.    * @param classMissing true if we know the classifier can handle
  989.    * (at least) moderate missing class values
  990.    * @return true if the test was passed
  991.    */
  992.   protected boolean datasetIntegrity(boolean nominalPredictor,
  993.      boolean numericPredictor, 
  994.      boolean numericClass,
  995.      boolean predictorMissing,
  996.      boolean classMissing) {
  997.     System.out.print("classifier doesn't alter original datasets");
  998.     printAttributeSummary(nominalPredictor, numericPredictor, numericClass);
  999.     System.out.print("...");
  1000.     int numTrain = 20, numTest = 20, numClasses = 2, missingLevel = 20;
  1001.     Instances train = null;
  1002.     Instances test = null;
  1003.     Classifier classifier = null;
  1004.     Evaluation evaluation = null;
  1005.     boolean built = false;
  1006.     try {
  1007.       train = makeTestDataset(42, numTrain, 
  1008.       nominalPredictor ? 2 : 0,
  1009.       numericPredictor ? 1 : 0, 
  1010.       numClasses, 
  1011.       numericClass);
  1012.       test = makeTestDataset(24, numTest,
  1013.      nominalPredictor ? 2 : 0,
  1014.      numericPredictor ? 1 : 0, 
  1015.      numClasses, 
  1016.      numericClass);
  1017.       if (nominalPredictor) {
  1018. train.deleteAttributeAt(0);
  1019. test.deleteAttributeAt(0);
  1020.       }
  1021.       if (missingLevel > 0) {
  1022. addMissing(train, missingLevel, predictorMissing, classMissing);
  1023. addMissing(test, Math.min(missingLevel, 50), predictorMissing, 
  1024.    classMissing);
  1025.       }
  1026.       classifier = Classifier.makeCopies(getClassifier(), 1)[0];
  1027.       evaluation = new Evaluation(train);
  1028.     } catch (Exception ex) {
  1029.       throw new Error("Error setting up for tests: " + ex.getMessage());
  1030.     }
  1031.     try {
  1032.       Instances trainCopy = new Instances(train);
  1033.       Instances testCopy = new Instances(test);
  1034.       classifier.buildClassifier(trainCopy);
  1035.       compareDatasets(train, trainCopy);
  1036.       built = true;
  1037.       testWRTZeroR(classifier, evaluation, trainCopy, testCopy);
  1038.       compareDatasets(test, testCopy);
  1039.       System.out.println("yes");
  1040.       return true;
  1041.     } catch (Exception ex) {
  1042.       System.out.println("no");
  1043.       if (m_Debug) {
  1044. System.out.println("n=== Full Report ===");
  1045. System.out.print("Problem during");
  1046. if (built) {
  1047.   System.out.print(" testing");
  1048. } else {
  1049.   System.out.print(" training");
  1050. }
  1051. System.out.println(": " + ex.getMessage() + "n");
  1052. System.out.println("Here are the datasets:n");
  1053. System.out.println("=== Train Dataset ===n"
  1054.    + train.toString() + "n");
  1055. System.out.println("=== Test Dataset ===n"
  1056.    + test.toString() + "nn");
  1057.       }
  1058.     }
  1059.     return false;
  1060.   }
  1061.   /**
  1062.    * Runs a text on the datasets with the given characteristics.
  1063.    */
  1064.   protected boolean runBasicTest(boolean nominalPredictor,
  1065.  boolean numericPredictor, 
  1066.  boolean numericClass,
  1067.  int missingLevel,
  1068.  boolean predictorMissing,
  1069.  boolean classMissing,
  1070.  int numTrain,
  1071.  int numTest,
  1072.  int numClasses,
  1073.  FastVector accepts) {
  1074.     Instances train = null;
  1075.     Instances test = null;
  1076.     Classifier classifier = null;
  1077.     Evaluation evaluation = null;
  1078.     boolean built = false;
  1079.     try {
  1080.       train = makeTestDataset(42, numTrain, 
  1081.       nominalPredictor ? 2 : 0,
  1082.       numericPredictor ? 1 : 0, 
  1083.       numClasses, 
  1084.       numericClass);
  1085.       test = makeTestDataset(24, numTest,
  1086.      nominalPredictor ? 2 : 0,
  1087.      numericPredictor ? 1 : 0, 
  1088.      numClasses, 
  1089.      numericClass);
  1090.       if (nominalPredictor) {
  1091. train.deleteAttributeAt(0);
  1092. test.deleteAttributeAt(0);
  1093.       }
  1094.       if (missingLevel > 0) {
  1095. addMissing(train, missingLevel, predictorMissing, classMissing);
  1096. addMissing(test, Math.min(missingLevel, 50), predictorMissing, 
  1097.    classMissing);
  1098.       }
  1099.       classifier = Classifier.makeCopies(getClassifier(), 1)[0];
  1100.       evaluation = new Evaluation(train);
  1101.     } catch (Exception ex) {
  1102.       throw new Error("Error setting up for tests: " + ex.getMessage());
  1103.     }
  1104.     try {
  1105.       classifier.buildClassifier(train);
  1106.       built = true;
  1107.       if (!testWRTZeroR(classifier, evaluation, train, test)) {
  1108. throw new Exception("Scheme performs worse than ZeroR");
  1109.       }
  1110.       System.out.println("yes");
  1111.       return true;
  1112.     } catch (Exception ex) {
  1113.       boolean acceptable = false;
  1114.       String msg = ex.getMessage().toLowerCase();
  1115.       if (msg.indexOf("worse than zeror") >= 0) {
  1116. System.out.println("warning: performs worse than ZeroR");
  1117.       } else {
  1118. for (int i = 0; i < accepts.size(); i++) {
  1119.   if (msg.indexOf((String)accepts.elementAt(i)) >= 0) {
  1120.     acceptable = true;
  1121.   }
  1122. }
  1123. System.out.println("no" + (acceptable ? " (OK error message)" : ""));
  1124.       }
  1125.       if (m_Debug) {
  1126. System.out.println("n=== Full Report ===");
  1127. System.out.print("Problem during");
  1128. if (built) {
  1129.   System.out.print(" testing");
  1130. } else {
  1131.   System.out.print(" training");
  1132. }
  1133. System.out.println(": " + ex.getMessage() + "n");
  1134. if (!acceptable) {
  1135.   if (accepts.size() > 0) {
  1136.     System.out.print("Error message doesn't mention ");
  1137.     for (int i = 0; i < accepts.size(); i++) {
  1138.       if (i != 0) {
  1139. System.out.print(" or ");
  1140.       }
  1141.       System.out.print('"' + (String)accepts.elementAt(i) + '"');
  1142.     }
  1143.   }
  1144.   System.out.println("here are the datasets:n");
  1145.   System.out.println("=== Train Dataset ===n"
  1146.      + train.toString() + "n");
  1147.   System.out.println("=== Test Dataset ===n"
  1148.      + test.toString() + "nn");
  1149. }
  1150.       }
  1151.     }
  1152.     return false;
  1153.   }
  1154.   /**
  1155.    * Determine whether the scheme performs worse than ZeroR during testing
  1156.    *
  1157.    * @param classifier the pre-trained classifier
  1158.    * @param evaluation the classifier evaluation object
  1159.    * @param train the training data
  1160.    * @param test the test data
  1161.    * @return true if the scheme performs better than ZeroR
  1162.    * @exception Exception if there was a problem during the scheme's testing
  1163.    */
  1164.   protected boolean testWRTZeroR(Classifier classifier,
  1165.  Evaluation evaluation,
  1166.  Instances train, Instances test) 
  1167.     throws Exception {
  1168.  
  1169.     evaluation.evaluateModel(classifier, test);
  1170.     try {
  1171.       // Tested OK, compare with ZeroR
  1172.       Classifier zeroR = new weka.classifiers.rules.ZeroR();
  1173.       zeroR.buildClassifier(train);
  1174.       Evaluation zeroREval = new Evaluation(train);
  1175.       zeroREval.evaluateModel(zeroR, test);
  1176.       return Utils.grOrEq(zeroREval.errorRate(), evaluation.errorRate());
  1177.     } catch (Exception ex) {
  1178.       throw new Error("Problem determining ZeroR performance: "
  1179.       + ex.getMessage());
  1180.     }
  1181.   }
  1182.   /**
  1183.    * Compare two datasets to see if they differ.
  1184.    *
  1185.    * @param data1 one set of instances
  1186.    * @param data2 the other set of instances
  1187.    * @exception Exception if the datasets differ
  1188.    */
  1189.   protected void compareDatasets(Instances data1, Instances data2)
  1190.     throws Exception {
  1191.     if (!data2.equalHeaders(data1)) {
  1192.       throw new Exception("header has been modified");
  1193.     }
  1194.     if (!(data2.numInstances() == data1.numInstances())) {
  1195.       throw new Exception("number of instances has changed");
  1196.     }
  1197.     for (int i = 0; i < data2.numInstances(); i++) {
  1198.       Instance orig = data1.instance(i);
  1199.       Instance copy = data2.instance(i);
  1200.       for (int j = 0; j < orig.numAttributes(); j++) {
  1201. if (orig.isMissing(j)) {
  1202.   if (!copy.isMissing(j)) {
  1203.     throw new Exception("instances have changed");
  1204.   }
  1205. } else if (orig.value(j) != copy.value(j)) {
  1206.     throw new Exception("instances have changed");
  1207. }
  1208. if (orig.weight() != copy.weight()) {
  1209.   throw new Exception("instance weights have changed");
  1210. }   
  1211.       }
  1212.     }
  1213.   }
  1214.   /**
  1215.    * Add missing values to a dataset.
  1216.    *
  1217.    * @param data the instances to add missing values to
  1218.    * @param level the level of missing values to add (if positive, this
  1219.    * is the probability that a value will be set to missing, if negative
  1220.    * all but one value will be set to missing (not yet implemented))
  1221.    * @param predictorMissing if true, predictor attributes will be modified
  1222.    * @param classMissing if true, the class attribute will be modified
  1223.    */
  1224.   protected void addMissing(Instances data, int level,
  1225.     boolean predictorMissing, boolean classMissing) {
  1226.     int classIndex = data.classIndex();
  1227.     Random random = new Random(1);
  1228.     for (int i = 0; i < data.numInstances(); i++) {
  1229.       Instance current = data.instance(i);
  1230.       for (int j = 0; j < data.numAttributes(); j++) {
  1231. if (((j == classIndex) && classMissing) ||
  1232.     ((j != classIndex) && predictorMissing)) {
  1233.   if (Math.abs(random.nextInt()) % 100 < level)
  1234.     current.setMissing(j);
  1235. }
  1236.       }
  1237.     }
  1238.   }
  1239.   /**
  1240.    * Make a simple set of instances, which can later be modified
  1241.    * for use in specific tests.
  1242.    *
  1243.    * @param seed the random number seed
  1244.    * @param numInstances the number of instances to generate
  1245.    * @param numNominal the number of nominal attributes
  1246.    * @param numNumeric the number of numeric attributes
  1247.    * @param numClasses the number of classes (if nominal class)
  1248.    * @param numericClass true if the class attribute should be numeric
  1249.    * @return the test dataset
  1250.    * @exception Exception if the dataset couldn't be generated
  1251.    */
  1252.   protected Instances makeTestDataset(int seed, int numInstances, 
  1253.       int numNominal, int numNumeric, 
  1254.       int numClasses, boolean numericClass)
  1255.     throws Exception {
  1256.     int numAttributes = numNominal + numNumeric + 1;
  1257.     Random random = new Random(seed);
  1258.     FastVector attributes = new FastVector(numAttributes);
  1259.     // Add Nominal attributes
  1260.     for (int i = 0; i < numNominal; i++) {
  1261.       FastVector nomStrings = new FastVector(i + 1);
  1262.       for(int j = 0; j <= i; j++) {
  1263. nomStrings.addElement("a" + (i + 1) + "l" + (j + 1));
  1264.       }
  1265.       attributes.addElement(new Attribute("Nominal" + (i + 1), nomStrings));
  1266.     }
  1267.     // Add Numeric attributes
  1268.     for (int i = 0; i < numNumeric; i++) {
  1269.       attributes.addElement(new Attribute("Numeric" + (i + 1)));
  1270.     }
  1271.     // TODO: Add some String attributes...
  1272.     // Add class attribute
  1273.     if (numericClass) {
  1274.       attributes.addElement(new Attribute("Class"));
  1275.     } else {
  1276.       FastVector nomStrings = new FastVector();
  1277.       for(int j = 0; j <numClasses; j++) {
  1278. nomStrings.addElement("cl" + (j + 1));
  1279.       }
  1280.       attributes.addElement(new Attribute("Class",nomStrings));
  1281.     }    
  1282.     Instances data = new Instances("CheckSet", attributes, numInstances);
  1283.     data.setClassIndex(data.numAttributes() - 1);
  1284.     // Generate the instances
  1285.     for (int i = 0; i < numInstances; i++) {
  1286.       Instance current = new Instance(numAttributes);
  1287.       current.setDataset(data);
  1288.       if (numericClass) {
  1289. current.setClassValue(random.nextFloat() * 0.25
  1290.       + Math.abs(random.nextInt())
  1291.       % Math.max(2, numNominal));
  1292.       } else {
  1293. current.setClassValue(Math.abs(random.nextInt()) % data.numClasses());
  1294.       }
  1295.       double classVal = current.classValue();
  1296.       double newVal = 0;
  1297.       for (int j = 0; j < numAttributes - 1; j++) {
  1298. switch (data.attribute(j).type()) {
  1299. case Attribute.NUMERIC:
  1300.   newVal = classVal * 4 + random.nextFloat() * 1 - 0.5;
  1301.   current.setValue(j, newVal);
  1302.   break;
  1303. case Attribute.NOMINAL:
  1304.   if (random.nextFloat() < 0.2) {
  1305.     newVal = Math.abs(random.nextInt())
  1306.       % data.attribute(j).numValues();
  1307.   } else {
  1308.     newVal = ((int)classVal) % data.attribute(j).numValues();
  1309.   }
  1310.   current.setValue(j, newVal);
  1311.   break;
  1312. case Attribute.STRING:
  1313.   System.err.println("Huh? this bit isn't implemented yet");
  1314.   break;
  1315. }
  1316.       }
  1317.       data.add(current);
  1318.     }
  1319.     return data;
  1320.   }
  1321.   /**
  1322.    * Print out a short summary string for the dataset characteristics
  1323.    *
  1324.    * @param nominalPredictor true if nominal predictor attributes are present
  1325.    * @param numericPredictor true if numeric predictor attributes are present
  1326.    * @param numericClass true if the class attribute is numeric
  1327.    */
  1328.   protected void printAttributeSummary(boolean nominalPredictor, 
  1329.        boolean numericPredictor, 
  1330.        boolean numericClass) {
  1331.     
  1332.     if (numericClass) {
  1333.       System.out.print(" (numeric class,");
  1334.     } else {
  1335.       System.out.print(" (nominal class,");
  1336.     }
  1337.     if (numericPredictor) {
  1338.       System.out.print(" numeric");
  1339.       if (nominalPredictor) {
  1340. System.out.print(" &");
  1341.       }
  1342.     }
  1343.     if (nominalPredictor) {
  1344.       System.out.print(" nominal");
  1345.     }
  1346.     System.out.print(" predictors)");
  1347.   }
  1348. }