content: /*
   *    This program is free software; you can redistribute it and/or modify
   *    it under the terms of the GNU General Public License as published by
   *    the Free Software Foundation; either version 2 of the License, or
   *    (at your option) any later version.
   *
   *    This program is distributed in the hope that it will be useful,
   *    but WITHOUT ANY WARRANTY; without even the implied warranty of
   *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *    GNU General Public License for more details.
   *
   *    You should have received a copy of the GNU General Public License
   *    along with this program; if not, write to the Free Software
   *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   */
  
  /*
   *    LinearRegression.java
   *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
   *
   */
  
  package weka.classifiers.functions;
  
  import weka.classifiers.Classifier;
  import weka.classifiers.AbstractClassifier;
  import weka.core.Capabilities;
  import weka.core.Instance;
  import weka.core.Instances;
  import weka.core.Matrix;
  import weka.core.Option;
  import weka.core.OptionHandler;
  import weka.core.RevisionUtils;
  import weka.core.SelectedTag;
  import weka.core.Tag;
  import weka.core.Utils;
  import weka.core.WeightedInstancesHandler;
  import weka.core.Capabilities.Capability;
  import weka.filters.Filter;
  import weka.filters.supervised.attribute.NominalToBinary;
  import weka.filters.unsupervised.attribute.ReplaceMissingValues;
  
  import java.util.Enumeration;
  import java.util.Vector;
  
  /**
   <!-- globalinfo-start -->
   * Class for using linear regression for prediction. Uses the Akaike criterion for model selection, and is able to deal with weighted instances.
   * <p/>
   <!-- globalinfo-end -->
   *
   <!-- options-start -->
   * Valid options are: <p/>
   * 
   * <pre> -D
   *  Produce debugging output.
   *  (default no debugging output)</pre>
   * 
   * <pre> -S &lt;number of selection method&gt;
   *  Set the attribute selection method to use. 1 = None, 2 = Greedy.
   *  (default 0 = M5' method)</pre>
   * 
   * <pre> -C
   *  Do not try to eliminate colinear attributes.
   * </pre>
   * 
   * <pre> -R &lt;double&gt;
   *  Set ridge parameter (default 1.0e-8).
   * </pre>
   * 
   <!-- options-end -->
   *
   * @author Eibe Frank (eibe@cs.waikato.ac.nz)
   * @author Len Trigg (trigg@cs.waikato.ac.nz)
   * @version $Revision: 5928 $
   */
  public class LinearRegression extends AbstractClassifier implements OptionHandler,
    WeightedInstancesHandler {
    
    /** for serialization */
    static final long serialVersionUID = -3364580862046573747L;
  
    /** Array for storing coefficients of linear regression. */
    private double[] m_Coefficients;
  
    /** Which attributes are relevant? */
    private boolean[] m_SelectedAttributes;
  
    /** Variable for storing transformed training data. */
    private Instances m_TransformedData;
  
    /** The filter for removing missing values. */
    private ReplaceMissingValues m_MissingFilter;
  
    /** The filter storing the transformation from nominal to 
        binary attributes. */
    private NominalToBinary m_TransformFilter;
  
    /** The standard deviations of the class attribute */
    private double m_ClassStdDev;
  
    /** The mean of the class attribute */
    private double m_ClassMean;
  
    /** The index of the class attribute */
    private int m_ClassIndex;
  
    /** The attributes means */
    private double[] m_Means;
  
    /** The attribute standard deviations */
    private double[] m_StdDevs;
  
    /** True if debug output will be printed */
    private boolean b_Debug;
  
    /** The current attribute selection method */
    private int m_AttributeSelection;
  
    /** Attribute selection method: M5 method */
    public static final int SELECTION_M5 = 0;
    /** Attribute selection method: No attribute selection */
    public static final int SELECTION_NONE = 1;
    /** Attribute selection method: Greedy method */
    public static final int SELECTION_GREEDY = 2;
    /** Attribute selection methods */
    public static final Tag [] TAGS_SELECTION = {
      new Tag(SELECTION_NONE, "No attribute selection"),
      new Tag(SELECTION_M5, "M5 method"),
      new Tag(SELECTION_GREEDY, "Greedy method")
    };
  
    /** Try to eliminate correlated attributes? */
    private boolean m_EliminateColinearAttributes = true;
  
    /** Turn off all checks and conversions? */
    private boolean m_checksTurnedOff = false;
  
    /** The ridge parameter */
    private double m_Ridge = 1.0e-8;
  
    /**
     * Turns off checks for missing values, etc. Use with caution.
     * Also turns off scaling.
     */
    public void turnChecksOff() {
  
      m_checksTurnedOff = true;
    }
  
    /**
     * Turns on checks for missing values, etc. Also turns
     * on scaling.
     */
    public void turnChecksOn() {
  
      m_checksTurnedOff = false;
    }
  
    /**
     * Returns a string describing this classifier
     * @return a description of the classifier suitable for
     * displaying in the explorer/experimenter gui
     */
    public String globalInfo() {
      return "Class for using linear regression for prediction. Uses the Akaike "
        +"criterion for model selection, and is able to deal with weighted "
        +"instances.";
    }
  
    /**
     * Returns default capabilities of the classifier.
     *
     * @return      the capabilities of this classifier
     */
    public Capabilities getCapabilities() {
      Capabilities result = super.getCapabilities();
      result.disableAll();
  
      // attributes
      result.enable(Capability.NOMINAL_ATTRIBUTES);
      result.enable(Capability.NUMERIC_ATTRIBUTES);
      result.enable(Capability.DATE_ATTRIBUTES);
      result.enable(Capability.MISSING_VALUES);
  
      // class
      result.enable(Capability.NUMERIC_CLASS);
      result.enable(Capability.DATE_CLASS);
      result.enable(Capability.MISSING_CLASS_VALUES);
      
      return result;
    }
  
    /**
     * Builds a regression model for the given data.
     *
     * @param data the training data to be used for generating the
     * linear regression function
     * @throws Exception if the classifier could not be built successfully
     */
    public void buildClassifier(Instances data) throws Exception {
    
      if (!m_checksTurnedOff) {
        // can classifier handle the data?
        getCapabilities().testWithFail(data);
  
        // remove instances with missing class
        data = new Instances(data);
        data.deleteWithMissingClass();
      }
  
      // Preprocess instances
      if (!m_checksTurnedOff) {
        m_TransformFilter = new NominalToBinary();
        m_TransformFilter.setInputFormat(data);
        data = Filter.useFilter(data, m_TransformFilter);
        m_MissingFilter = new ReplaceMissingValues();
        m_MissingFilter.setInputFormat(data);
        data = Filter.useFilter(data, m_MissingFilter);
        data.deleteWithMissingClass();
      } else {
        m_TransformFilter = null;
        m_MissingFilter = null;
      }
  
      m_ClassIndex = data.classIndex();
      m_TransformedData = data;
  
      // Turn all attributes on for a start
      m_SelectedAttributes = new boolean[data.numAttributes()];
      for (int i = 0; i < data.numAttributes(); i++) {
        if (i != m_ClassIndex) {
  	m_SelectedAttributes[i] = true;
        }
      }
      m_Coefficients = null;
  
      // Compute means and standard deviations
      m_Means = new double[data.numAttributes()];
      m_StdDevs = new double[data.numAttributes()];
      for (int j = 0; j < data.numAttributes(); j++) {
        if (j != data.classIndex()) {
  	m_Means[j] = data.meanOrMode(j);
  	m_StdDevs[j] = Math.sqrt(data.variance(j));
  	if (m_StdDevs[j] == 0) {
  	  m_SelectedAttributes[j] = false;
  	} 
        }
      }
  
      m_ClassStdDev = Math.sqrt(data.variance(m_TransformedData.classIndex()));
      m_ClassMean = data.meanOrMode(m_TransformedData.classIndex());
  
      // Perform the regression
      findBestModel();
  
      // Save memory
      m_TransformedData = new Instances(data, 0);
    }
  
    /**
     * Classifies the given instance using the linear regression function.
     *
     * @param instance the test instance
     * @return the classification
     * @throws Exception if classification can't be done successfully
     */
    public double classifyInstance(Instance instance) throws Exception {
  
      // Transform the input instance
      Instance transformedInstance = instance;
      if (!m_checksTurnedOff) {
        m_TransformFilter.input(transformedInstance);
        m_TransformFilter.batchFinished();
        transformedInstance = m_TransformFilter.output();
        m_MissingFilter.input(transformedInstance);
        m_MissingFilter.batchFinished();
        transformedInstance = m_MissingFilter.output();
      }
  
      // Calculate the dependent variable from the regression model
      return regressionPrediction(transformedInstance,
  				m_SelectedAttributes,
  				m_Coefficients);
    }
  
    /**
     * Outputs the linear regression model as a string.
     * 
     * @return the model as string
     */
    public String toString() {
  
      if (m_TransformedData == null) {
        return "Linear Regression: No model built yet.";
      }
      try {
        StringBuffer text = new StringBuffer();
        int column = 0;
        boolean first = true;
        
        text.append("\nLinear Regression Model\n\n");
        
        text.append(m_TransformedData.classAttribute().name()+" =\n\n");
        for (int i = 0; i < m_TransformedData.numAttributes(); i++) {
  	if ((i != m_ClassIndex) 
  	    && (m_SelectedAttributes[i])) {
  	  if (!first) 
  	    text.append(" +\n");
  	  else
  	    first = false;
  	  text.append(Utils.doubleToString(m_Coefficients[column], 12, 4)
  		      + " * ");
  	  text.append(m_TransformedData.attribute(i).name());
  	  column++;
  	}
        }
        text.append(" +\n" + 
  		  Utils.doubleToString(m_Coefficients[column], 12, 4));
        return text.toString();
      } catch (Exception e) {
        return "Can't print Linear Regression!";
      }
    }
  
    /**
     * Returns an enumeration describing the available options.
     *
     * @return an enumeration of all the available options.
     */
    public Enumeration listOptions() {
      
      Vector newVector = new Vector(4);
      newVector.addElement(new Option("\tProduce debugging output.\n"
  				    + "\t(default no debugging output)",
  				    "D", 0, "-D"));
      newVector.addElement(new Option("\tSet the attribute selection method"
  				    + " to use. 1 = None, 2 = Greedy.\n"
  				    + "\t(default 0 = M5' method)",
  				    "S", 1, "-S <number of selection method>"));
      newVector.addElement(new Option("\tDo not try to eliminate colinear"
  				    + " attributes.\n",
  				    "C", 0, "-C"));
      newVector.addElement(new Option("\tSet ridge parameter (default 1.0e-8).\n",
  				    "R", 1, "-R <double>"));
      return newVector.elements();
    }
  
    /**
     * Parses a given list of options. <p/>
     *
     <!-- options-start -->
     * Valid options are: <p/>
     * 
     * <pre> -D
     *  Produce debugging output.
     *  (default no debugging output)</pre>
     * 
     * <pre> -S &lt;number of selection method&gt;
     *  Set the attribute selection method to use. 1 = None, 2 = Greedy.
     *  (default 0 = M5' method)</pre>
     * 
     * <pre> -C
     *  Do not try to eliminate colinear attributes.
     * </pre>
     * 
     * <pre> -R &lt;double&gt;
     *  Set ridge parameter (default 1.0e-8).
     * </pre>
     * 
     <!-- options-end -->
     *
     * @param options the list of options as an array of strings
     * @throws Exception if an option is not supported
     */
    public void setOptions(String[] options) throws Exception {
  
      String selectionString = Utils.getOption('S', options);
      if (selectionString.length() != 0) {
        setAttributeSelectionMethod(new SelectedTag(Integer
  						  .parseInt(selectionString),
  						  TAGS_SELECTION));
      } else {
        setAttributeSelectionMethod(new SelectedTag(SELECTION_M5,
  						  TAGS_SELECTION));
      }
      String ridgeString = Utils.getOption('R', options);
      if (ridgeString.length() != 0) {
        setRidge(new Double(ridgeString).doubleValue());
      } else {
        setRidge(1.0e-8);
      }
      setDebug(Utils.getFlag('D', options));
      setEliminateColinearAttributes(!Utils.getFlag('C', options));
    }
  
    /**
     * Returns the coefficients for this linear model.
     * 
     * @return the coefficients for this linear model
     */
    public double[] coefficients() {
  
      double[] coefficients = new double[m_SelectedAttributes.length + 1];
      int counter = 0;
      for (int i = 0; i < m_SelectedAttributes.length; i++) {
        if ((m_SelectedAttributes[i]) && ((i != m_ClassIndex))) {
  	coefficients[i] = m_Coefficients[counter++];
        }
      }
      coefficients[m_SelectedAttributes.length] = m_Coefficients[counter];
      return coefficients;
    }
  
    /**
     * Gets the current settings of the classifier.
     *
     * @return an array of strings suitable for passing to setOptions
     */
    public String [] getOptions() {
  
      String [] options = new String [6];
      int current = 0;
  
      options[current++] = "-S";
      options[current++] = "" + getAttributeSelectionMethod()
        .getSelectedTag().getID();
      if (getDebug()) {
        options[current++] = "-D";
      }
      if (!getEliminateColinearAttributes()) {
        options[current++] = "-C";
      }
      options[current++] = "-R";
      options[current++] = "" + getRidge();
  
      while (current < options.length) {
        options[current++] = "";
      }
      return options;
    }
    
    /**
     * Returns the tip text for this property
     * @return tip text for this property suitable for
     * displaying in the explorer/experimenter gui
     */
    public String ridgeTipText() {
      return "The value of the Ridge parameter.";
    }
  
    /**
     * Get the value of Ridge.
     *
     * @return Value of Ridge.
     */
    public double getRidge() {
      
      return m_Ridge;
    }
    
    /**
     * Set the value of Ridge.
     *
     * @param newRidge Value to assign to Ridge.
     */
    public void setRidge(double newRidge) {
      
      m_Ridge = newRidge;
    }
    
    /**
     * Returns the tip text for this property
     * @return tip text for this property suitable for
     * displaying in the explorer/experimenter gui
     */
    public String eliminateColinearAttributesTipText() {
      return "Eliminate colinear attributes.";
    }
  
    /**
     * Get the value of EliminateColinearAttributes.
     *
     * @return Value of EliminateColinearAttributes.
     */
    public boolean getEliminateColinearAttributes() {
      
      return m_EliminateColinearAttributes;
    }
    
    /**
     * Set the value of EliminateColinearAttributes.
     *
     * @param newEliminateColinearAttributes Value to assign to EliminateColinearAttributes.
     */
    public void setEliminateColinearAttributes(boolean newEliminateColinearAttributes) {
      
      m_EliminateColinearAttributes = newEliminateColinearAttributes;
    }
    
    /**
     * Get the number of coefficients used in the model
     *
     * @return the number of coefficients
     */
    public int numParameters()
    {
      return m_Coefficients.length-1;
    }
  
    /**
     * Returns the tip text for this property
     * @return tip text for this property suitable for
     * displaying in the explorer/experimenter gui
     */
    public String attributeSelectionMethodTipText() {
      return "Set the method used to select attributes for use in the linear "
        +"regression. Available methods are: no attribute selection, attribute "
        +"selection using M5's method (step through the attributes removing the one "
        +"with the smallest standardised coefficient until no improvement is observed "
        +"in the estimate of the error given by the Akaike "
        +"information criterion), and a greedy selection using the Akaike information "
        +"metric.";
    }
  
    /**
     * Sets the method used to select attributes for use in the
     * linear regression. 
     *
     * @param method the attribute selection method to use.
     */
    public void setAttributeSelectionMethod(SelectedTag method) {
      
      if (method.getTags() == TAGS_SELECTION) {
        m_AttributeSelection = method.getSelectedTag().getID();
      }
    }
  
    /**
     * Gets the method used to select attributes for use in the
     * linear regression. 
     *
     * @return the method to use.
     */
    public SelectedTag getAttributeSelectionMethod() {
      
      return new SelectedTag(m_AttributeSelection, TAGS_SELECTION);
    }
  
    /**
     * Returns the tip text for this property
     * @return tip text for this property suitable for
     * displaying in the explorer/experimenter gui
     */
    public String debugTipText() {
      return "Outputs debug information to the console.";
    }
  
    /**
     * Controls whether debugging output will be printed
     *
     * @param debug true if debugging output should be printed
     */
    public void setDebug(boolean debug) {
  
      b_Debug = debug;
    }
  
    /**
     * Controls whether debugging output will be printed
     *
     * @return true if debugging output is printed
     */
    public boolean getDebug() {
  
      return b_Debug;
    }
  
    /**
     * Removes the attribute with the highest standardised coefficient
     * greater than 1.5 from the selected attributes.
     *
     * @param selectedAttributes an array of flags indicating which 
     * attributes are included in the regression model
     * @param coefficients an array of coefficients for the regression
     * model
     * @return true if an attribute was removed
     */
    private boolean deselectColinearAttributes(boolean [] selectedAttributes,
  					     double [] coefficients) {
  
      double maxSC = 1.5;
      int maxAttr = -1, coeff = 0;
      for (int i = 0; i < selectedAttributes.length; i++) {
        if (selectedAttributes[i]) {
  	double SC = Math.abs(coefficients[coeff] * m_StdDevs[i] 
  			     / m_ClassStdDev);
  	if (SC > maxSC) {
  	  maxSC = SC;
  	  maxAttr = i;
  	}
  	coeff++;
        }
      }
      if (maxAttr >= 0) {
        selectedAttributes[maxAttr] = false;
        if (b_Debug) {
  	System.out.println("Deselected colinear attribute:" + (maxAttr + 1)
  			   + " with standardised coefficient: " + maxSC);
        }
        return true;
      }
      return false;
    }
  
    /**
     * Performs a greedy search for the best regression model using
     * Akaike's criterion.
     *
     * @throws Exception if regression can't be done
     */
    private void findBestModel() throws Exception {
  
      // For the weighted case we still use numInstances in
      // the calculation of the Akaike criterion. 
      int numInstances = m_TransformedData.numInstances();
  
      if (b_Debug) {
        System.out.println((new Instances(m_TransformedData, 0)).toString());
      }
  
      // Perform a regression for the full model, and remove colinear attributes
      do {
        m_Coefficients = doRegression(m_SelectedAttributes);
      } while (m_EliminateColinearAttributes && 
  	     deselectColinearAttributes(m_SelectedAttributes, m_Coefficients));
  
      // Figure out current number of attributes + 1. (We treat this model
      // as the full model for the Akaike-based methods.)
      int numAttributes = 1;
      for (int i = 0; i < m_SelectedAttributes.length; i++) {
        if (m_SelectedAttributes[i]) {
  	numAttributes++;
        }
      }
  
      double fullMSE = calculateSE(m_SelectedAttributes, m_Coefficients);
      double akaike = (numInstances - numAttributes) + 2 * numAttributes;
      if (b_Debug) {
        System.out.println("Initial Akaike value: " + akaike);
      }
  
      boolean improved;
      int currentNumAttributes = numAttributes;
      switch (m_AttributeSelection) {
  
      case SELECTION_GREEDY:
  
        // Greedy attribute removal
        do {
  	boolean [] currentSelected = (boolean []) m_SelectedAttributes.clone();
  	improved = false;
  	currentNumAttributes--;
  
  	for (int i = 0; i < m_SelectedAttributes.length; i++) {
  	  if (currentSelected[i]) {
  
  	    // Calculate the akaike rating without this attribute
  	    currentSelected[i] = false;
  	    double [] currentCoeffs = doRegression(currentSelected);
  	    double currentMSE = calculateSE(currentSelected, currentCoeffs);
  	    double currentAkaike = currentMSE / fullMSE 
  	      * (numInstances - numAttributes)
  	      + 2 * currentNumAttributes;
  	    if (b_Debug) {
  	      System.out.println("(akaike: " + currentAkaike);
  	    }
  
  	    // If it is better than the current best
  	    if (currentAkaike < akaike) {
  	      if (b_Debug) {
  		System.err.println("Removing attribute " + (i + 1)
  				   + " improved Akaike: " + currentAkaike);
  	      }
  	      improved = true;
  	      akaike = currentAkaike;
  	      System.arraycopy(currentSelected, 0,
  			       m_SelectedAttributes, 0,
  			       m_SelectedAttributes.length);
  	      m_Coefficients = currentCoeffs;
  	    }
  	    currentSelected[i] = true;
  	  }
  	}
        } while (improved);
        break;
  
      case SELECTION_M5:
  
        // Step through the attributes removing the one with the smallest 
        // standardised coefficient until no improvement in Akaike
        do {
  	improved = false;
  	currentNumAttributes--;
  
  	// Find attribute with smallest SC
  	double minSC = 0;
  	int minAttr = -1, coeff = 0;
  	for (int i = 0; i < m_SelectedAttributes.length; i++) {
  	  if (m_SelectedAttributes[i]) {
  	    double SC = Math.abs(m_Coefficients[coeff] * m_StdDevs[i] 
  				 / m_ClassStdDev);
  	    if ((coeff == 0) || (SC < minSC)) {
  	      minSC = SC;
  	      minAttr = i;
  	    }
  	    coeff++;
  	  }
  	}
  
  	// See whether removing it improves the Akaike score
  	if (minAttr >= 0) {
  	  m_SelectedAttributes[minAttr] = false;
  	  double [] currentCoeffs = doRegression(m_SelectedAttributes);
  	  double currentMSE = calculateSE(m_SelectedAttributes, currentCoeffs);
  	  double currentAkaike = currentMSE / fullMSE 
  	    * (numInstances - numAttributes)
  	    + 2 * currentNumAttributes;
  	  if (b_Debug) {
  	    System.out.println("(akaike: " + currentAkaike);
  	  }
  
  	  // If it is better than the current best
  	  if (currentAkaike < akaike) {
  	    if (b_Debug) {
  	      System.err.println("Removing attribute " + (minAttr + 1)
  				 + " improved Akaike: " + currentAkaike);
  	    }
  	    improved = true;
  	    akaike = currentAkaike;
  	    m_Coefficients = currentCoeffs;
  	  } else {
  	    m_SelectedAttributes[minAttr] = true;
  	  }
  	}
        } while (improved);
        break;
  
      case SELECTION_NONE:
        break;
      }
    }
  
    /**
     * Calculate the squared error of a regression model on the 
     * training data
     *
     * @param selectedAttributes an array of flags indicating which 
     * attributes are included in the regression model
     * @param coefficients an array of coefficients for the regression
     * model
     * @return the mean squared error on the training data
     * @throws Exception if there is a missing class value in the training
     * data
     */
    private double calculateSE(boolean [] selectedAttributes, 
  			      double [] coefficients) throws Exception {
  
      double mse = 0;
      for (int i = 0; i < m_TransformedData.numInstances(); i++) {
        double prediction = regressionPrediction(m_TransformedData.instance(i),
  					       selectedAttributes,
  					       coefficients);
        double error = prediction - m_TransformedData.instance(i).classValue();
        mse += error * error;
      }
      return mse;
    }
  
    /**
     * Calculate the dependent value for a given instance for a
     * given regression model.
     *
     * @param transformedInstance the input instance
     * @param selectedAttributes an array of flags indicating which 
     * attributes are included in the regression model
     * @param coefficients an array of coefficients for the regression
     * model
     * @return the regression value for the instance.
     * @throws Exception if the class attribute of the input instance
     * is not assigned
     */
    private double regressionPrediction(Instance transformedInstance,
  				      boolean [] selectedAttributes,
  				      double [] coefficients) 
    throws Exception {
      
      double result = 0;
      int column = 0;
      for (int j = 0; j < transformedInstance.numAttributes(); j++) {
        if ((m_ClassIndex != j) 
  	  && (selectedAttributes[j])) {
  	result += coefficients[column] * transformedInstance.value(j);
  	column++;
        }
      }
      result += coefficients[column];
      
      return result;
    }
  
    /**
     * Calculate a linear regression using the selected attributes
     *
     * @param selectedAttributes an array of booleans where each element
     * is true if the corresponding attribute should be included in the
     * regression.
     * @return an array of coefficients for the linear regression model.
     * @throws Exception if an error occurred during the regression.
     */
    private double [] doRegression(boolean [] selectedAttributes) 
    throws Exception {
  
      if (b_Debug) {
        System.out.print("doRegression(");
        for (int i = 0; i < selectedAttributes.length; i++) {
  	System.out.print(" " + selectedAttributes[i]);
        }
        System.out.println(" )");
      }
      int numAttributes = 0;
      for (int i = 0; i < selectedAttributes.length; i++) {
        if (selectedAttributes[i]) {
  	numAttributes++;
        }
      }
  
      // Check whether there are still attributes left
      Matrix independent = null, dependent = null;
      double[] weights = null;
      if (numAttributes > 0) {
        independent = new Matrix(m_TransformedData.numInstances(), 
  			       numAttributes);
        dependent = new Matrix(m_TransformedData.numInstances(), 1);
        for (int i = 0; i < m_TransformedData.numInstances(); i ++) {
  	Instance inst = m_TransformedData.instance(i);
  	int column = 0;
  	for (int j = 0; j < m_TransformedData.numAttributes(); j++) {
  	  if (j == m_ClassIndex) {
  	    dependent.setElement(i, 0, inst.classValue());
  	  } else {
  	    if (selectedAttributes[j]) {
  	      double value = inst.value(j) - m_Means[j];
  	      
  	      // We only need to do this if we want to
  	      // scale the input
  	      if (!m_checksTurnedOff) {
  		value /= m_StdDevs[j];
  	      }
  	      independent.setElement(i, column, value);
  	      column++;
  	    }
  	  }
  	}
        }
        
        // Grab instance weights
        weights = new double [m_TransformedData.numInstances()];
        for (int i = 0; i < weights.length; i++) {
  	weights[i] = m_TransformedData.instance(i).weight();
        }
      }
  
      // Compute coefficients (note that we have to treat the
      // intercept separately so that it doesn't get affected
      // by the ridge constant.)
      double[] coefficients = new double[numAttributes + 1];
      if (numAttributes > 0) {
        double[] coeffsWithoutIntercept  =
  	independent.regression(dependent, weights, m_Ridge);
        System.arraycopy(coeffsWithoutIntercept, 0, coefficients, 0,
  		       numAttributes);
      }
      coefficients[numAttributes] = m_ClassMean;
  	   
      // Convert coefficients into original scale
      int column = 0;
      for(int i = 0; i < m_TransformedData.numAttributes(); i++) {
        if ((i != m_TransformedData.classIndex()) &&
  	  (selectedAttributes[i])) {
  
  	// We only need to do this if we have scaled the
  	// input.
  	if (!m_checksTurnedOff) {
  	  coefficients[column] /= m_StdDevs[i];
  	}
  
  	// We have centred the input
  	coefficients[coefficients.length - 1] -= 
  	  coefficients[column] * m_Means[i];
  	column++;
        }
      }
  
      return coefficients;
    }
    
    /**
     * Returns the revision string.
     * 
     * @return		the revision
     */
    public String getRevision() {
      return RevisionUtils.extract("$Revision: 5928 $");
    }
   
    /**
     * Generates a linear regression function predictor.
     *
     * @param argv the options
     */
    public static void main(String argv[]) {
      runClassifier(new LinearRegression(), argv);
    }
  }
  