PrincipalComponents.java
Upload User: rhdiban
Upload Date: 2013-08-09
Package Size: 15085k
Code Size: 26k
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.  *    principalComponents.java
  18.  *    Copyright (C) 2000 Mark Hall
  19.  *
  20.  */
  21. package weka.attributeSelection;
  22. import  java.io.*;
  23. import  java.util.*;
  24. import  weka.core.*;
  25. import  weka.filters.*;
  26. /**
  27.  * Class for performing principal components analysis/transformation.
  28.  *
  29.  * @author Mark Hall (mhall@cs.waikato.ac.nz)
  30.  * @version $Revision: 1.16 $
  31.  */
  32. public class PrincipalComponents extends AttributeEvaluator 
  33.   implements AttributeTransformer, OptionHandler {
  34.   
  35.   /** The data to transform analyse/transform */
  36.   private Instances m_trainInstances;
  37.   /** Keep a copy for the class attribute (if set) */
  38.   private Instances m_trainCopy;
  39.   /** The header for the transformed data format */
  40.   private Instances m_transformedFormat;
  41.   /** The header for data transformed back to the original space */
  42.   private Instances m_originalSpaceFormat;
  43.   /** Data has a class set */
  44.   private boolean m_hasClass;
  45.   /** Class index */
  46.   private int m_classIndex;
  47.   /** Number of attributes */
  48.   private int m_numAttribs;
  49.   /** Number of instances */
  50.   private int m_numInstances;
  51.   /** Correlation matrix for the original data */
  52.   private double [][] m_correlation;
  53.   /** Will hold the unordered linear transformations of the (normalized)
  54.       original data */
  55.   private double [][] m_eigenvectors;
  56.   
  57.   /** Eigenvalues for the corresponding eigenvectors */
  58.   private double [] m_eigenvalues = null;
  59.   /** Sorted eigenvalues */
  60.   private int [] m_sortedEigens;
  61.   /** sum of the eigenvalues */
  62.   private double m_sumOfEigenValues = 0.0;
  63.   
  64.   /** Filters for original data */
  65.   private ReplaceMissingValuesFilter m_replaceMissingFilter;
  66.   private NormalizationFilter m_normalizeFilter;
  67.   private NominalToBinaryFilter m_nominalToBinFilter;
  68.   private AttributeFilter m_attributeFilter;
  69.   
  70.   /** used to remove the class column if a class column is set */
  71.   private AttributeFilter m_attribFilter;
  72.   /** The number of attributes in the pc transformed data */
  73.   private int m_outputNumAtts = -1;
  74.   
  75.   /** normalize the input data? */
  76.   private boolean m_normalize = true;
  77.   /** the amount of varaince to cover in the original data when
  78.       retaining the best n PC's */
  79.   private double m_coverVariance = 0.95;
  80.   /** transform the data through the pc space and back to the original
  81.       space ? */
  82.   private boolean m_transBackToOriginal = false;
  83.   /** holds the transposed eigenvectors for converting back to the
  84.       original space */
  85.   private double [][] m_eTranspose;
  86.   /**
  87.    * Returns a string describing this attribute transformer
  88.    * @return a description of the evaluator suitable for
  89.    * displaying in the explorer/experimenter gui
  90.    */
  91.   public String globalInfo() {
  92.     return "Performs a principal components analysis and transformation of "
  93.       +"the data. Use in conjunction with a Ranker search. Dimensionality "
  94.       +"reduction is accomplished by choosing enough eigenvectors to "
  95.       +"account for some percentage of the variance in the original data---"
  96.       +"default 0.95 (95%). Attribute noise can be filtered by transforming "
  97.       +"to the PC space, eliminating some of the worst eigenvectors, and "
  98.       +"then transforming back to the original space.";
  99.   }
  100.   /**
  101.    * Returns an enumeration describing the available options <p>
  102.    *
  103.    * -N <classifier>
  104.    * Don't normalize the input data. <p>
  105.    *
  106.    * @return an enumeration of all the available options
  107.    **/
  108.   public Enumeration listOptions () {
  109.     Vector newVector = new Vector(3);
  110.     newVector.addElement(new Option("tDon't normalize input data." 
  111.     , "D", 0, "-D"));
  112.     newVector.addElement(new Option("tRetain enough PC attributes to account "
  113.     +"ntfor this proportion of variance in "
  114.     +"the original data. (default = 0.95)",
  115.     "R",1,"-R"));
  116.     
  117.     newVector.addElement(new Option("tTransform through the PC space and "
  118.     +"ntback to the original space."
  119.     , "O", 0, "-O"));
  120.     return  newVector.elements();
  121.   }
  122.   /**
  123.    * Parses a given list of options.
  124.    *
  125.    * Valid options are:<p>
  126.    * -N <classifier>
  127.    * Don't normalize the input data. <p>
  128.    *
  129.    * @param options the list of options as an array of strings
  130.    * @exception Exception if an option is not supported
  131.    */
  132.   public void setOptions (String[] options)
  133.     throws Exception
  134.   {
  135.     resetOptions();
  136.     String optionString;
  137.     optionString = Utils.getOption('R', options);
  138.     if (optionString.length() != 0) {
  139.       Double temp;
  140.       temp = Double.valueOf(optionString);
  141.       setVarianceCovered(temp.doubleValue());
  142.     }
  143.     setNormalize(!Utils.getFlag('D', options));
  144.     setTransformBackToOriginal(Utils.getFlag('O', options));
  145.   }
  146.   /**
  147.    * Reset to defaults
  148.    */
  149.   private void resetOptions() {
  150.     m_coverVariance = 0.95;
  151.     m_normalize = true;
  152.     m_sumOfEigenValues = 0.0;
  153.     m_transBackToOriginal = false;
  154.   }
  155.   /**
  156.    * Returns the tip text for this property
  157.    * @return tip text for this property suitable for
  158.    * displaying in the explorer/experimenter gui
  159.    */
  160.   public String normalizeTipText() {
  161.     return "Normalize input data.";
  162.   }
  163.   /**
  164.    * Set whether input data will be normalized.
  165.    * @param n true if input data is to be normalized
  166.    */
  167.   public void setNormalize(boolean n) {
  168.     m_normalize = n;
  169.   }
  170.   /**
  171.    * Gets whether or not input data is to be normalized
  172.    * @return true if input data is to be normalized
  173.    */
  174.   public boolean getNormalize() {
  175.     return m_normalize;
  176.   }
  177.   /**
  178.    * Returns the tip text for this property
  179.    * @return tip text for this property suitable for
  180.    * displaying in the explorer/experimenter gui
  181.    */
  182.   public String varianceCoveredTipText() {
  183.     return "Retain enough PC attributes to account for this proportion of "
  184.       +"variance.";
  185.   }
  186.   /**
  187.    * Sets the amount of variance to account for when retaining
  188.    * principal components
  189.    * @param vc the proportion of total variance to account for
  190.    */
  191.   public void setVarianceCovered(double vc) {
  192.     m_coverVariance = vc;
  193.   }
  194.   /**
  195.    * Gets the proportion of total variance to account for when
  196.    * retaining principal components
  197.    * @return the proportion of variance to account for
  198.    */
  199.   public double getVarianceCovered() {
  200.     return m_coverVariance;
  201.   }
  202.   /**
  203.    * Returns the tip text for this property
  204.    * @return tip text for this property suitable for
  205.    * displaying in the explorer/experimenter gui
  206.    */
  207.   public String transformBackToOriginalTipText() {
  208.     return "Transform through the PC space and back to the original space. "
  209.       +"If only the best n PCs are retained (by setting varianceCovered < 1) "
  210.       +"then this option will give a dataset in the original space but with "
  211.       +"less attribute noise.";
  212.   }
  213.   /**
  214.    * Sets whether the data should be transformed back to the original
  215.    * space
  216.    * @param b true if the data should be transformed back to the
  217.    * original space
  218.    */
  219.   public void setTransformBackToOriginal(boolean b) {
  220.     m_transBackToOriginal = b;
  221.   }
  222.   
  223.   /**
  224.    * Gets whether the data is to be transformed back to the original
  225.    * space.
  226.    * @return true if the data is to be transformed back to the original space
  227.    */
  228.   public boolean getTransformBackToOriginal() {
  229.     return m_transBackToOriginal;
  230.   }
  231.   /**
  232.    * Gets the current settings of PrincipalComponents
  233.    *
  234.    * @return an array of strings suitable for passing to setOptions()
  235.    */
  236.   public String[] getOptions () {
  237.     String[] options = new String[4];
  238.     int current = 0;
  239.     if (!getNormalize()) {
  240.       options[current++] = "-D";
  241.     }
  242.     options[current++] = "-R"; options[current++] = ""+getVarianceCovered();
  243.     if (getTransformBackToOriginal()) {
  244.       options[current++] = "-O";
  245.     }
  246.     
  247.     while (current < options.length) {
  248.       options[current++] = "";
  249.     }
  250.     
  251.     return  options;
  252.   }
  253.   /**
  254.    * Initializes principal components and performs the analysis
  255.    * @param data the instances to analyse/transform
  256.    * @exception Exception if analysis fails
  257.    */
  258.   public void buildEvaluator(Instances data) throws Exception {
  259.     buildAttributeConstructor(data);
  260.   }
  261.   private void buildAttributeConstructor (Instances data) throws Exception {
  262.     m_eigenvalues = null;
  263.     m_outputNumAtts = -1;
  264.     m_attributeFilter = null;
  265.     m_nominalToBinFilter = null;
  266.     m_sumOfEigenValues = 0.0;
  267.     if (data.checkForStringAttributes()) {
  268.       throw  new Exception("Can't handle string attributes!");
  269.     }
  270.     m_trainInstances = data;
  271.     // make a copy of the training data so that we can get the class
  272.     // column to append to the transformed data (if necessary)
  273.     m_trainCopy = new Instances(m_trainInstances);
  274.     
  275.     m_replaceMissingFilter = new ReplaceMissingValuesFilter();
  276.     m_replaceMissingFilter.setInputFormat(m_trainInstances);
  277.     m_trainInstances = Filter.useFilter(m_trainInstances, 
  278. m_replaceMissingFilter);
  279.     if (m_normalize) {
  280.       m_normalizeFilter = new NormalizationFilter();
  281.       m_normalizeFilter.setInputFormat(m_trainInstances);
  282.       m_trainInstances = Filter.useFilter(m_trainInstances, m_normalizeFilter);
  283.     }
  284.     m_nominalToBinFilter = new NominalToBinaryFilter();
  285.     m_nominalToBinFilter.setInputFormat(m_trainInstances);
  286.     m_trainInstances = Filter.useFilter(m_trainInstances, 
  287. m_nominalToBinFilter);
  288.     
  289.     // delete any attributes with only one distinct value or are all missing
  290.     Vector deleteCols = new Vector();
  291.     for (int i=0;i<m_trainInstances.numAttributes();i++) {
  292.       if (m_trainInstances.numDistinctValues(i) <=1) {
  293. deleteCols.addElement(new Integer(i));
  294.       }
  295.     }
  296.     if (m_trainInstances.classIndex() >=0) {
  297.       // get rid of the class column
  298.       m_hasClass = true;
  299.       m_classIndex = m_trainInstances.classIndex();
  300.       deleteCols.addElement(new Integer(m_classIndex));
  301.     }
  302.     // remove columns from the data if necessary
  303.     if (deleteCols.size() > 0) {
  304.       m_attributeFilter = new AttributeFilter();
  305.       int [] todelete = new int [deleteCols.size()];
  306.       for (int i=0;i<deleteCols.size();i++) {
  307. todelete[i] = ((Integer)(deleteCols.elementAt(i))).intValue();
  308.       }
  309.       m_attributeFilter.setAttributeIndicesArray(todelete);
  310.       m_attributeFilter.setInvertSelection(false);
  311.       m_attributeFilter.setInputFormat(m_trainInstances);
  312.       m_trainInstances = Filter.useFilter(m_trainInstances, m_attributeFilter);
  313.     }
  314.     m_numInstances = m_trainInstances.numInstances();
  315.     m_numAttribs = m_trainInstances.numAttributes();
  316.     fillCorrelation();
  317.     double [] d = new double[m_numAttribs+1]; 
  318.     double [][] v = new double[m_numAttribs+1][m_numAttribs+1];
  319.     jacobi(m_correlation, m_numAttribs, d, v);
  320.     m_eigenvectors = (double [][])v.clone();
  321.     
  322.     m_eigenvalues = (double [])d.clone();
  323.     // any eigenvalues less than 0 are not worth anything --- change to 0
  324.     for (int i=0;i<m_eigenvalues.length;i++) {
  325.       if (m_eigenvalues[i] < 0) {
  326. m_eigenvalues[i] = 0.0;
  327.       }
  328.     }
  329.     m_sortedEigens = Utils.sort(m_eigenvalues);
  330.     m_sumOfEigenValues = Utils.sum(m_eigenvalues);
  331.     m_transformedFormat = setOutputFormat();
  332.     if (m_transBackToOriginal) {
  333.       m_originalSpaceFormat = setOutputFormatOriginal();
  334.       
  335.       // new ordered eigenvector matrix
  336.       int numVectors = (m_transformedFormat.classIndex() < 0) 
  337. ? m_transformedFormat.numAttributes()
  338. : m_transformedFormat.numAttributes()-1;
  339.       double [][] orderedVectors = 
  340. new double [m_eigenvectors.length][numVectors+1];
  341.       
  342.       // try converting back to the original space
  343.       for (int i=m_numAttribs;i>(m_numAttribs-numVectors);i--) {
  344. for (int j=1;j<=m_numAttribs;j++) {
  345.   orderedVectors[j][m_numAttribs-i+1] = 
  346.     m_eigenvectors[j][m_sortedEigens[i]];
  347. }
  348.       }
  349.       
  350.       // transpose the matrix
  351.       int nr = orderedVectors.length;
  352.       int nc = orderedVectors[0].length;
  353.       m_eTranspose = 
  354. new double [nc][nr];
  355.       for (int i=0;i<nc;i++) {
  356. for (int j=0;j<nr;j++) {
  357.   m_eTranspose[i][j] = orderedVectors[j][i];
  358. }
  359.       }
  360.     }
  361.   }
  362.   /**
  363.    * Returns just the header for the transformed data (ie. an empty
  364.    * set of instances. This is so that AttributeSelection can
  365.    * determine the structure of the transformed data without actually
  366.    * having to get all the transformed data through getTransformedData().
  367.    * @return the header of the transformed data.
  368.    * @exception Exception if the header of the transformed data can't
  369.    * be determined.
  370.    */
  371.   public Instances transformedHeader() throws Exception {
  372.     if (m_eigenvalues == null) {
  373.       throw new Exception("Principal components hasn't been built yet");
  374.     }
  375.     if (m_transBackToOriginal) {
  376.       return m_originalSpaceFormat;
  377.     } else {
  378.       return m_transformedFormat;
  379.     }
  380.   }
  381.   /**
  382.    * Gets the transformed training data.
  383.    * @return the transformed training data
  384.    * @exception Exception if transformed data can't be returned
  385.    */
  386.   public Instances transformedData() throws Exception {
  387.     if (m_eigenvalues == null) {
  388.       throw new Exception("Principal components hasn't been built yet");
  389.     }
  390.     Instances output;
  391.     if (m_transBackToOriginal) {
  392.       output = new Instances(m_originalSpaceFormat);
  393.     } else {
  394.       output = new Instances(m_transformedFormat);
  395.     }
  396.     for (int i=0;i<m_trainCopy.numInstances();i++) {
  397.       Instance converted = convertInstance(m_trainCopy.instance(i));
  398.       output.add(converted);
  399.     }
  400.     return output;
  401.   }
  402.   /**
  403.    * Evaluates the merit of a transformed attribute. This is defined
  404.    * to be 1 minus the cumulative variance explained. Merit can't
  405.    * be meaningfully evaluated if the data is to be transformed back
  406.    * to the original space.
  407.    * @param att the attribute to be evaluated
  408.    * @return the merit of a transformed attribute
  409.    * @exception Exception if attribute can't be evaluated
  410.    */
  411.   public double evaluateAttribute(int att) throws Exception {
  412.     if (m_eigenvalues == null) {
  413.       throw new Exception("Principal components hasn't been built yet!");
  414.     }
  415.     if (m_transBackToOriginal) {
  416.       return 1.0; // can't evaluate back in the original space!
  417.     }
  418.     // return 1-cumulative variance explained for this transformed att
  419.     double cumulative = 0.0;
  420.     for (int i=m_numAttribs;i>=m_numAttribs-att;i--) {
  421.       cumulative += m_eigenvalues[m_sortedEigens[i]];
  422.     }
  423.     return 1.0-cumulative/m_sumOfEigenValues;
  424.   }
  425.   /**
  426.    * Fill the correlation matrix
  427.    */
  428.   private void fillCorrelation() {
  429.     m_correlation = new double[m_numAttribs+1][m_numAttribs+1];
  430.     double [] att1 = new double [m_numInstances];
  431.     double [] att2 = new double [m_numInstances];
  432.     double corr;
  433.     for (int i=1;i<=m_numAttribs;i++) {
  434.       for (int j=1;j<=m_numAttribs;j++) {
  435. if (i == j) {
  436.   m_correlation[i][j] = 1.0;
  437. } else {
  438.   for (int k=0;k<m_numInstances;k++) {
  439.     att1[k] = m_trainInstances.instance(k).value(i-1);
  440.     att2[k] = m_trainInstances.instance(k).value(j-1);
  441.   }
  442.   corr = Utils.correlation(att1,att2,m_numInstances);
  443.   m_correlation[i][j] = corr;
  444.   m_correlation[i][j] = corr;
  445. }
  446.       }
  447.     }
  448.   }
  449.   /**
  450.    * Return a summary of the analysis
  451.    * @return a summary of the analysis.
  452.    */
  453.   private String principalComponentsSummary() {
  454.     StringBuffer result = new StringBuffer();
  455.     double cumulative = 0.0;
  456.     Instances output = null;
  457.     int numVectors=0;
  458.     try {
  459.       output = setOutputFormat();
  460.       numVectors = (output.classIndex() < 0) 
  461. ? output.numAttributes()
  462. : output.numAttributes()-1;
  463.     } catch (Exception ex) {
  464.     }
  465.     result.append("Correlation matrixn"+matrixToString(m_correlation)
  466.   +"nn");
  467.     result.append("eigenvaluetproportiontcumulativen");
  468.     for (int i=m_numAttribs;i>(m_numAttribs-numVectors);i--) {
  469.       cumulative+=m_eigenvalues[m_sortedEigens[i]];
  470.       result.append(Utils.doubleToString(m_eigenvalues[m_sortedEigens[i]],9,5)
  471.     +"t"+Utils.
  472.     doubleToString((m_eigenvalues[m_sortedEigens[i]] / 
  473.     m_sumOfEigenValues),
  474.      9,5)
  475.     +"t"+Utils.doubleToString((cumulative / 
  476. m_sumOfEigenValues),9,5)
  477.     +"t"+output.attribute(m_numAttribs-i).name()+"n");
  478.     }
  479.     result.append("nEigenvectorsn");
  480.     for (int j=1;j<=numVectors;j++) {
  481.       result.append(" V"+j+'t');
  482.     }
  483.     result.append("n");
  484.     for (int j=1;j<=m_numAttribs;j++) {
  485.       for (int i=m_numAttribs;i>(m_numAttribs-numVectors);i--) {
  486. result.append(Utils.
  487.       doubleToString(m_eigenvectors[j][m_sortedEigens[i]],7,4)
  488.       +"t");
  489.       }
  490.       result.append(m_trainInstances.attribute(j-1).name()+'n');
  491.     }
  492.     if (m_transBackToOriginal) {
  493.       result.append("nPC space transformed back to original space.n"
  494.     +"(Note: can't evaluate attributes in the original "
  495.     +"space)n");
  496.     }
  497.     return result.toString();
  498.   }
  499.   /**
  500.    * Returns a description of this attribute transformer
  501.    * @return a String describing this attribute transformer
  502.    */
  503.   public String toString() {
  504.     if (m_eigenvalues == null) {
  505.       return "Principal components hasn't been built yet!";
  506.     } else {
  507.       return "tPrincipal Components Attribute Transformernn"
  508. +principalComponentsSummary();
  509.     }
  510.   }
  511.   /**
  512.    * Return a matrix as a String
  513.    * @return a String describing a matrix
  514.    */
  515.   private String matrixToString(double [][] matrix) {
  516.     StringBuffer result = new StringBuffer();
  517.     int size = matrix.length-1;
  518.     for (int i=1;i<=size;i++) {
  519.       for (int j=1;j<=size;j++) {
  520. result.append(Utils.doubleToString(matrix[i][j],6,2)+" ");
  521. if (j == size) {
  522.   result.append('n');
  523. }
  524.       }
  525.     }
  526.     return result.toString();
  527.   }
  528.   /**
  529.    * Convert a pc transformed instance back to the original space
  530.    */
  531.   private Instance convertInstanceToOriginal(Instance inst)
  532.     throws Exception {
  533.     double[] newVals;
  534.     if (m_hasClass) {
  535.       newVals = new double[m_numAttribs+1];
  536.     } else {
  537.       newVals = new double[m_numAttribs];
  538.     }
  539.     if (m_hasClass) {
  540.       // class is always appended as the last attribute
  541.       newVals[m_numAttribs] = inst.value(inst.numAttributes()-1);
  542.     }
  543.     for (int i=1;i<m_eTranspose[0].length;i++) {
  544.       double tempval = 0.0;
  545.       for (int j=1;j<m_eTranspose.length;j++) {
  546. tempval += (m_eTranspose[j][i] * 
  547.     inst.value(j - 1));
  548.        }
  549.       newVals[i - 1] = tempval;
  550.     }
  551.     
  552.     if (inst instanceof SparseInstance) {
  553.       return new SparseInstance(inst.weight(), newVals);
  554.     } else {
  555.       return new Instance(inst.weight(), newVals);
  556.     }      
  557.   }
  558.   /**
  559.    * Transform an instance in original (unormalized) format. Convert back
  560.    * to the original space if requested.
  561.    * @param instance an instance in the original (unormalized) format
  562.    * @return a transformed instance
  563.    * @exception Exception if instance cant be transformed
  564.    */
  565.   public Instance convertInstance(Instance instance) throws Exception {
  566.     if (m_eigenvalues == null) {
  567.       throw new Exception("convertInstance: Principal components not "
  568.   +"built yet");
  569.     }
  570.     double[] newVals = new double[m_outputNumAtts];
  571.     Instance tempInst = (Instance)instance.copy();
  572.     if (!instance.equalHeaders(m_trainCopy.instance(0))) {
  573.       throw new Exception("Can't convert instance: header's don't match: "
  574.   +"PrincipalComponents");
  575.     }
  576.     m_replaceMissingFilter.input(tempInst);
  577.     m_replaceMissingFilter.batchFinished();
  578.     tempInst = m_replaceMissingFilter.output();
  579.     if (m_normalize) {
  580.       m_normalizeFilter.input(tempInst);
  581.       m_normalizeFilter.batchFinished();
  582.       tempInst = m_normalizeFilter.output();
  583.     }
  584.     m_nominalToBinFilter.input(tempInst);
  585.     m_nominalToBinFilter.batchFinished();
  586.     tempInst = m_nominalToBinFilter.output();
  587.     if (m_attributeFilter != null) {
  588.       m_attributeFilter.input(tempInst);
  589.       m_attributeFilter.batchFinished();
  590.       tempInst = m_attributeFilter.output();
  591.     }
  592.     if (m_hasClass) {
  593.        newVals[m_outputNumAtts - 1] = instance.value(instance.classIndex());
  594.     }
  595.     double cumulative = 0;
  596.     for (int i = m_numAttribs; i >= 1; i--) {
  597.       double tempval = 0.0;
  598.       for (int j = 1; j <= m_numAttribs; j++) {
  599. tempval += (m_eigenvectors[j][m_sortedEigens[i]] * 
  600.     tempInst.value(j - 1));
  601.        }
  602.       newVals[m_numAttribs - i] = tempval;
  603.       cumulative+=m_eigenvalues[m_sortedEigens[i]];
  604.       if ((cumulative / m_sumOfEigenValues) >= m_coverVariance) {
  605. break;
  606.       }
  607.     }
  608.     
  609.     if (!m_transBackToOriginal) {
  610.       if (instance instanceof SparseInstance) {
  611.       return new SparseInstance(instance.weight(), newVals);
  612.       } else {
  613. return new Instance(instance.weight(), newVals);
  614.       }      
  615.     } else {
  616.       if (instance instanceof SparseInstance) {
  617. return convertInstanceToOriginal(new SparseInstance(instance.weight(), 
  618.     newVals));
  619.       } else {
  620. return convertInstanceToOriginal(new Instance(instance.weight(),
  621.       newVals));
  622.       }
  623.     }
  624.   }
  625.   /**
  626.    * Set up the header for the PC->original space dataset
  627.    */
  628.   private Instances setOutputFormatOriginal() throws Exception {
  629.     FastVector attributes = new FastVector();
  630.     
  631.     for (int i=0;i<m_numAttribs;i++) {
  632.       String att = m_trainInstances.attribute(i).name();
  633.       attributes.addElement(new Attribute(att));
  634.     }
  635.     
  636.     if (m_hasClass) {
  637.       attributes.addElement(m_trainCopy.classAttribute().copy());
  638.     }
  639.     Instances outputFormat = 
  640.       new Instances(m_trainCopy.relationName()+"->PC->original space",
  641.     attributes, 0);
  642.     
  643.     // set the class to be the last attribute if necessary
  644.     if (m_hasClass) {
  645.       outputFormat.setClassIndex(outputFormat.numAttributes()-1);
  646.     }
  647.     return outputFormat;
  648.   }
  649.   /**
  650.    * Set the format for the transformed data
  651.    * @return a set of empty Instances (header only) in the new format
  652.    * @exception Exception if the output format can't be set
  653.    */
  654.   private Instances setOutputFormat() throws Exception {
  655.     if (m_eigenvalues == null) {
  656.       return null;
  657.     }
  658.     double cumulative = 0.0;
  659.     FastVector attributes = new FastVector();
  660.      for (int i=m_numAttribs;i>=1;i--) {
  661.        StringBuffer attName = new StringBuffer();
  662.        for (int j=1;j<=m_numAttribs;j++) {
  663.  attName.append(Utils.
  664. doubleToString(m_eigenvectors[j][m_sortedEigens[i]],
  665.        5,3)
  666. +m_trainInstances.attribute(j-1).name());
  667.  if (j != m_numAttribs) {
  668.    if (m_eigenvectors[j+1][m_sortedEigens[i]] >= 0) {
  669.      attName.append("+");
  670.    }
  671.  }
  672.        }
  673.        attributes.addElement(new Attribute(attName.toString()));
  674.        cumulative+=m_eigenvalues[m_sortedEigens[i]];
  675.        if ((cumulative / m_sumOfEigenValues) >= m_coverVariance) {
  676.  break;
  677.        }
  678.      }
  679.      
  680.      if (m_hasClass) {
  681.        attributes.addElement(m_trainCopy.classAttribute().copy());
  682.      }
  683.      Instances outputFormat = 
  684.        new Instances(m_trainInstances.relationName()+"_principal components",
  685.      attributes, 0);
  686.      // set the class to be the last attribute if necessary
  687.      if (m_hasClass) {
  688.        outputFormat.setClassIndex(outputFormat.numAttributes()-1);
  689.      }
  690.      
  691.      m_outputNumAtts = outputFormat.numAttributes();
  692.      return outputFormat;
  693.   }
  694.   // jacobi routine adapted from numerical recipies
  695.   // note arrays are from 1..n inclusive
  696.   void jacobi(double [][] a, int n, double [] d, double [][] v) {
  697.     int j,iq,ip,i;
  698.     double tresh,theta,tau,t,sm,s,h,g,c;
  699.     double [] b;
  700.     double [] z;
  701.     b = new double [n+1];
  702.     z = new double [n+1];
  703.     for (ip=1;ip<=n;ip++) {
  704.       for (iq=1;iq<=n;iq++) v[ip][iq]=0.0;
  705.       v[ip][ip]=1.0;
  706.     }
  707.     for (ip=1;ip<=n;ip++) {
  708.       b[ip]=d[ip]=a[ip][ip];
  709.       z[ip]=0.0;
  710.     }
  711.     //    *nrot=0;
  712.     for (i=1;i<=50;i++) {
  713.       sm=0.0;
  714.       for (ip=1;ip<=n-1;ip++) {
  715. for (iq=ip+1;iq<=n;iq++)
  716.   sm += Math.abs(a[ip][iq]);
  717.       }
  718.       if (sm == 0.0) {
  719. // free_vector(z,1,n);
  720. // free_vector(b,1,n);
  721. return;
  722.       }
  723.       if (i < 4)
  724. tresh=0.2*sm/(n*n);
  725.       else
  726. tresh=0.0;
  727.       for (ip=1;ip<=n-1;ip++) {
  728. for (iq=ip+1;iq<=n;iq++) {
  729.   g=100.0*Math.abs(a[ip][iq]);
  730.   if (i > 4 && (double)(Math.abs(d[ip])+g) == (double)Math.abs(d[ip])
  731.       && (double)(Math.abs(d[iq])+g) == (double)Math.abs(d[iq]))
  732.     a[ip][iq]=0.0;
  733.   else if (Math.abs(a[ip][iq]) > tresh) {
  734.     h=d[iq]-d[ip];
  735.     if ((double)(Math.abs(h)+g) == (double)Math.abs(h))
  736.       t=(a[ip][iq])/h;
  737.     else {
  738.       theta=0.5*h/(a[ip][iq]);
  739.       t=1.0/(Math.abs(theta)+Math.sqrt(1.0+theta*theta));
  740.       if (theta < 0.0) t = -t;
  741.     }
  742.     c=1.0/Math.sqrt(1+t*t);
  743.     s=t*c;
  744.     tau=s/(1.0+c);
  745.     h=t*a[ip][iq];
  746.     z[ip] -= h;
  747.     z[iq] += h;
  748.     d[ip] -= h;
  749.     d[iq] += h;
  750.     a[ip][iq]=0.0;
  751.     for (j=1;j<=ip-1;j++) {
  752.       //       rotate(a,j,ip,j,iq)
  753.       g=a[j][ip];
  754.       h=a[j][iq];
  755.       a[j][ip]=g-s*(h+g*tau);
  756.       a[j][iq]=h+s*(g-h*tau);
  757.     }
  758.     for (j=ip+1;j<=iq-1;j++) {
  759.       //       rotate(a,ip,j,j,iq)
  760.       g=a[ip][j];
  761.       h=a[j][iq];
  762.       a[ip][j]=g-s*(h+g*tau);
  763.       a[j][iq]=h+s*(g-h*tau);
  764.     }
  765.     for (j=iq+1;j<=n;j++) {
  766.       //       rotate(a,ip,j,iq,j)
  767.       g=a[ip][j];
  768.       h=a[iq][j];
  769.       a[ip][j]=g-s*(h+g*tau);
  770.       a[iq][j]=h+s*(g-h*tau);
  771.     }
  772.     for (j=1;j<=n;j++) {
  773.       //       rotate(v,j,ip,j,iq)
  774.       g=v[j][ip];
  775.       h=v[j][iq];
  776.       v[j][ip]=g-s*(h+g*tau);
  777.       v[j][iq]=h+s*(g-h*tau);
  778.     }
  779.     //     ++(*nrot);
  780.   }
  781. }
  782.       }
  783.       for (ip=1;ip<=n;ip++) {
  784. b[ip] += z[ip];
  785. d[ip]=b[ip];
  786. z[ip]=0.0;
  787.       }
  788.     }
  789.     System.err.println("Too many iterations in routine jacobi");
  790.   }
  791.   /**
  792.    * Main method for testing this class
  793.    * @param argv should contain the command line arguments to the
  794.    * evaluator/transformer (see AttributeSelection)
  795.    */
  796.   public static void main(String [] argv) {
  797.     try {
  798.       System.out.println(AttributeSelection.
  799.  SelectAttributes(new PrincipalComponents(), argv));
  800.     }
  801.     catch (Exception e) {
  802.       e.printStackTrace();
  803.       System.out.println(e.getMessage());
  804.     }
  805.   }
  806. }