Cobweb.java
Upload User: rhdiban
Upload Date: 2013-08-09
Package Size: 15085k
Code Size: 28k
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.  *    Cobweb.java
  18.  *    Copyright (C) 2001 Mark Hall
  19.  *
  20.  */
  21. package weka.clusterers;
  22. import java.io.*;
  23. import java.util.*; 
  24. import weka.core.*; 
  25. import weka.filters.Filter;
  26. import weka.filters.unsupervised.attribute.Add;
  27. import weka.experiment.Stats;
  28. /**
  29.  * Class implementing the Cobweb and Classit clustering algorithms.<p><p>
  30.  *
  31.  * Note: the application of node operators (merging, splitting etc.) in
  32.  * terms of ordering and priority differs (and is somewhat ambiguous)
  33.  * between the original Cobweb and Classit papers. This algorithm always
  34.  * compares the best host, adding a new leaf, merging the two best hosts, and
  35.  * splitting the best host when considering where to place a new instance.<p>
  36.  *
  37.  * Valid options are:<p>
  38.  *
  39.  * -A <acuity> <br>
  40.  * Acuity. <p>
  41.  *
  42.  * -C <cutoff> <br>
  43.  * Cutoff. <p>
  44.  *
  45.  * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
  46.  * @version $Revision: 1.15 $
  47.  * @see Clusterer
  48.  * @see OptionHandler
  49.  * @see Drawable
  50.  */
  51. public class Cobweb extends Clusterer implements OptionHandler, Drawable {
  52.   /**
  53.    * Inner class handling node operations for Cobweb.
  54.    *
  55.    * @see Serializable
  56.    */
  57.   private class CNode implements Serializable {
  58.     
  59.     /**
  60.      * Within cluster attribute statistics
  61.      */
  62.     private AttributeStats [] m_attStats;
  63.     /**
  64.      * Number of attributes
  65.      */
  66.     private int m_numAttributes;
  67.     
  68.     /**
  69.      * Instances at this node
  70.      */
  71.     protected Instances m_clusterInstances = null;
  72.     /**
  73.      * Children of this node
  74.      */
  75.     private FastVector m_children = null;
  76.     /**
  77.      * Total instances at this node
  78.      */
  79.     private double m_totalInstances = 0.0;
  80.     /**
  81.      * Cluster number of this node
  82.      */
  83.     private int m_clusterNum = -1;
  84.     /**
  85.      * Creates an empty <code>CNode</code> instance.
  86.      *
  87.      * @param numAttributes the number of attributes in the data
  88.      */
  89.     public CNode(int numAttributes) {      
  90.       m_numAttributes = numAttributes;
  91.     }
  92.     /**
  93.      * Creates a new leaf <code>CNode</code> instance.
  94.      *
  95.      * @param numAttributes the number of attributes in the data
  96.      * @param leafInstance the instance to store at this leaf
  97.      */
  98.     public CNode(int numAttributes, Instance leafInstance) {
  99.       this(numAttributes);
  100.       if (m_clusterInstances == null) {
  101. m_clusterInstances = new Instances(leafInstance.dataset(), 1);
  102.       }
  103.       m_clusterInstances.add(leafInstance);
  104.       updateStats(leafInstance, false);
  105.     }
  106.     
  107.     /**
  108.      * Adds an instance to this cluster.
  109.      *
  110.      * @param newInstance the instance to add
  111.      * @exception Exception if an error occurs
  112.      */
  113.     protected void addInstance(Instance newInstance) throws Exception {
  114.       // Add the instance to this cluster
  115.       if (m_clusterInstances == null) {
  116. m_clusterInstances = new Instances(newInstance.dataset(), 1);
  117. m_clusterInstances.add(newInstance);
  118. updateStats(newInstance, false);
  119. return;
  120.       } else if (m_children == null) {
  121. /* we are a leaf, so make our existing instance(s) into a child
  122.    and then add the new instance as a child */
  123. m_children = new FastVector();
  124. CNode tempSubCluster = new CNode(m_numAttributes, 
  125.  m_clusterInstances.instance(0)); 
  126. // System.out.println("Dumping "+m_clusterInstances.numInstances());
  127. for (int i = 1; i < m_clusterInstances.numInstances(); i++) {
  128.   tempSubCluster.m_clusterInstances.
  129.     add(m_clusterInstances.instance(i));
  130.   tempSubCluster.updateStats(m_clusterInstances.instance(i), false);
  131. }
  132. m_children = new FastVector();
  133. m_children.addElement(tempSubCluster);
  134. m_children.addElement(new CNode(m_numAttributes, newInstance));
  135. m_clusterInstances.add(newInstance);
  136. updateStats(newInstance, false);
  137. // here is where we check against cutoff (also check cutoff
  138. // in findHost)
  139. if (categoryUtility() < m_cutoff) {
  140.   //   System.out.println("Cutting (leaf add) ");
  141.   m_children = null;
  142. }
  143. return;
  144.       }
  145.       
  146.       // otherwise, find the best host for this instance
  147.       CNode bestHost = findHost(newInstance, false);
  148.       if (bestHost != null) {
  149. // now add to the best host
  150. bestHost.addInstance(newInstance);
  151.       }
  152.     }
  153.     /**
  154.      * Temporarily adds a new instance to each of this nodes children
  155.      * in turn and computes the category utility.
  156.      *
  157.      * @param newInstance the new instance to evaluate
  158.      * @return an array of category utility values---the result of considering
  159.      * each child in turn as a host for the new instance
  160.      * @exception Exception if an error occurs
  161.      */
  162.     private double [] cuScoresForChildren(Instance newInstance) 
  163.       throws Exception {
  164.       // look for a host in existing children
  165.       double [] categoryUtils = new double [m_children.size()];
  166.       
  167.       // look for a home for this instance in the existing children
  168.       for (int i = 0; i < m_children.size(); i++) {
  169. CNode temp = (CNode) m_children.elementAt(i);
  170. // tentitively add the new instance to this child
  171. temp.updateStats(newInstance, false);
  172. categoryUtils[i] = categoryUtility();
  173. // remove the new instance from this child
  174. temp.updateStats(newInstance, true);
  175.       }
  176.       return categoryUtils;
  177.     }
  178.     private double cuScoreForBestTwoMerged(CNode merged, 
  179.    CNode a, CNode b,
  180.    Instance newInstance) 
  181.       throws Exception {
  182.       double mergedCU = -Double.MAX_VALUE;
  183.       // consider merging the best and second
  184.       // best.
  185.       merged.m_clusterInstances = new Instances(m_clusterInstances, 1);
  186.       
  187.       merged.addChildNode(a);
  188.       merged.addChildNode(b);
  189.       merged.updateStats(newInstance, false); // add new instance to stats
  190.       // remove the best and second best nodes
  191.       m_children.removeElementAt(m_children.indexOf(a));
  192.       m_children.removeElementAt(m_children.indexOf(b));
  193.       m_children.addElement(merged);
  194.       mergedCU = categoryUtility();
  195.       // restore the status quo
  196.       merged.updateStats(newInstance, true);
  197.       m_children.removeElementAt(m_children.indexOf(merged));
  198.       m_children.addElement(a);
  199.       m_children.addElement(b);
  200.       return mergedCU;
  201.     }
  202.     /**
  203.      * Finds a host for the new instance in this nodes children. Also
  204.      * considers merging the two best hosts and splitting the best host.
  205.      *
  206.      * @param newInstance the instance to find a host for
  207.      * @param structureFrozen true if the instance is not to be added to
  208.      * the tree and instead the best potential host is to be returned
  209.      * @return the best host
  210.      * @exception Exception if an error occurs
  211.      */
  212.     private CNode findHost(Instance newInstance, 
  213.    boolean structureFrozen) throws Exception {
  214.       if (!structureFrozen) {
  215. updateStats(newInstance, false);
  216.       }
  217.       
  218.       // look for a host in existing children and also consider as a new leaf
  219.       double [] categoryUtils = cuScoresForChildren(newInstance);
  220.       
  221.       // make a temporary new leaf for this instance and get CU
  222.       CNode newLeaf = new CNode(m_numAttributes, newInstance);
  223.       m_children.addElement(newLeaf);
  224.       double bestHostCU = categoryUtility();
  225.       CNode finalBestHost = newLeaf;
  226.       
  227.       // remove new leaf when seaching for best and second best nodes to
  228.       // consider for merging and splitting
  229.       m_children.removeElementAt(m_children.size()-1);
  230.       // now determine the best host (and the second best)
  231.       int best = 0;
  232.       int secondBest = 0;
  233.       for (int i = 0; i < categoryUtils.length; i++) {
  234. if (categoryUtils[i] > categoryUtils[secondBest]) {
  235.   if (categoryUtils[i] > categoryUtils[best]) {
  236.     secondBest = best;
  237.     best = i;
  238.   } else {
  239.     secondBest = i;
  240.   }
  241.       }
  242.       
  243.       CNode a = (CNode) m_children.elementAt(best);
  244.       CNode b = (CNode) m_children.elementAt(secondBest);
  245.       if (categoryUtils[best] > bestHostCU) {
  246. bestHostCU = categoryUtils[best];
  247. finalBestHost = a;
  248. // System.out.println("Node is best");
  249.       }
  250.       if (structureFrozen) {
  251. if (finalBestHost == newLeaf) {
  252.   return null; // *this* node is the best host
  253. } else {
  254.   return finalBestHost;
  255. }
  256.       }
  257.       double mergedCU = -Double.MAX_VALUE;
  258.       CNode merged = new CNode(m_numAttributes);
  259.       if (a != b) {
  260. mergedCU = cuScoreForBestTwoMerged(merged, a, b, newInstance);
  261. if (mergedCU > bestHostCU) {
  262.   bestHostCU = mergedCU;
  263.   finalBestHost = merged;
  264. }
  265.       }
  266.       // Consider splitting the best
  267.       double splitCU = -Double.MAX_VALUE;
  268.       double splitBestChildCU = -Double.MAX_VALUE;
  269.       double splitPlusNewLeafCU = -Double.MAX_VALUE;
  270.       double splitPlusMergeBestTwoCU = -Double.MAX_VALUE;
  271.       if (a.m_children != null) {
  272. FastVector tempChildren = new FastVector();
  273. for (int i = 0; i < m_children.size(); i++) {
  274.   CNode existingChild = (CNode)m_children.elementAt(i);
  275.   if (existingChild != a) {
  276.     tempChildren.addElement(existingChild);
  277.   }
  278. }
  279. for (int i = 0; i < a.m_children.size(); i++) {
  280.   CNode promotedChild = (CNode)a.m_children.elementAt(i);
  281.   tempChildren.addElement(promotedChild);
  282. }
  283. // also add the new leaf
  284. tempChildren.addElement(newLeaf);
  285. FastVector saveStatusQuo = m_children;
  286. m_children = tempChildren;
  287. splitPlusNewLeafCU = categoryUtility(); // split + new leaf
  288. // remove the new leaf
  289. tempChildren.removeElementAt(tempChildren.size()-1);
  290. // now look for best and second best
  291. categoryUtils = cuScoresForChildren(newInstance);
  292. // now determine the best host (and the second best)
  293. best = 0;
  294. secondBest = 0;
  295. for (int i = 0; i < categoryUtils.length; i++) {
  296.   if (categoryUtils[i] > categoryUtils[secondBest]) {
  297.     if (categoryUtils[i] > categoryUtils[best]) {
  298.       secondBest = best;
  299.       best = i;
  300.     } else {
  301.       secondBest = i;
  302.     }
  303.   } 
  304. }
  305. CNode sa = (CNode) m_children.elementAt(best);
  306. CNode sb = (CNode) m_children.elementAt(secondBest);
  307. splitBestChildCU = categoryUtils[best];
  308. // now merge best and second best
  309. CNode mergedSplitChildren = new CNode(m_numAttributes);
  310. if (sa != sb) {
  311.   splitPlusMergeBestTwoCU = 
  312.     cuScoreForBestTwoMerged(mergedSplitChildren, sa, sb, newInstance);
  313. }
  314. splitCU = (splitBestChildCU > splitPlusNewLeafCU) ?
  315.   splitBestChildCU : splitPlusNewLeafCU;
  316. splitCU = (splitCU > splitPlusMergeBestTwoCU) ? 
  317.   splitCU : splitPlusMergeBestTwoCU;
  318. if (splitCU > bestHostCU) {
  319.   bestHostCU = splitCU;
  320.   finalBestHost = this;
  321.   //   tempChildren.removeElementAt(tempChildren.size()-1);
  322. } else {
  323.   // restore the status quo
  324.   m_children = saveStatusQuo;
  325. }
  326.       }
  327.       if (finalBestHost != this) {
  328. // can commit the instance to the set of instances at this node
  329. m_clusterInstances.add(newInstance);
  330.       } else {
  331. m_numberSplits++;
  332.       }
  333.       if (finalBestHost == merged) {
  334. m_numberMerges++;
  335. m_children.removeElementAt(m_children.indexOf(a));
  336. m_children.removeElementAt(m_children.indexOf(b));
  337. m_children.addElement(merged);
  338.       }
  339.       if (finalBestHost == newLeaf) {
  340. finalBestHost = new CNode(m_numAttributes);
  341. m_children.addElement(finalBestHost);
  342.       }
  343.       if (bestHostCU < m_cutoff) {
  344. if (finalBestHost == this) {
  345.   // splitting was the best, but since we are cutting all children
  346.   // recursion is aborted and we still need to add the instance
  347.   // to the set of instances at this node
  348.   m_clusterInstances.add(newInstance);
  349. }
  350. m_children = null;
  351. finalBestHost = null;
  352.       }
  353.       if (finalBestHost == this) {
  354. // splitting is still the best, so downdate the stats as 
  355. // we'll be recursively calling on this node
  356. updateStats(newInstance, true);
  357.       }
  358.       return finalBestHost;
  359.     }
  360.     
  361.     /**
  362.      * Adds the supplied node as a child of this node. All of the child's
  363.      * instances are added to this nodes instances
  364.      *
  365.      * @param child the child to add
  366.      */
  367.     protected void addChildNode(CNode child) {
  368.       for (int i = 0; i < child.m_clusterInstances.numInstances(); i++) {
  369. Instance temp = child.m_clusterInstances.instance(i);
  370. m_clusterInstances.add(temp);
  371. updateStats(temp, false);
  372.       }
  373.       if (m_children == null) {
  374. m_children = new FastVector();
  375.       }
  376.       m_children.addElement(child);
  377.     }
  378.     /**
  379.      * Computes the utility of all children with respect to this node
  380.      *
  381.      * @return the category utility of the children with respect to this node.
  382.      */
  383.     protected double categoryUtility() throws Exception {
  384.       
  385.       if (m_children == null) {
  386. throw new Exception("categoryUtility: No children!");
  387.       }
  388.       double totalCU = 0;
  389.      
  390.       for (int i = 0; i < m_children.size(); i++) {
  391. CNode child = (CNode) m_children.elementAt(i);
  392. totalCU += categoryUtilityChild(child);
  393.       }
  394.       totalCU /= (double)m_children.size();
  395.       return totalCU;
  396.     }
  397.     /**
  398.      * Computes the utility of a single child with respect to this node
  399.      *
  400.      * @param child the child for which to compute the utility
  401.      * @return the utility of the child with respect to this node
  402.      * @exception Exception if something goes wrong
  403.      */
  404.     protected double categoryUtilityChild(CNode child) throws Exception {
  405.       
  406.       double sum = 0;
  407.       for (int i = 0; i < m_numAttributes; i++) {
  408. if (m_clusterInstances.attribute(i).isNominal()) {
  409.   for (int j = 0; 
  410.        j < m_clusterInstances.attribute(i).numValues(); j++) {
  411.     double x = child.getProbability(i, j);
  412.     double y = getProbability(i, j);
  413.     sum += (x * x) - (y * y);
  414.   }
  415. } else {
  416.   // numeric attribute
  417.   sum += ((m_normal / child.getStandardDev(i)) - 
  418.   (m_normal / getStandardDev(i)));
  419.   
  420. }
  421.       }
  422.       return (child.m_totalInstances / m_totalInstances) * sum;
  423.     }
  424.     /**
  425.      * Returns the probability of a value of a nominal attribute in this node
  426.      *
  427.      * @param attIndex the index of the attribute
  428.      * @param valueIndex the index of the value of the attribute
  429.      * @return the probability
  430.      * @exception Exception if the requested attribute is not nominal
  431.      */
  432.     protected double getProbability(int attIndex, int valueIndex) 
  433.       throws Exception {
  434.       
  435.       if (!m_clusterInstances.attribute(attIndex).isNominal()) {
  436. throw new Exception("getProbability: attribute is not nominal");
  437.       }
  438.       if (m_attStats[attIndex].totalCount <= 0) {
  439. return 0;
  440.       }
  441.       return (double) m_attStats[attIndex].nominalCounts[valueIndex] / 
  442. (double) m_attStats[attIndex].totalCount;
  443.     }
  444.     /**
  445.      * Returns the standard deviation of a numeric attribute
  446.      *
  447.      * @param attIndex the index of the attribute
  448.      * @return the standard deviation
  449.      * @exception Exception if an error occurs
  450.      */
  451.     protected double getStandardDev(int attIndex) throws Exception {
  452.       if (!m_clusterInstances.attribute(attIndex).isNumeric()) {
  453. throw new Exception("getStandardDev: attribute is not numeric");
  454.       }
  455.       m_attStats[attIndex].numericStats.calculateDerived();
  456.       double stdDev = m_attStats[attIndex].numericStats.stdDev;
  457.       if (Double.isNaN(stdDev) || Double.isInfinite(stdDev)) {
  458. return m_acuity;
  459.       }
  460.       return Math.max(m_acuity, stdDev);
  461.     }
  462.     /**
  463.      * Update attribute stats using the supplied instance. 
  464.      *
  465.      * @param updateInstance the instance for updating
  466.      * @param delete true if the values of the supplied instance are
  467.      * to be removed from the statistics
  468.      */
  469.     protected void updateStats(Instance updateInstance, 
  470.        boolean delete) {
  471.       if (m_attStats == null) {
  472. m_attStats = new AttributeStats[m_numAttributes];
  473. for (int i = 0; i < m_numAttributes; i++) {
  474.   m_attStats[i] = new AttributeStats();
  475.   if (m_clusterInstances.attribute(i).isNominal()) {
  476.     m_attStats[i].nominalCounts = 
  477.       new int [m_clusterInstances.attribute(i).numValues()];
  478.   } else {
  479.     m_attStats[i].numericStats = new Stats();
  480.   }
  481. }
  482.       }
  483.       for (int i = 0; i < m_numAttributes; i++) {
  484. if (!updateInstance.isMissing(i)) {
  485.   double value = updateInstance.value(i);
  486.   if (m_clusterInstances.attribute(i).isNominal()) {
  487.     m_attStats[i].nominalCounts[(int)value] += (delete) ? 
  488.       (-1.0 * updateInstance.weight()) : 
  489.       updateInstance.weight();
  490.     m_attStats[i].totalCount += (delete) ?
  491.       (-1.0 * updateInstance.weight()) :
  492.       updateInstance.weight();
  493.   } else {
  494.     if (delete) {
  495.       m_attStats[i].numericStats.subtract(value, 
  496.   updateInstance.weight());
  497.     } else {
  498.       m_attStats[i].numericStats.add(value, updateInstance.weight());
  499.     }
  500.   }
  501. }
  502.       }
  503.       m_totalInstances += (delete) 
  504. ? (-1.0 * updateInstance.weight()) 
  505. : (updateInstance.weight());
  506.     }
  507.     /**
  508.      * Recursively assigns numbers to the nodes in the tree.
  509.      *
  510.      * @param cl_num an <code>int[]</code> value
  511.      * @exception Exception if an error occurs
  512.      */
  513.     private void assignClusterNums(int [] cl_num) throws Exception {
  514.       if (m_children != null && m_children.size() < 2) {
  515. throw new Exception("assignClusterNums: tree not built correctly!");
  516.       }
  517.       
  518.       m_clusterNum = cl_num[0];
  519.       cl_num[0]++;
  520.       if (m_children != null) {
  521. for (int i = 0; i < m_children.size(); i++) {
  522.   CNode child = (CNode) m_children.elementAt(i);
  523.   child.assignClusterNums(cl_num);
  524. }
  525.       }
  526.     }
  527.     /**
  528.      * Recursively build a string representation of the Cobweb tree
  529.      *
  530.      * @param depth depth of this node in the tree
  531.      * @param text holds the string representation
  532.      */
  533.     protected void dumpTree(int depth, StringBuffer text) {
  534.       if (m_children == null) {
  535. text.append("n");
  536. for (int j = 0; j < depth; j++) {
  537.   text.append("|   ");
  538. }
  539. text.append("leaf "+m_clusterNum+" ["
  540.     +m_clusterInstances.numInstances()+"]");
  541.       } else {
  542. for (int i = 0; i < m_children.size(); i++) {
  543.   text.append("n");
  544.   for (int j = 0; j < depth; j++) {
  545.     text.append("|   ");
  546.   }
  547.   text.append("node "+m_clusterNum+" ["
  548.       +m_clusterInstances.numInstances()
  549.       +"]");
  550.   ((CNode) m_children.elementAt(i)).dumpTree(depth+1, text);
  551. }
  552.       }
  553.     }
  554.     /**
  555.      * Returns the instances at this node as a string. Appends the cluster
  556.      * number of the child that each instance belongs to.
  557.      *
  558.      * @return a <code>String</code> value
  559.      * @exception Exception if an error occurs
  560.      */
  561.     protected String dumpData() throws Exception {
  562.       if (m_children == null) {
  563. return m_clusterInstances.toString();
  564.       }
  565.       // construct instances string with cluster numbers attached
  566.       CNode tempNode = new CNode(m_numAttributes);
  567.       tempNode.m_clusterInstances = new Instances(m_clusterInstances, 1);
  568.       for (int i = 0; i < m_children.size(); i++) {
  569. tempNode.addChildNode((CNode)m_children.elementAt(i));
  570.       }
  571.       Instances tempInst = tempNode.m_clusterInstances;
  572.       tempNode = null;
  573.       StringBuffer instBuff = new StringBuffer();
  574.       Add af = new Add();
  575.       af.setAttributeName("Cluster");
  576.       String labels = "";
  577.       for (int i = 0; i < m_children.size(); i++) {
  578. CNode temp = (CNode)m_children.elementAt(i);
  579. labels += ("C"+temp.m_clusterNum);
  580. if (i < m_children.size()-1) {
  581.   labels+=",";
  582. }
  583.       }
  584.       af.setNominalLabels(labels);
  585.       af.setInputFormat(tempInst);
  586.       tempInst = Filter.useFilter(tempInst, af);
  587.       tempInst.setRelationName("Cluster "+m_clusterNum);
  588.       
  589.       int z = 0;
  590.       for (int i = 0; i < m_children.size(); i++) {
  591. CNode temp = (CNode)m_children.elementAt(i);
  592. for (int j = 0; j < temp.m_clusterInstances.numInstances(); j++) {
  593.   tempInst.instance(z).setValue(m_numAttributes, (double)i);
  594.   z++;
  595. }
  596.       }
  597.       return tempInst.toString();
  598.     }
  599.     /**
  600.      * Recursively generate the graph string for the Cobweb tree.
  601.      *
  602.      * @param text holds the graph string
  603.      */
  604.     protected void graphTree(StringBuffer text) throws Exception {
  605.       
  606.       text.append("N"+m_clusterNum
  607.   + " [label=""+((m_children == null) 
  608.   ? "leaf " : "node ")
  609.   +m_clusterNum+" "
  610.   +" ("+m_clusterInstances.numInstances()
  611.   +")" "
  612.   +((m_children == null) 
  613.     ? "shape=box style=filled " : "")
  614.   +(m_saveInstances 
  615.     ? "data =n"+dumpData() +"n,n"
  616.     : "")
  617.   + "]n");
  618.       if (m_children != null) {
  619. for (int i = 0; i < m_children.size(); i++) {
  620.   CNode temp = (CNode)m_children.elementAt(i);
  621.   text.append("N"+m_clusterNum
  622.       +"->"
  623.       +"N" + temp.m_clusterNum
  624.       + "n");
  625. }
  626. for (int i = 0; i < m_children.size(); i++) {
  627.   CNode temp = (CNode)m_children.elementAt(i);
  628.   temp.graphTree(text);
  629. }
  630.       }
  631.     }
  632.   }
  633.   /**
  634.    * Normal constant.
  635.    */
  636.   protected static final double m_normal = 1.0/(2 * Math.sqrt(Math.PI));
  637.   /**
  638.    * Acuity (minimum standard deviation).
  639.    */
  640.   protected double m_acuity = 1.0;
  641.   /**
  642.    * Cutoff (minimum category utility).
  643.    */
  644.   protected double m_cutoff = 0.01 * Cobweb.m_normal;
  645.   /**
  646.    * Holds the root of the Cobweb tree.
  647.    */
  648.   protected CNode m_cobwebTree = null;
  649.   /**
  650.    * Number of clusters (nodes in the tree).
  651.    */
  652.   protected int m_numberOfClusters = -1;
  653.   
  654.   protected int m_numberSplits;
  655.   protected int m_numberMerges;
  656.   /**
  657.    * Output instances in graph representation of Cobweb tree (Allows
  658.    * instances at nodes in the tree to be visualized in the Explorer).
  659.    */
  660.   protected boolean m_saveInstances = false;
  661.   /**
  662.    * Builds the clusterer.
  663.    *
  664.    * @param data the training instances.
  665.    * @exception Exception if something goes wrong.
  666.    */
  667.   public void buildClusterer(Instances data) throws Exception {
  668.     m_numberOfClusters = -1;
  669.     m_cobwebTree = null;
  670.     m_numberSplits = 0;
  671.     m_numberMerges = 0;
  672.     if (data.checkForStringAttributes()) {
  673.       throw new Exception("Can't handle string attributes!");
  674.     }
  675.     
  676.     // randomize the instances
  677.     data = new Instances(data);
  678.     data.randomize(new Random(42));
  679.     for (int i = 0; i < data.numInstances(); i++) {
  680.       addInstance(data.instance(i));
  681.     }
  682.     
  683.     int [] numClusts = new int [1];
  684.     numClusts[0] = 0;
  685.     m_cobwebTree.assignClusterNums(numClusts);
  686.     m_numberOfClusters = numClusts[0];
  687.   }
  688.   /**
  689.    * Classifies a given instance.
  690.    *
  691.    * @param instance the instance to be assigned to a cluster
  692.    * @return the number of the assigned cluster as an interger
  693.    * if the class is enumerated, otherwise the predicted value
  694.    * @exception Exception if instance could not be classified
  695.    * successfully
  696.    */
  697.   public int clusterInstance(Instance instance) throws Exception {
  698.     CNode host = m_cobwebTree;
  699.     CNode temp = null;
  700.     
  701.     do {
  702.       if (host.m_children == null) {
  703. temp = null;
  704. break;
  705.       }
  706.       host.updateStats(instance, false);
  707.       temp = host.findHost(instance, true);
  708.       host.updateStats(instance, true);
  709.       
  710.       if (temp != null) {
  711. host = temp;
  712.       }
  713.     } while (temp != null);
  714.     
  715.     return host.m_clusterNum;
  716.   }
  717.   /**
  718.    * Returns the number of clusters.
  719.    *
  720.    * @exception Exception if something goes wrong.
  721.    */
  722.   public int numberOfClusters() throws Exception {
  723.     return m_numberOfClusters;
  724.   }
  725.   /**
  726.    * Adds an instance to the Cobweb tree.
  727.    *
  728.    * @param newInstance the instance to be added
  729.    * @exception Exception if something goes wrong
  730.    */
  731.   public void addInstance(Instance newInstance) throws Exception {
  732.     if (m_cobwebTree == null) {
  733.       m_cobwebTree = new CNode(newInstance.numAttributes(), newInstance);
  734.     } else {
  735.       m_cobwebTree.addInstance(newInstance);
  736.     }
  737.   }
  738.   /**
  739.    * Returns an enumeration describing the available options.
  740.    *
  741.    * @return an enumeration of all the available options.
  742.    **/
  743.   public Enumeration listOptions() {
  744.     
  745.     Vector newVector = new Vector(2);
  746.     
  747.     newVector.addElement(new Option("tAcuity.n"
  748.     +"t(default=1.0)", "A", 1,"-A <acuity>"));
  749.     newVector.addElement(new Option("tCutoff.n"
  750.     +"at(default=0.002)", "C", 1,"-C <cutoff>"));
  751.     
  752.     return newVector.elements();
  753.   }
  754.   /**
  755.    * Parses a given list of options.
  756.    *
  757.    * Valid options are:<p>
  758.    *
  759.    * -A <acuity> <br>
  760.    * Acuity. <p>
  761.    *
  762.    * -C <cutoff> <br>
  763.    * Cutoff. <p>
  764.    *
  765.    * @param options the list of options as an array of strings
  766.    * @exception Exception if an option is not supported
  767.    *
  768.    **/
  769.   public void setOptions(String[] options) throws Exception {
  770.     String optionString;
  771.     optionString = Utils.getOption('A', options); 
  772.     if (optionString.length() != 0) {
  773.       Double temp = new Double(optionString);
  774.       setAcuity(temp.doubleValue());
  775.     }
  776.     else {
  777.       m_acuity = 1.0;
  778.     }
  779.     optionString = Utils.getOption('C', options); 
  780.     if (optionString.length() != 0) {
  781.       Double temp = new Double(optionString);
  782.       setCutoff(temp.doubleValue());
  783.     }
  784.     else {
  785.       m_cutoff = 0.01 * Cobweb.m_normal;
  786.     }
  787.   }
  788.   /**
  789.    * Returns the tip text for this property
  790.    * @return tip text for this property suitable for
  791.    * displaying in the explorer/experimenter gui
  792.    */
  793.   public String acuityTipText() {
  794.     return "set the minimum standard deviation for numeric attributes";
  795.   }
  796.   /**
  797.    * set the acuity.
  798.    * @param a the acuity value
  799.    */
  800.   public void setAcuity(double a) {
  801.     m_acuity = a;
  802.   }
  803.   /**
  804.    * get the acuity value
  805.    * @return the acuity
  806.    */
  807.   public double getAcuity() {
  808.     return m_acuity;
  809.   }
  810.   /**
  811.    * Returns the tip text for this property
  812.    * @return tip text for this property suitable for
  813.    * displaying in the explorer/experimenter gui
  814.    */
  815.   public String cutoffTipText() {
  816.     return "set the category utility threshold by which to prune nodes";
  817.   }
  818.   /**
  819.    * set the cutoff
  820.    * @param c the cutof
  821.    */
  822.   public void setCutoff(double c) {
  823.     m_cutoff = c;
  824.   }
  825.   /**
  826.    * get the cutoff
  827.    * @return the cutoff
  828.    */
  829.   public double getCutoff() {
  830.     return m_cutoff;
  831.   }
  832.   
  833.   /**
  834.    * Returns the tip text for this property
  835.    * @return tip text for this property suitable for
  836.    * displaying in the explorer/experimenter gui
  837.    */
  838.   public String saveInstanceDataTipText() {
  839.     return "save instance information for visualization purposes";
  840.   }
  841.   /**
  842.    * Get the value of saveInstances.
  843.    *
  844.    * @return Value of saveInstances.
  845.    */
  846.   public boolean getSaveInstanceData() {
  847.     
  848.     return m_saveInstances;
  849.   }
  850.   
  851.   /**
  852.    * Set the value of saveInstances.
  853.    *
  854.    * @param newsaveInstances Value to assign to saveInstances.
  855.    */
  856.   public void setSaveInstanceData(boolean newsaveInstances) {
  857.     
  858.     m_saveInstances = newsaveInstances;
  859.   }
  860.   
  861.   /**
  862.    * Gets the current settings of Cobweb.
  863.    *
  864.    * @return an array of strings suitable for passing to setOptions()
  865.    */
  866.   public String [] getOptions() {
  867.     
  868.     String [] options = new String [4];
  869.     int current = 0;
  870.     options[current++] = "-A"; 
  871.     options[current++] = "" + m_acuity;
  872.     options[current++] = "-C"; 
  873.     options[current++] = "" + m_cutoff;
  874.     while (current < options.length) {
  875.       options[current++] = "";
  876.     }
  877.     return options;
  878.   }
  879.   /**
  880.    * Returns a description of the clusterer as a string.
  881.    *
  882.    * @return a string describing the clusterer.
  883.    */
  884.   public String toString() { 
  885.     StringBuffer text = new StringBuffer();
  886.     if (m_cobwebTree == null) {
  887.       return "Cobweb hasn't been built yet!";
  888.     }
  889.     else {
  890.       m_cobwebTree.dumpTree(0, text); 
  891.       return "Number of merges: "
  892. + m_numberMerges+"nNumber of splits: "
  893. + m_numberSplits+"nNumber of clusters: "
  894. + m_numberOfClusters+"n"+text.toString()+"nn";
  895.      
  896.     }
  897.   }
  898.   /**
  899.    * Generates the graph string of the Cobweb tree
  900.    *
  901.    * @return a <code>String</code> value
  902.    * @exception Exception if an error occurs
  903.    */
  904.   public String graph() throws Exception {
  905.     StringBuffer text = new StringBuffer();
  906.     
  907.     text.append("digraph CobwebTree {n");
  908.     m_cobwebTree.graphTree(text);
  909.     text.append("}n");
  910.     return text.toString();
  911.   }
  912.   // Main method for testing this class
  913.   public static void main(String [] argv)
  914.   {
  915.     try {
  916.       System.out.println(ClusterEvaluation.evaluateClusterer(new Cobweb(), 
  917.      argv));
  918.     }
  919.     catch (Exception e)
  920.       {
  921. System.out.println(e.getMessage());
  922. e.printStackTrace();
  923.       }
  924.   }
  925. }